From 1d2a75262082645b1d80d1b1da630edc43e816da Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Tue, 26 Oct 2021 14:44:08 -0400 Subject: [PATCH 001/266] Modification for handling as_dict argument within cursur for PyMSSQL From my somewhat limited looking into all the Python database modules only pymssql allows for the as_dict argument. This change makes it not an inclusion of all the other modules but an exclusion except for the one, pymssql. --- src/DatabaseLibrary/query.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 289d048..9054bc3 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -345,10 +345,10 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): spParams = [] cur = None try: - if self.db_api_module_name in ["cx_Oracle"]: - cur = self._dbconnection.cursor() - else: + if self.db_api_module_name == "pymssql": cur = self._dbconnection.cursor(as_dict=False) + else: + cur = self._dbconnection.cursor() PY3K = sys.version_info >= (3, 0) if not PY3K: spName = spName.encode('ascii', 'ignore') From 83311d12b0ebc265115bd0c90557848a62b3ff5f Mon Sep 17 00:00:00 2001 From: Robin Matz Date: Sat, 8 Apr 2023 05:59:49 +0200 Subject: [PATCH 002/266] Provide custom error message to assertion keywords --- src/DatabaseLibrary/assertion.py | 73 ++++++++++++------- test/SQLite3_DB_Tests.robot | 116 +++++++++++++++++++++++++++---- 2 files changed, 149 insertions(+), 40 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 70ed6dd..07d5d52 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -20,11 +20,11 @@ class Assertion(object): 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, selectStatement, sansTran=False, msg=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 - commit or rollback. + commit or rollback. The default error message can be overridden with the `msg` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -40,19 +40,22 @@ def check_if_exists_in_database(self, selectStatement, sansTran=False): 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 | + + Using optional `msg` to override the default error message: + | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | msg=my error message | """ 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) + raise AssertionError(msg or "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): + def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=None): """ 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. + transaction commit or rollback. The default error message can be overridden with the `msg` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -68,18 +71,21 @@ def check_if_not_exists_in_database(self, selectStatement, sansTran=False): 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 | + + Using optional `msg` to override the default error message: + | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ 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)) + raise AssertionError(msg or "Expected to have have no rows from '%s' " + "but got some rows : %s." % (selectStatement, queryResults)) - def row_count_is_0(self, selectStatement, sansTran=False): + def row_count_is_0(self, selectStatement, sansTran=False, msg=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 - rollback. + rollback. The default error message can be overridden with the `msg` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -95,18 +101,21 @@ def row_count_is_0(self, selectStatement, sansTran=False): 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 | + + Using optional `msg` to override the default error message: + | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ logger.info('Executing : Row Count Is 0 | %s ' % selectStatement) num_rows = self.row_count(selectStatement, sansTran) 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)) + raise AssertionError(msg or "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): + def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg=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. + transaction commit or rollback. The default error message can be overridden with the `msg` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -123,18 +132,21 @@ def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False): 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 | + + Using optional `msg` to override the default error message: + | Row Count Is Equal To X | SELECT id FROM person | 1 | msg=my error message | """ 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)) + raise AssertionError(msg or "Expected same number of rows to be returned from '%s' " + "than the returned rows of %s" % (selectStatement, num_rows)) - def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False): + def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, msg=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. + transaction commit or rollback. The default error message can be overridden with the `msg` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -151,14 +163,17 @@ def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False): 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 | + + Using optional `msg` to override the default error message: + | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | msg=my error message | """ 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)) + raise AssertionError(msg or "Expected more rows to be returned from '%s' " + "than the returned rows of %s" % (selectStatement, num_rows)) - def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False): + def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg=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 @@ -179,17 +194,20 @@ def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False): 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 | + + Using optional `msg` to override the default error message: + | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 | msg=my error message | """ 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)) + raise AssertionError(msg or "Expected less rows to be returned from '%s' " + "than the returned rows of %s" % (selectStatement, num_rows)) - def table_must_exist(self, tableName, sansTran=False): + def table_must_exist(self, tableName, sansTran=False, msg=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. + explicit transaction commit or rollback. The default error message can be overridden with the `msg` argument. For example, given we have a table `person` in a database @@ -202,6 +220,9 @@ def table_must_exist(self, tableName, sansTran=False): Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Table Must Exist | person | True | + + Using optional `msg` to override the default error message: + | Table Must Exist | first_name | msg=my error message | """ logger.info('Executing : Table Must Exist | %s ' % tableName) if self.db_api_module_name in ["cx_Oracle"]: @@ -216,4 +237,4 @@ def table_must_exist(self, tableName, sansTran=False): 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) + raise AssertionError(msg or "Table '%s' does not exist in the db" % tableName) diff --git a/test/SQLite3_DB_Tests.robot b/test/SQLite3_DB_Tests.robot index 3b63684..3a9f7af 100644 --- a/test/SQLite3_DB_Tests.robot +++ b/test/SQLite3_DB_Tests.robot @@ -40,29 +40,117 @@ 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 Exists In DB Fails + [Tags] db smoke + ${expected_error} = Catenate + ... Expected to have have at least one row from 'SELECT id FROM person WHERE first_name = 'Joe';' + ... but got 0 rows. + Run Keyword And Expect Error ${expected_error} Check If Exists In Database + ... SELECT id FROM person WHERE first_name = 'Joe'; + +Check If Exists In DB Fails With Message + [Tags] db smoke + Run Keyword And Expect Error my error message Check If Exists In Database + ... SELECT id FROM person WHERE first_name = 'Joe'; msg=my error message + Check If Not Exists In DB - Joe [Tags] db smoke Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe'; +Check If Not Exists In DB Fails + [Tags] db smoke + ${expected_error} = Catenate + ... Expected to have have no rows from 'SELECT id FROM person WHERE first_name = 'Franz Allan';' + ... but got some rows : [(1,)* + Run Keyword And Expect Error ${expected_error} Check If Not Exists In Database + ... SELECT id FROM person WHERE first_name = 'Franz Allan'; + +Check If Not Exists In DB Fails With Message + [Tags] db smoke + Run Keyword And Expect Error my error message Check If Not Exists In Database + ... SELECT id FROM person WHERE first_name = 'Franz Allan'; msg=my error message + Table Must Exist - person [Tags] db smoke Table Must Exist person -Verify Row Count is 0 +Table Must Exist Fails + [Tags] db smoke + Run Keyword And Expect Error Table 'nonexistent' does not exist in the db + ... Table Must Exist nonexistent + +Table Must Exist Fails With Message + [Tags] db smoke + Run Keyword And Expect Error my error message + ... Table Must Exist nonexistent msg=my error message + +Verify Row Count Is 0 + [Tags] db smoke + Row Count Is 0 SELECT * FROM person WHERE first_name = 'NotHere'; + +Verify Row Count Is 0 Fails + [Tags] db smoke + ${expected_error} = Catenate + ... Expected zero rows to be returned from 'SELECT * FROM person WHERE first_name = 'Franz Allan';' + ... but got rows back. Number of rows returned was 1 + Run Keyword And Expect Error ${expected_error} Row Count Is 0 + ... SELECT * FROM person WHERE first_name = 'Franz Allan'; + +Verify Row Count Is 0 Fails With Message + [Tags] db smoke + Run Keyword And Expect Error my error message Row Count Is 0 + ... SELECT * FROM person WHERE first_name = 'Franz Allan'; msg=my error message + +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 Equal To X Fails + [Tags] db smoke + ${expected_error} = Catenate + ... Expected same number of rows to be returned from 'SELECT id FROM person;' + ... than the returned rows of 2 + Run Keyword And Expect Error ${expected_error} + ... Row Count Is Equal To X SELECT id FROM person; 3 + +Verify Row Count Is Equal To X Fails With Message + [Tags] db smoke + Run Keyword And Expect Error my error message Row Count Is Equal To X + ... SELECT id FROM person; 3 msg=my error message + +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 Less Than X Fails + [Tags] db smoke + ${expected_error} = Catenate + ... Expected less rows to be returned from 'SELECT id FROM person;' + ... than the returned rows of 2 + Run Keyword And Expect Error ${expected_error} Row Count Is Less Than X + ... SELECT id FROM person; 2 + +Verify Row Count Is Less Than X Fails With Message [Tags] db smoke - Row Count is 0 SELECT * FROM person WHERE first_name = 'NotHere'; + Run Keyword And Expect Error my error message Row Count Is Less Than X + ... SELECT id FROM person; 2 msg=my error message -Verify Row Count is Equal to X +Verify Row Count Is Greater Than X [Tags] db smoke - Row Count is Equal to X SELECT id FROM person; 2 + Row Count Is Greater Than X SELECT * FROM person; 1 -Verify Row Count is Less Than X +Verify Row Count Is Greater Than X Fails [Tags] db smoke - Row Count is Less Than X SELECT id FROM person; 3 + ${expected_error} = Catenate + ... Expected more rows to be returned from 'SELECT * FROM person;' + ... than the returned rows of 2 + Run Keyword And Expect Error ${expected_error} + ... Row Count Is Greater Than X SELECT * FROM person; 3 -Verify Row Count is Greater Than X +Verify Row Count Is Greater Than X Fails With Message [Tags] db smoke - Row Count is Greater Than X SELECT * FROM person; 1 + Run Keyword And Expect Error my error message Row Count Is Greater Than X + ... SELECT * FROM person; 3 msg=my error message Retrieve Row Count [Tags] db smoke @@ -118,8 +206,8 @@ 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 @@ -168,7 +256,7 @@ Add person in first transaction Verify person in first transaction [Tags] db smoke - Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True + Row Count Is Equal To X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True Begin second transaction [Tags] db smoke @@ -184,7 +272,7 @@ Add person in second transaction 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 + Row Count Is Equal To X SELECT * FROM person WHERE last_name = 'Baggins'; 2 True Rollback second transaction [Tags] db smoke @@ -194,7 +282,7 @@ Rollback second transaction Verify second transaction rollback [Tags] db smoke - Row Count is Equal to X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True + Row Count Is Equal To X SELECT * FROM person WHERE last_name = 'Baggins'; 1 True Rollback first transaction [Tags] db smoke @@ -204,7 +292,7 @@ Rollback first transaction Verify first transaction rollback [Tags] db smoke - Row Count is 0 SELECT * FROM person WHERE last_name = 'Baggins'; True + Row Count Is 0 SELECT * FROM person WHERE last_name = 'Baggins'; True Drop person and foobar tables [Tags] db smoke From 5d177233185fb6584d3455a5c5ac87c456930528 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sat, 13 May 2023 07:23:59 -0400 Subject: [PATCH 003/266] Initial GitHub Actions Workflows --- .github/workflows/MySQL-tests.yml | 131 +++++++++++++++++++++++++ .github/workflows/PostgreSQL-tests.yml | 71 ++++++++++++++ .github/workflows/README.rst | 53 ++++++++++ .github/workflows/SQLite-tests.yml | 38 +++++++ 4 files changed, 293 insertions(+) create mode 100644 .github/workflows/MySQL-tests.yml create mode 100644 .github/workflows/PostgreSQL-tests.yml create mode 100644 .github/workflows/README.rst create mode 100644 .github/workflows/SQLite-tests.yml diff --git a/.github/workflows/MySQL-tests.yml b/.github/workflows/MySQL-tests.yml new file mode 100644 index 0000000..6e085bd --- /dev/null +++ b/.github/workflows/MySQL-tests.yml @@ -0,0 +1,131 @@ +name: MySQL (pymysql, pyodbc) Tests + +on: [push, pull_request] + +env: + DB_DATABASE: my_db_test + DB_USER: root + DB_PASSWORD: root + DB_HOST: 127.0.0.1 + DB_PORT: 3306 + +jobs: + dbsetup: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # py-db-module: ["pymysql", "pyodbc", "pymssql", "MySQLdb"] + include: + - db-name: PyMySQL + py-db-module: pymysql + pip-install: pymysql + - db-name: PyODBC + py-db-module: pyodbc + pip-install: pyodbc + - db-name: MySQLdb + py-db-module: MySQLdb + pip-install: mysqlclient + steps: + - uses: actions/checkout@v2 + + - name: Set up MySQL + run: | + sudo /etc/init.d/mysql start + mysql -e 'CREATE DATABASE ${{ matrix.py-db-module }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} + + - name: Verify MySQL Setup + run: | + mysql -e "SHOW DATABASES;" -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} + netstat -ano + + - name: Install ODBC driver for PostgreSQL + run: | + echo "*** apt-get install the driver" + sudo apt-get install --yes odbc-postgresql + echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' + ls -l /usr/lib/x86_64-linux-gnu/odbc || true + echo '*** add full paths to Postgres .so files in /etc/odbcinst.ini' + sudo sed -i 's|Driver=psqlodbca.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbca.so|g' /etc/odbcinst.ini + sudo sed -i 's|Driver=psqlodbcw.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so|g' /etc/odbcinst.ini + sudo sed -i 's|Setup=libodbcpsqlS.so|Setup=/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so|g' /etc/odbcinst.ini + + - name: Install ODBC driver for MySQL + run: | + cd "$RUNNER_TEMP" + echo "*** download driver zip file" + curl --silent --show-error --write-out "$CURL_OUTPUT_FORMAT" -O "https://www.mirrorservice.org/sites/ftp.mysql.com/Downloads/Connector-ODBC/8.0/${MYSQL_DRIVER}.tar.gz" + ls -l "${MYSQL_DRIVER}.tar.gz" + tar -xz -f "${MYSQL_DRIVER}.tar.gz" + echo "*** copy driver file to /usr/lib" + sudo cp -v "${MYSQL_DRIVER}/lib/libmyodbc8a.so" /usr/lib/x86_64-linux-gnu/odbc/ + sudo chmod a+r /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so + echo "*** create odbcinst.ini entry" + echo '[MySQL ODBC 8.0 ANSI Driver]' > mysql_odbcinst.ini + echo 'Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so' >> mysql_odbcinst.ini + echo 'UsageCount = 1' >> mysql_odbcinst.ini + echo 'Threading = 2' >> mysql_odbcinst.ini + sudo odbcinst -i -d -f mysql_odbcinst.ini + env: + CURL_OUTPUT_FORMAT: '%{http_code} %{filename_effective} %{size_download} %{time_total}\n' + MYSQL_DRIVER: mysql-connector-odbc-8.0.22-linux-glibc2.12-x86-64bit + + - name: Check ODBC setup + run: | + echo "*** odbcinst -j" + odbcinst -j + echo "*** cat /etc/odbcinst.ini" + cat /etc/odbcinst.ini + echo "*** cat /etc/odbc.ini" + cat /etc/odbc.ini + echo '*** ls -l /opt/microsoft/msodbcsql17/lib64' + ls -l /opt/microsoft/msodbcsql17/lib64 || true + echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' + ls -l /usr/lib/x86_64-linux-gnu/odbc || true + + - uses: actions/setup-python@v2 + with: + python-version: '3.8.16' + + - name: Setup Python Packaging and Pip + run: | + python -m pip install --upgrade pip + pip install wheel + + - name: Setup Robot Framework + run: | + pip install robotframework + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install ${{ github.workspace }}/. + + - name: Setup Python DB module + run: | + pip install ${{ matrix.pip-install }} + + - name: Run Robot Framework tests using PyMySQL + if: matrix.py-db-module == 'pymysql' + working-directory: ./test + run: | + robot -d pymysql/ -v DBModule:${{ matrix.py-db-module }} -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/MySQL_DB_Tests.robot + + - name: Run Robot Framework tests using PyODBC + if: matrix.py-db-module == 'pyodbc' + working-directory: ./test + run: | + python3 -c "import pyodbc; print(pyodbc.drivers())" + robot --loglevel TRACE -d pyodbc/ -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:localhost -v DBPort:${{ env.DB_PORT }} -v DBCharset:utf8mb4 -v dbDriver:"{MySQL ODBC 8.0 ANSI Driver}" ${{ github.workspace }}/test/PyODBC_DB_Tests.robot + + - name: Run Robot Framework tests using ${{ matrix.py-db-module }} + if: matrix.py-db-module == 'ignore-all-modules' + working-directory: ./test + run: | + robot -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/PyODBC_DB_Tests.robot + + - name: Upload Robot Logs + if: always() + uses: actions/upload-artifact@v2 + with: + name: log-files + path: ./test/ \ No newline at end of file diff --git a/.github/workflows/PostgreSQL-tests.yml b/.github/workflows/PostgreSQL-tests.yml new file mode 100644 index 0000000..36e32af --- /dev/null +++ b/.github/workflows/PostgreSQL-tests.yml @@ -0,0 +1,71 @@ +name: PostgreSQL (psycopg2) Tests + +on: [push, pull_request] + +env: + DB_DATABASE: db + DB_USER: postgres + DB_PASSWORD: postgres + DB_HOST: postgres + DB_PORT: 5432 + DB_MODULE: psycopg2 + +jobs: + run-robotframework-tests-psycopg2: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + services: + postgres: + image: postgres:11 + env: + POSTGRES_DB: db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - '5432' + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install ${{ env.DB_MODULE }} + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Set DB options if in ACT + if: ${{ env.ACT }} + run: | + echo "DB_HOST=${{ env.ACT_DB_HOST }}" >> $GITHUB_ENV + echo "DB_PORT=${{ env.ACT_POSTGRES_DB_PORT }}" >> $GITHUB_ENV + + - name: Set DB options if not in ACT + if: ${{ !env.ACT }} + run: | + echo "DB_HOST=localhost" >> $GITHUB_ENV + echo "DB_PORT=${{ job.services.postgres.ports[5432] }}" >> $GITHUB_ENV + + - name: Run Robot Framework tests using psycopg2 + working-directory: ./test + run: | + robot -T --xunit result.xml -d results/ -v DBModule:${{ env.DB_MODULE }} -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/PostgreSQL_DB_Tests.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: log-files + path: ./test/results/ diff --git a/.github/workflows/README.rst b/.github/workflows/README.rst new file mode 100644 index 0000000..d407a8e --- /dev/null +++ b/.github/workflows/README.rst @@ -0,0 +1,53 @@ +Test Framework for the DatabaseLibrary using Github Actions +=========================================================== + +.. contents:: + +!! Work In Progress !! +---------------------- +Note at this time this GitHub Actions CI workflow is a work in progress. I've been trying +to build a "complete" set of database and databse module suites. This notes, which are also +a work in progress, are here to help guide you and remind us of where we are at. + +About this test framework +------------------------- +As I re-exmine using GitHub Actions and expanding the internal test suite, I am reminded +of why creating a test suite with GitHub Actions has been difficult. The number of modules +currently supported (by code) is between nine and twelve. The number of databases known to +be supported by Python is over thirty. So the possibly test matrix for the library is very, +very, large. + +Given the possibilities we will start with looking from the perspective of Python modules +(as compared to databases) and limit it to those nine currently supported by test code. + +Database Systems and Python Modules +---------------------------- + +There are a variety of database systems and Python modules that the DatabaseLibrary supports. This +chart is intended to keep track of those implemented and resources around them. + + +================================== =========== ========================== ======================================= + Database Systems module Status Workflow +================================== =========== ========================== ======================================= +MySQL pymysql Completed MySQL-tests.yml +\ pyodbc Completed MySQL-tests.yml +PostgreSQL psycopg2 Completed PostgreSQL-tests.yml +\ psycopg3 Not Yet Implemented +\ pyodbc Not Yet Implemented +SQLite sqlite3 Completed SQLite-tests.yml +================================== =========== ========================== ======================================= + + +================================== =========== ========================== ======================================= + Database module ..something.. Comment +================================== =========== ========================== ======================================= +Sub-Etha h2g2 The Hitchhiker's Database to the Galaxy +================================== =========== ========================== ======================================= + + +References: + +`PEP 249 - Python Database API Specification v2.0`_ + +`Database interfaces available for Python`_ \ No newline at end of file diff --git a/.github/workflows/SQLite-tests.yml b/.github/workflows/SQLite-tests.yml new file mode 100644 index 0000000..73b2573 --- /dev/null +++ b/.github/workflows/SQLite-tests.yml @@ -0,0 +1,38 @@ +name: SQLite (sqlite3) Tests + +on: [push, pull_request] + +jobs: + run-robotframework-tests-sqlite: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Run Robot Framework tests using SQLite3 + working-directory: ./test + run: | + robot -T --xunit result.xml -d results/ ${{ github.workspace }}/test/SQLite3_DB_Tests.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: log-files + path: ./test/results/ From 3446706739b894ca5fee33012f537baf515d269f Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sat, 13 May 2023 09:26:20 -0400 Subject: [PATCH 004/266] Updated robot tests for Initial Github Actions --- test/MySQL_DB_Tests.robot | 16 ++++++++-------- test/PostgreSQL_DB_Tests.robot | 14 +++++++------- test/PyODBC_DB_Tests.robot | 33 ++++++++++++++++++--------------- test/SQLite3_DB_Tests.robot | 4 ++-- 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/test/MySQL_DB_Tests.robot b/test/MySQL_DB_Tests.robot index d242df9..c7f16d0 100644 --- a/test/MySQL_DB_Tests.robot +++ b/test/MySQL_DB_Tests.robot @@ -1,5 +1,5 @@ *** Settings *** -Suite Setup Connect To Database pymysql ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} +Suite Setup Connect To Database ${DBModule} ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} Suite Teardown Disconnect From Database Library DatabaseLibrary Library OperatingSystem @@ -77,11 +77,11 @@ Verify person Description @{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) + Should Be Equal As Strings ${output} ('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) + Should Be Equal As Strings ${output} ('first_name', 253, None, 80, 80, 0, True) ${output} = Set Variable ${queryResults[2]} - Should Be Equal As Strings ${output} (u'last_name', 253, None, 20, 20, 0, True) + Should Be Equal As Strings ${output} ('last_name', 253, None, 80, 80, 0, True) ${NumColumns} = Get Length ${queryResults} Should Be Equal As Integers ${NumColumns} 3 @@ -91,9 +91,9 @@ Verify foobar Description @{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) + Should Be Equal As Strings ${output} ('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) + Should Be Equal As Strings ${output} ('firstname', 253, None, 80, 80, 0, True) ${NumColumns} = Get Length ${queryResults} Should Be Equal As Integers ${NumColumns} 2 @@ -113,8 +113,8 @@ 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 diff --git a/test/PostgreSQL_DB_Tests.robot b/test/PostgreSQL_DB_Tests.robot index fb458db..9533466 100644 --- a/test/PostgreSQL_DB_Tests.robot +++ b/test/PostgreSQL_DB_Tests.robot @@ -66,11 +66,11 @@ Verify person Description @{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) + Should Be Equal As Strings ${output} Column(name='id', type_code=23) ${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) + Should Be Equal As Strings ${output} Column(name='first_name', type_code=1043) ${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) + Should Be Equal As Strings ${output} Column(name='last_name', type_code=1043) ${NumColumns} = Get Length ${queryResults} Should Be Equal As Integers ${NumColumns} 3 @@ -80,9 +80,9 @@ Verify foobar Description @{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) + Should Be Equal As Strings ${output} Column(name='id', type_code=23) ${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) + Should Be Equal As Strings ${output} Column(name='firstname', type_code=1043) ${NumColumns} = Get Length ${queryResults} Should Be Equal As Integers ${NumColumns} 2 @@ -106,8 +106,8 @@ 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 ${output} = Execute SQL String SELECT COUNT(*) FROM person; diff --git a/test/PyODBC_DB_Tests.robot b/test/PyODBC_DB_Tests.robot index 554b294..3f7feac 100644 --- a/test/PyODBC_DB_Tests.robot +++ b/test/PyODBC_DB_Tests.robot @@ -1,16 +1,19 @@ *** Settings *** -Suite Setup Connect To Database pyodbc ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} dbDriver=${dbDriver} +Suite Setup Connect To Database ${DBModule} ${DBName} ${DBUser} ${DBPass} dbHost=${DBHost} dbPort=${DBPort} dbCharset=${DBCharset} dbDriver=${dbDriver} Suite Teardown Disconnect From Database Library DatabaseLibrary +Library Collections Library OperatingSystem *** Variables *** +${DBModule} pyodbc ${DBHost} ${EMPTY} ${DBName} ${EMPTY} ${DBPass} ${EMPTY} ${DBPort} ${EMPTY} ${DBUser} ${EMPTY} -${dbDriver} ${EMPTY} +${DBCharset} ${None} +#${dbDriver} ${EMPTY} *** Test Cases *** Create person table @@ -61,25 +64,25 @@ Retrieve records from person table Verify person Description Comment Query db for table column descriptions - @{queryResults} = Description SELECT TOP 1 * FROM person; + @{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, None, None, None, None) + Should Be Equal As Strings ${output} ('id', , None, 10, 10, 0, True) ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} (u'first_name', 1, None, None, None, None, None) + Should Be Equal As Strings ${output} ('first_name', , None, 20, 20, 0, True) ${output} = Set Variable ${queryResults[2]} - Should Be Equal As Strings ${output} (u'last_name', 1, None, None, None, None, None) + Should Be Equal As Strings ${output} ('last_name', , None, 20, 20, 0, True) ${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; + @{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, None, None, None, None) + Should Be Equal As Strings ${output} ('id', , None, 10, 10, 0, False) ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} (u'firstname', 1, None, None, None, None, None) + Should Be Equal As Strings ${output} ('firstname', , None, 20, 20, 0, True) ${NumColumns} = Get Length ${queryResults} Should Be Equal As Integers ${NumColumns} 2 @@ -96,8 +99,8 @@ Verify Query - Row Count foobar table 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 + 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; @@ -127,7 +130,7 @@ 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 + ${output} = Execute SQL String SAVEPOINT first True Log ${output} Should Be Equal As Strings ${output} None @@ -140,7 +143,7 @@ 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 + ${output} = Execute SQL String SAVEPOINT second True Log ${output} Should Be Equal As Strings ${output} None @@ -153,7 +156,7 @@ 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 + ${output} = Execute SQL String ROLLBACK TO second True Log ${output} Should Be Equal As Strings ${output} None @@ -161,7 +164,7 @@ 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 + ${output} = Execute SQL String ROLLBACK TO first True Log ${output} Should Be Equal As Strings ${output} None diff --git a/test/SQLite3_DB_Tests.robot b/test/SQLite3_DB_Tests.robot index 3b63684..6c94999 100644 --- a/test/SQLite3_DB_Tests.robot +++ b/test/SQLite3_DB_Tests.robot @@ -118,8 +118,8 @@ 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 From 5429e1d77562bd5acf207e75630d64e098493975 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sat, 13 May 2023 10:56:09 -0400 Subject: [PATCH 005/266] Fixed a couple of issues within the dtabase connector - Switched dbConfigFile from hardcoded default argument to None and then a check if None set to value. This might not be necessary as a string should NOT be a mutable object and thus not a problem. But going to leave this in here anyways. I'll note I don't know what this configfile is nor is there a resource folder so this might need to be updated anyways. - Corrected issue with the PyODBC connection string with the correct format being host:port and not a comma between the two. --- src/DatabaseLibrary/connection_manager.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 3060a45..6801ef4 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -34,7 +34,7 @@ def __init__(self): self._dbconnection = None self.db_api_module_name = None - 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"): + def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None, dbPassword=None, dbHost=None, dbPort=None, dbCharset=None, dbDriver=None, dbConfigFile=None): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using `dbName`, `dbUsername`, and `dbPassword`. @@ -75,6 +75,8 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None | Connect To Database | psycopg2 | my_db_test | """ + if dbConfigFile is None: + dbConfigFile = "./resources/db.cfg" config = ConfigParser.ConfigParser() config.read([dbConfigFile]) @@ -91,6 +93,7 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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)) @@ -102,8 +105,8 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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)) + 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";)' % ( From 2d46910db96aa82478e0aab80034dca32f0afc41 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 30 May 2023 13:21:01 +0200 Subject: [PATCH 006/266] [CI] update actions to later versions because of node.js update warning --- .github/workflows/MySQL-tests.yml | 6 +++--- .github/workflows/PostgreSQL-tests.yml | 6 +++--- .github/workflows/SQLite-tests.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/MySQL-tests.yml b/.github/workflows/MySQL-tests.yml index 6e085bd..c85b78d 100644 --- a/.github/workflows/MySQL-tests.yml +++ b/.github/workflows/MySQL-tests.yml @@ -27,7 +27,7 @@ jobs: py-db-module: MySQLdb pip-install: mysqlclient steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up MySQL run: | @@ -83,7 +83,7 @@ jobs: echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' ls -l /usr/lib/x86_64-linux-gnu/odbc || true - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: '3.8.16' @@ -125,7 +125,7 @@ jobs: - name: Upload Robot Logs if: always() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: log-files path: ./test/ \ No newline at end of file diff --git a/.github/workflows/PostgreSQL-tests.yml b/.github/workflows/PostgreSQL-tests.yml index 36e32af..17a7413 100644 --- a/.github/workflows/PostgreSQL-tests.yml +++ b/.github/workflows/PostgreSQL-tests.yml @@ -30,9 +30,9 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: '3.8.14' @@ -65,7 +65,7 @@ jobs: - name: Upload Robot Logs if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: log-files path: ./test/results/ diff --git a/.github/workflows/SQLite-tests.yml b/.github/workflows/SQLite-tests.yml index 73b2573..61862ea 100644 --- a/.github/workflows/SQLite-tests.yml +++ b/.github/workflows/SQLite-tests.yml @@ -10,9 +10,9 @@ jobs: steps: - name: Check out repository code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: '3.8.14' @@ -32,7 +32,7 @@ jobs: - name: Upload Robot Logs if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: log-files path: ./test/results/ From 2084256a084f43fe575a7390b3204d4a7aa2e909 Mon Sep 17 00:00:00 2001 From: amochin Date: Sun, 4 Jun 2023 11:24:31 +0200 Subject: [PATCH 007/266] ignore python venv --- .gitignore | 748 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 748 insertions(+) diff --git a/.gitignore b/.gitignore index 68d5d70..f4f0807 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,751 @@ test/my_db_test.db test/output.xml test/report.html test/logs/ +venv/pyvenv.cfg +venv/Lib/site-packages/distutils-precedence.pth +venv/Lib/site-packages/_distutils_hack/__init__.py +venv/Lib/site-packages/_distutils_hack/override.py +venv/Lib/site-packages/pip/__init__.py +venv/Lib/site-packages/pip/__main__.py +venv/Lib/site-packages/pip/__pip-runner__.py +venv/Lib/site-packages/pip/py.typed +venv/Lib/site-packages/pip/_internal/__init__.py +venv/Lib/site-packages/pip/_internal/build_env.py +venv/Lib/site-packages/pip/_internal/cache.py +venv/Lib/site-packages/pip/_internal/configuration.py +venv/Lib/site-packages/pip/_internal/exceptions.py +venv/Lib/site-packages/pip/_internal/main.py +venv/Lib/site-packages/pip/_internal/pyproject.py +venv/Lib/site-packages/pip/_internal/self_outdated_check.py +venv/Lib/site-packages/pip/_internal/wheel_builder.py +venv/Lib/site-packages/pip/_internal/cli/__init__.py +venv/Lib/site-packages/pip/_internal/cli/autocompletion.py +venv/Lib/site-packages/pip/_internal/cli/base_command.py +venv/Lib/site-packages/pip/_internal/cli/cmdoptions.py +venv/Lib/site-packages/pip/_internal/cli/command_context.py +venv/Lib/site-packages/pip/_internal/cli/main_parser.py +venv/Lib/site-packages/pip/_internal/cli/main.py +venv/Lib/site-packages/pip/_internal/cli/parser.py +venv/Lib/site-packages/pip/_internal/cli/progress_bars.py +venv/Lib/site-packages/pip/_internal/cli/req_command.py +venv/Lib/site-packages/pip/_internal/cli/spinners.py +venv/Lib/site-packages/pip/_internal/cli/status_codes.py +venv/Lib/site-packages/pip/_internal/commands/__init__.py +venv/Lib/site-packages/pip/_internal/commands/cache.py +venv/Lib/site-packages/pip/_internal/commands/check.py +venv/Lib/site-packages/pip/_internal/commands/completion.py +venv/Lib/site-packages/pip/_internal/commands/configuration.py +venv/Lib/site-packages/pip/_internal/commands/debug.py +venv/Lib/site-packages/pip/_internal/commands/download.py +venv/Lib/site-packages/pip/_internal/commands/freeze.py +venv/Lib/site-packages/pip/_internal/commands/hash.py +venv/Lib/site-packages/pip/_internal/commands/help.py +venv/Lib/site-packages/pip/_internal/commands/index.py +venv/Lib/site-packages/pip/_internal/commands/inspect.py +venv/Lib/site-packages/pip/_internal/commands/install.py +venv/Lib/site-packages/pip/_internal/commands/list.py +venv/Lib/site-packages/pip/_internal/commands/search.py +venv/Lib/site-packages/pip/_internal/commands/show.py +venv/Lib/site-packages/pip/_internal/commands/uninstall.py +venv/Lib/site-packages/pip/_internal/commands/wheel.py +venv/Lib/site-packages/pip/_internal/distributions/__init__.py +venv/Lib/site-packages/pip/_internal/distributions/base.py +venv/Lib/site-packages/pip/_internal/distributions/installed.py +venv/Lib/site-packages/pip/_internal/distributions/sdist.py +venv/Lib/site-packages/pip/_internal/distributions/wheel.py +venv/Lib/site-packages/pip/_internal/index/__init__.py +venv/Lib/site-packages/pip/_internal/index/collector.py +venv/Lib/site-packages/pip/_internal/index/package_finder.py +venv/Lib/site-packages/pip/_internal/index/sources.py +venv/Lib/site-packages/pip/_internal/locations/__init__.py +venv/Lib/site-packages/pip/_internal/locations/_distutils.py +venv/Lib/site-packages/pip/_internal/locations/_sysconfig.py +venv/Lib/site-packages/pip/_internal/locations/base.py +venv/Lib/site-packages/pip/_internal/metadata/__init__.py +venv/Lib/site-packages/pip/_internal/metadata/_json.py +venv/Lib/site-packages/pip/_internal/metadata/base.py +venv/Lib/site-packages/pip/_internal/metadata/pkg_resources.py +venv/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py +venv/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py +venv/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py +venv/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py +venv/Lib/site-packages/pip/_internal/models/__init__.py +venv/Lib/site-packages/pip/_internal/models/candidate.py +venv/Lib/site-packages/pip/_internal/models/direct_url.py +venv/Lib/site-packages/pip/_internal/models/format_control.py +venv/Lib/site-packages/pip/_internal/models/index.py +venv/Lib/site-packages/pip/_internal/models/installation_report.py +venv/Lib/site-packages/pip/_internal/models/link.py +venv/Lib/site-packages/pip/_internal/models/scheme.py +venv/Lib/site-packages/pip/_internal/models/search_scope.py +venv/Lib/site-packages/pip/_internal/models/selection_prefs.py +venv/Lib/site-packages/pip/_internal/models/target_python.py +venv/Lib/site-packages/pip/_internal/models/wheel.py +venv/Lib/site-packages/pip/_internal/network/__init__.py +venv/Lib/site-packages/pip/_internal/network/auth.py +venv/Lib/site-packages/pip/_internal/network/cache.py +venv/Lib/site-packages/pip/_internal/network/download.py +venv/Lib/site-packages/pip/_internal/network/lazy_wheel.py +venv/Lib/site-packages/pip/_internal/network/session.py +venv/Lib/site-packages/pip/_internal/network/utils.py +venv/Lib/site-packages/pip/_internal/network/xmlrpc.py +venv/Lib/site-packages/pip/_internal/operations/__init__.py +venv/Lib/site-packages/pip/_internal/operations/check.py +venv/Lib/site-packages/pip/_internal/operations/freeze.py +venv/Lib/site-packages/pip/_internal/operations/prepare.py +venv/Lib/site-packages/pip/_internal/operations/install/__init__.py +venv/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py +venv/Lib/site-packages/pip/_internal/operations/install/legacy.py +venv/Lib/site-packages/pip/_internal/operations/install/wheel.py +venv/Lib/site-packages/pip/_internal/req/__init__.py +venv/Lib/site-packages/pip/_internal/req/constructors.py +venv/Lib/site-packages/pip/_internal/req/req_file.py +venv/Lib/site-packages/pip/_internal/req/req_install.py +venv/Lib/site-packages/pip/_internal/req/req_set.py +venv/Lib/site-packages/pip/_internal/req/req_uninstall.py +venv/Lib/site-packages/pip/_internal/resolution/__init__.py +venv/Lib/site-packages/pip/_internal/resolution/base.py +venv/Lib/site-packages/pip/_internal/resolution/legacy/__init__.py +venv/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__init__.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/base.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/candidates.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/factory.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/provider.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/reporter.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/requirements.py +venv/Lib/site-packages/pip/_internal/resolution/resolvelib/resolver.py +venv/Lib/site-packages/pip/_internal/utils/__init__.py +venv/Lib/site-packages/pip/_internal/utils/_log.py +venv/Lib/site-packages/pip/_internal/utils/appdirs.py +venv/Lib/site-packages/pip/_internal/utils/compat.py +venv/Lib/site-packages/pip/_internal/utils/compatibility_tags.py +venv/Lib/site-packages/pip/_internal/utils/datetime.py +venv/Lib/site-packages/pip/_internal/utils/deprecation.py +venv/Lib/site-packages/pip/_internal/utils/direct_url_helpers.py +venv/Lib/site-packages/pip/_internal/utils/distutils_args.py +venv/Lib/site-packages/pip/_internal/utils/egg_link.py +venv/Lib/site-packages/pip/_internal/utils/encoding.py +venv/Lib/site-packages/pip/_internal/utils/entrypoints.py +venv/Lib/site-packages/pip/_internal/utils/filesystem.py +venv/Lib/site-packages/pip/_internal/utils/filetypes.py +venv/Lib/site-packages/pip/_internal/utils/glibc.py +venv/Lib/site-packages/pip/_internal/utils/hashes.py +venv/Lib/site-packages/pip/_internal/utils/inject_securetransport.py +venv/Lib/site-packages/pip/_internal/utils/logging.py +venv/Lib/site-packages/pip/_internal/utils/misc.py +venv/Lib/site-packages/pip/_internal/utils/models.py +venv/Lib/site-packages/pip/_internal/utils/packaging.py +venv/Lib/site-packages/pip/_internal/utils/setuptools_build.py +venv/Lib/site-packages/pip/_internal/utils/subprocess.py +venv/Lib/site-packages/pip/_internal/utils/temp_dir.py +venv/Lib/site-packages/pip/_internal/utils/unpacking.py +venv/Lib/site-packages/pip/_internal/utils/urls.py +venv/Lib/site-packages/pip/_internal/utils/virtualenv.py +venv/Lib/site-packages/pip/_internal/utils/wheel.py +venv/Lib/site-packages/pip/_internal/vcs/__init__.py +venv/Lib/site-packages/pip/_internal/vcs/bazaar.py +venv/Lib/site-packages/pip/_internal/vcs/git.py +venv/Lib/site-packages/pip/_internal/vcs/mercurial.py +venv/Lib/site-packages/pip/_internal/vcs/subversion.py +venv/Lib/site-packages/pip/_internal/vcs/versioncontrol.py +venv/Lib/site-packages/pip/_vendor/__init__.py +venv/Lib/site-packages/pip/_vendor/six.py +venv/Lib/site-packages/pip/_vendor/typing_extensions.py +venv/Lib/site-packages/pip/_vendor/vendor.txt +venv/Lib/site-packages/pip/_vendor/cachecontrol/__init__.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/_cmd.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/adapter.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/cache.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/compat.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/controller.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/filewrapper.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/heuristics.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/serialize.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/wrapper.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__init__.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py +venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py +venv/Lib/site-packages/pip/_vendor/certifi/__init__.py +venv/Lib/site-packages/pip/_vendor/certifi/__main__.py +venv/Lib/site-packages/pip/_vendor/certifi/cacert.pem +venv/Lib/site-packages/pip/_vendor/certifi/core.py +venv/Lib/site-packages/pip/_vendor/chardet/__init__.py +venv/Lib/site-packages/pip/_vendor/chardet/big5freq.py +venv/Lib/site-packages/pip/_vendor/chardet/big5prober.py +venv/Lib/site-packages/pip/_vendor/chardet/chardistribution.py +venv/Lib/site-packages/pip/_vendor/chardet/charsetgroupprober.py +venv/Lib/site-packages/pip/_vendor/chardet/charsetprober.py +venv/Lib/site-packages/pip/_vendor/chardet/codingstatemachine.py +venv/Lib/site-packages/pip/_vendor/chardet/cp949prober.py +venv/Lib/site-packages/pip/_vendor/chardet/enums.py +venv/Lib/site-packages/pip/_vendor/chardet/escprober.py +venv/Lib/site-packages/pip/_vendor/chardet/escsm.py +venv/Lib/site-packages/pip/_vendor/chardet/eucjpprober.py +venv/Lib/site-packages/pip/_vendor/chardet/euckrfreq.py +venv/Lib/site-packages/pip/_vendor/chardet/euckrprober.py +venv/Lib/site-packages/pip/_vendor/chardet/euctwfreq.py +venv/Lib/site-packages/pip/_vendor/chardet/euctwprober.py +venv/Lib/site-packages/pip/_vendor/chardet/gb2312freq.py +venv/Lib/site-packages/pip/_vendor/chardet/gb2312prober.py +venv/Lib/site-packages/pip/_vendor/chardet/hebrewprober.py +venv/Lib/site-packages/pip/_vendor/chardet/jisfreq.py +venv/Lib/site-packages/pip/_vendor/chardet/johabfreq.py +venv/Lib/site-packages/pip/_vendor/chardet/johabprober.py +venv/Lib/site-packages/pip/_vendor/chardet/jpcntx.py +venv/Lib/site-packages/pip/_vendor/chardet/langbulgarianmodel.py +venv/Lib/site-packages/pip/_vendor/chardet/langgreekmodel.py +venv/Lib/site-packages/pip/_vendor/chardet/langhebrewmodel.py +venv/Lib/site-packages/pip/_vendor/chardet/langhungarianmodel.py +venv/Lib/site-packages/pip/_vendor/chardet/langrussianmodel.py +venv/Lib/site-packages/pip/_vendor/chardet/langthaimodel.py +venv/Lib/site-packages/pip/_vendor/chardet/langturkishmodel.py +venv/Lib/site-packages/pip/_vendor/chardet/latin1prober.py +venv/Lib/site-packages/pip/_vendor/chardet/mbcharsetprober.py +venv/Lib/site-packages/pip/_vendor/chardet/mbcsgroupprober.py +venv/Lib/site-packages/pip/_vendor/chardet/mbcssm.py +venv/Lib/site-packages/pip/_vendor/chardet/sbcharsetprober.py +venv/Lib/site-packages/pip/_vendor/chardet/sbcsgroupprober.py +venv/Lib/site-packages/pip/_vendor/chardet/sjisprober.py +venv/Lib/site-packages/pip/_vendor/chardet/universaldetector.py +venv/Lib/site-packages/pip/_vendor/chardet/utf8prober.py +venv/Lib/site-packages/pip/_vendor/chardet/utf1632prober.py +venv/Lib/site-packages/pip/_vendor/chardet/version.py +venv/Lib/site-packages/pip/_vendor/chardet/cli/__init__.py +venv/Lib/site-packages/pip/_vendor/chardet/cli/chardetect.py +venv/Lib/site-packages/pip/_vendor/chardet/metadata/__init__.py +venv/Lib/site-packages/pip/_vendor/chardet/metadata/languages.py +venv/Lib/site-packages/pip/_vendor/colorama/__init__.py +venv/Lib/site-packages/pip/_vendor/colorama/ansi.py +venv/Lib/site-packages/pip/_vendor/colorama/ansitowin32.py +venv/Lib/site-packages/pip/_vendor/colorama/initialise.py +venv/Lib/site-packages/pip/_vendor/colorama/win32.py +venv/Lib/site-packages/pip/_vendor/colorama/winterm.py +venv/Lib/site-packages/pip/_vendor/distlib/__init__.py +venv/Lib/site-packages/pip/_vendor/distlib/compat.py +venv/Lib/site-packages/pip/_vendor/distlib/database.py +venv/Lib/site-packages/pip/_vendor/distlib/index.py +venv/Lib/site-packages/pip/_vendor/distlib/locators.py +venv/Lib/site-packages/pip/_vendor/distlib/manifest.py +venv/Lib/site-packages/pip/_vendor/distlib/markers.py +venv/Lib/site-packages/pip/_vendor/distlib/metadata.py +venv/Lib/site-packages/pip/_vendor/distlib/resources.py +venv/Lib/site-packages/pip/_vendor/distlib/scripts.py +venv/Lib/site-packages/pip/_vendor/distlib/t32.exe +venv/Lib/site-packages/pip/_vendor/distlib/t64-arm.exe +venv/Lib/site-packages/pip/_vendor/distlib/t64.exe +venv/Lib/site-packages/pip/_vendor/distlib/util.py +venv/Lib/site-packages/pip/_vendor/distlib/version.py +venv/Lib/site-packages/pip/_vendor/distlib/w32.exe +venv/Lib/site-packages/pip/_vendor/distlib/w64-arm.exe +venv/Lib/site-packages/pip/_vendor/distlib/w64.exe +venv/Lib/site-packages/pip/_vendor/distlib/wheel.py +venv/Lib/site-packages/pip/_vendor/distro/__init__.py +venv/Lib/site-packages/pip/_vendor/distro/__main__.py +venv/Lib/site-packages/pip/_vendor/distro/distro.py +venv/Lib/site-packages/pip/_vendor/idna/__init__.py +venv/Lib/site-packages/pip/_vendor/idna/codec.py +venv/Lib/site-packages/pip/_vendor/idna/compat.py +venv/Lib/site-packages/pip/_vendor/idna/core.py +venv/Lib/site-packages/pip/_vendor/idna/idnadata.py +venv/Lib/site-packages/pip/_vendor/idna/intranges.py +venv/Lib/site-packages/pip/_vendor/idna/package_data.py +venv/Lib/site-packages/pip/_vendor/idna/uts46data.py +venv/Lib/site-packages/pip/_vendor/msgpack/__init__.py +venv/Lib/site-packages/pip/_vendor/msgpack/exceptions.py +venv/Lib/site-packages/pip/_vendor/msgpack/ext.py +venv/Lib/site-packages/pip/_vendor/msgpack/fallback.py +venv/Lib/site-packages/pip/_vendor/packaging/__about__.py +venv/Lib/site-packages/pip/_vendor/packaging/__init__.py +venv/Lib/site-packages/pip/_vendor/packaging/_manylinux.py +venv/Lib/site-packages/pip/_vendor/packaging/_musllinux.py +venv/Lib/site-packages/pip/_vendor/packaging/_structures.py +venv/Lib/site-packages/pip/_vendor/packaging/markers.py +venv/Lib/site-packages/pip/_vendor/packaging/requirements.py +venv/Lib/site-packages/pip/_vendor/packaging/specifiers.py +venv/Lib/site-packages/pip/_vendor/packaging/tags.py +venv/Lib/site-packages/pip/_vendor/packaging/utils.py +venv/Lib/site-packages/pip/_vendor/packaging/version.py +venv/Lib/site-packages/pip/_vendor/pep517/__init__.py +venv/Lib/site-packages/pip/_vendor/pep517/_compat.py +venv/Lib/site-packages/pip/_vendor/pep517/build.py +venv/Lib/site-packages/pip/_vendor/pep517/check.py +venv/Lib/site-packages/pip/_vendor/pep517/colorlog.py +venv/Lib/site-packages/pip/_vendor/pep517/dirtools.py +venv/Lib/site-packages/pip/_vendor/pep517/envbuild.py +venv/Lib/site-packages/pip/_vendor/pep517/meta.py +venv/Lib/site-packages/pip/_vendor/pep517/wrappers.py +venv/Lib/site-packages/pip/_vendor/pep517/in_process/__init__.py +venv/Lib/site-packages/pip/_vendor/pep517/in_process/_in_process.py +venv/Lib/site-packages/pip/_vendor/pkg_resources/__init__.py +venv/Lib/site-packages/pip/_vendor/pkg_resources/py31compat.py +venv/Lib/site-packages/pip/_vendor/platformdirs/__init__.py +venv/Lib/site-packages/pip/_vendor/platformdirs/__main__.py +venv/Lib/site-packages/pip/_vendor/platformdirs/android.py +venv/Lib/site-packages/pip/_vendor/platformdirs/api.py +venv/Lib/site-packages/pip/_vendor/platformdirs/macos.py +venv/Lib/site-packages/pip/_vendor/platformdirs/unix.py +venv/Lib/site-packages/pip/_vendor/platformdirs/version.py +venv/Lib/site-packages/pip/_vendor/platformdirs/windows.py +venv/Lib/site-packages/pip/_vendor/pygments/__init__.py +venv/Lib/site-packages/pip/_vendor/pygments/__main__.py +venv/Lib/site-packages/pip/_vendor/pygments/cmdline.py +venv/Lib/site-packages/pip/_vendor/pygments/console.py +venv/Lib/site-packages/pip/_vendor/pygments/filter.py +venv/Lib/site-packages/pip/_vendor/pygments/formatter.py +venv/Lib/site-packages/pip/_vendor/pygments/lexer.py +venv/Lib/site-packages/pip/_vendor/pygments/modeline.py +venv/Lib/site-packages/pip/_vendor/pygments/plugin.py +venv/Lib/site-packages/pip/_vendor/pygments/regexopt.py +venv/Lib/site-packages/pip/_vendor/pygments/scanner.py +venv/Lib/site-packages/pip/_vendor/pygments/sphinxext.py +venv/Lib/site-packages/pip/_vendor/pygments/style.py +venv/Lib/site-packages/pip/_vendor/pygments/token.py +venv/Lib/site-packages/pip/_vendor/pygments/unistring.py +venv/Lib/site-packages/pip/_vendor/pygments/util.py +venv/Lib/site-packages/pip/_vendor/pygments/filters/__init__.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/__init__.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/_mapping.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/bbcode.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/groff.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/html.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/img.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/irc.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/latex.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/other.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/rtf.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/svg.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal.py +venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal256.py +venv/Lib/site-packages/pip/_vendor/pygments/lexers/__init__.py +venv/Lib/site-packages/pip/_vendor/pygments/lexers/_mapping.py +venv/Lib/site-packages/pip/_vendor/pygments/lexers/python.py +venv/Lib/site-packages/pip/_vendor/pygments/styles/__init__.py +venv/Lib/site-packages/pip/_vendor/pyparsing/__init__.py +venv/Lib/site-packages/pip/_vendor/pyparsing/actions.py +venv/Lib/site-packages/pip/_vendor/pyparsing/common.py +venv/Lib/site-packages/pip/_vendor/pyparsing/core.py +venv/Lib/site-packages/pip/_vendor/pyparsing/exceptions.py +venv/Lib/site-packages/pip/_vendor/pyparsing/helpers.py +venv/Lib/site-packages/pip/_vendor/pyparsing/results.py +venv/Lib/site-packages/pip/_vendor/pyparsing/testing.py +venv/Lib/site-packages/pip/_vendor/pyparsing/unicode.py +venv/Lib/site-packages/pip/_vendor/pyparsing/util.py +venv/Lib/site-packages/pip/_vendor/pyparsing/diagram/__init__.py +venv/Lib/site-packages/pip/_vendor/requests/__init__.py +venv/Lib/site-packages/pip/_vendor/requests/__version__.py +venv/Lib/site-packages/pip/_vendor/requests/_internal_utils.py +venv/Lib/site-packages/pip/_vendor/requests/adapters.py +venv/Lib/site-packages/pip/_vendor/requests/api.py +venv/Lib/site-packages/pip/_vendor/requests/auth.py +venv/Lib/site-packages/pip/_vendor/requests/certs.py +venv/Lib/site-packages/pip/_vendor/requests/compat.py +venv/Lib/site-packages/pip/_vendor/requests/cookies.py +venv/Lib/site-packages/pip/_vendor/requests/exceptions.py +venv/Lib/site-packages/pip/_vendor/requests/help.py +venv/Lib/site-packages/pip/_vendor/requests/hooks.py +venv/Lib/site-packages/pip/_vendor/requests/models.py +venv/Lib/site-packages/pip/_vendor/requests/packages.py +venv/Lib/site-packages/pip/_vendor/requests/sessions.py +venv/Lib/site-packages/pip/_vendor/requests/status_codes.py +venv/Lib/site-packages/pip/_vendor/requests/structures.py +venv/Lib/site-packages/pip/_vendor/requests/utils.py +venv/Lib/site-packages/pip/_vendor/resolvelib/__init__.py +venv/Lib/site-packages/pip/_vendor/resolvelib/providers.py +venv/Lib/site-packages/pip/_vendor/resolvelib/reporters.py +venv/Lib/site-packages/pip/_vendor/resolvelib/resolvers.py +venv/Lib/site-packages/pip/_vendor/resolvelib/structs.py +venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__init__.py +venv/Lib/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py +venv/Lib/site-packages/pip/_vendor/rich/__init__.py +venv/Lib/site-packages/pip/_vendor/rich/__main__.py +venv/Lib/site-packages/pip/_vendor/rich/_cell_widths.py +venv/Lib/site-packages/pip/_vendor/rich/_emoji_codes.py +venv/Lib/site-packages/pip/_vendor/rich/_emoji_replace.py +venv/Lib/site-packages/pip/_vendor/rich/_export_format.py +venv/Lib/site-packages/pip/_vendor/rich/_extension.py +venv/Lib/site-packages/pip/_vendor/rich/_inspect.py +venv/Lib/site-packages/pip/_vendor/rich/_log_render.py +venv/Lib/site-packages/pip/_vendor/rich/_loop.py +venv/Lib/site-packages/pip/_vendor/rich/_palettes.py +venv/Lib/site-packages/pip/_vendor/rich/_pick.py +venv/Lib/site-packages/pip/_vendor/rich/_ratio.py +venv/Lib/site-packages/pip/_vendor/rich/_spinners.py +venv/Lib/site-packages/pip/_vendor/rich/_stack.py +venv/Lib/site-packages/pip/_vendor/rich/_timer.py +venv/Lib/site-packages/pip/_vendor/rich/_win32_console.py +venv/Lib/site-packages/pip/_vendor/rich/_windows_renderer.py +venv/Lib/site-packages/pip/_vendor/rich/_windows.py +venv/Lib/site-packages/pip/_vendor/rich/_wrap.py +venv/Lib/site-packages/pip/_vendor/rich/abc.py +venv/Lib/site-packages/pip/_vendor/rich/align.py +venv/Lib/site-packages/pip/_vendor/rich/ansi.py +venv/Lib/site-packages/pip/_vendor/rich/bar.py +venv/Lib/site-packages/pip/_vendor/rich/box.py +venv/Lib/site-packages/pip/_vendor/rich/cells.py +venv/Lib/site-packages/pip/_vendor/rich/color_triplet.py +venv/Lib/site-packages/pip/_vendor/rich/color.py +venv/Lib/site-packages/pip/_vendor/rich/columns.py +venv/Lib/site-packages/pip/_vendor/rich/console.py +venv/Lib/site-packages/pip/_vendor/rich/constrain.py +venv/Lib/site-packages/pip/_vendor/rich/containers.py +venv/Lib/site-packages/pip/_vendor/rich/control.py +venv/Lib/site-packages/pip/_vendor/rich/default_styles.py +venv/Lib/site-packages/pip/_vendor/rich/diagnose.py +venv/Lib/site-packages/pip/_vendor/rich/emoji.py +venv/Lib/site-packages/pip/_vendor/rich/errors.py +venv/Lib/site-packages/pip/_vendor/rich/file_proxy.py +venv/Lib/site-packages/pip/_vendor/rich/filesize.py +venv/Lib/site-packages/pip/_vendor/rich/highlighter.py +venv/Lib/site-packages/pip/_vendor/rich/json.py +venv/Lib/site-packages/pip/_vendor/rich/jupyter.py +venv/Lib/site-packages/pip/_vendor/rich/layout.py +venv/Lib/site-packages/pip/_vendor/rich/live_render.py +venv/Lib/site-packages/pip/_vendor/rich/live.py +venv/Lib/site-packages/pip/_vendor/rich/logging.py +venv/Lib/site-packages/pip/_vendor/rich/markup.py +venv/Lib/site-packages/pip/_vendor/rich/measure.py +venv/Lib/site-packages/pip/_vendor/rich/padding.py +venv/Lib/site-packages/pip/_vendor/rich/pager.py +venv/Lib/site-packages/pip/_vendor/rich/palette.py +venv/Lib/site-packages/pip/_vendor/rich/panel.py +venv/Lib/site-packages/pip/_vendor/rich/pretty.py +venv/Lib/site-packages/pip/_vendor/rich/progress_bar.py +venv/Lib/site-packages/pip/_vendor/rich/progress.py +venv/Lib/site-packages/pip/_vendor/rich/prompt.py +venv/Lib/site-packages/pip/_vendor/rich/protocol.py +venv/Lib/site-packages/pip/_vendor/rich/region.py +venv/Lib/site-packages/pip/_vendor/rich/repr.py +venv/Lib/site-packages/pip/_vendor/rich/rule.py +venv/Lib/site-packages/pip/_vendor/rich/scope.py +venv/Lib/site-packages/pip/_vendor/rich/screen.py +venv/Lib/site-packages/pip/_vendor/rich/segment.py +venv/Lib/site-packages/pip/_vendor/rich/spinner.py +venv/Lib/site-packages/pip/_vendor/rich/status.py +venv/Lib/site-packages/pip/_vendor/rich/style.py +venv/Lib/site-packages/pip/_vendor/rich/styled.py +venv/Lib/site-packages/pip/_vendor/rich/syntax.py +venv/Lib/site-packages/pip/_vendor/rich/table.py +venv/Lib/site-packages/pip/_vendor/rich/terminal_theme.py +venv/Lib/site-packages/pip/_vendor/rich/text.py +venv/Lib/site-packages/pip/_vendor/rich/theme.py +venv/Lib/site-packages/pip/_vendor/rich/themes.py +venv/Lib/site-packages/pip/_vendor/rich/traceback.py +venv/Lib/site-packages/pip/_vendor/rich/tree.py +venv/Lib/site-packages/pip/_vendor/tenacity/__init__.py +venv/Lib/site-packages/pip/_vendor/tenacity/_asyncio.py +venv/Lib/site-packages/pip/_vendor/tenacity/_utils.py +venv/Lib/site-packages/pip/_vendor/tenacity/after.py +venv/Lib/site-packages/pip/_vendor/tenacity/before_sleep.py +venv/Lib/site-packages/pip/_vendor/tenacity/before.py +venv/Lib/site-packages/pip/_vendor/tenacity/nap.py +venv/Lib/site-packages/pip/_vendor/tenacity/retry.py +venv/Lib/site-packages/pip/_vendor/tenacity/stop.py +venv/Lib/site-packages/pip/_vendor/tenacity/tornadoweb.py +venv/Lib/site-packages/pip/_vendor/tenacity/wait.py +venv/Lib/site-packages/pip/_vendor/tomli/__init__.py +venv/Lib/site-packages/pip/_vendor/tomli/_parser.py +venv/Lib/site-packages/pip/_vendor/tomli/_re.py +venv/Lib/site-packages/pip/_vendor/tomli/_types.py +venv/Lib/site-packages/pip/_vendor/urllib3/__init__.py +venv/Lib/site-packages/pip/_vendor/urllib3/_collections.py +venv/Lib/site-packages/pip/_vendor/urllib3/_version.py +venv/Lib/site-packages/pip/_vendor/urllib3/connection.py +venv/Lib/site-packages/pip/_vendor/urllib3/connectionpool.py +venv/Lib/site-packages/pip/_vendor/urllib3/exceptions.py +venv/Lib/site-packages/pip/_vendor/urllib3/fields.py +venv/Lib/site-packages/pip/_vendor/urllib3/filepost.py +venv/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py +venv/Lib/site-packages/pip/_vendor/urllib3/request.py +venv/Lib/site-packages/pip/_vendor/urllib3/response.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__init__.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/appengine.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/securetransport.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/socks.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py +venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py +venv/Lib/site-packages/pip/_vendor/urllib3/packages/__init__.py +venv/Lib/site-packages/pip/_vendor/urllib3/packages/six.py +venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py +venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/__init__.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/connection.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/proxy.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/queue.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/request.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/response.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/retry.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/ssltransport.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/timeout.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/url.py +venv/Lib/site-packages/pip/_vendor/urllib3/util/wait.py +venv/Lib/site-packages/pip/_vendor/webencodings/__init__.py +venv/Lib/site-packages/pip/_vendor/webencodings/labels.py +venv/Lib/site-packages/pip/_vendor/webencodings/mklabels.py +venv/Lib/site-packages/pip/_vendor/webencodings/tests.py +venv/Lib/site-packages/pip/_vendor/webencodings/x_user_defined.py +venv/Lib/site-packages/pip-22.3.1.dist-info/entry_points.txt +venv/Lib/site-packages/pip-22.3.1.dist-info/INSTALLER +venv/Lib/site-packages/pip-22.3.1.dist-info/LICENSE.txt +venv/Lib/site-packages/pip-22.3.1.dist-info/METADATA +venv/Lib/site-packages/pip-22.3.1.dist-info/RECORD +venv/Lib/site-packages/pip-22.3.1.dist-info/REQUESTED +venv/Lib/site-packages/pip-22.3.1.dist-info/top_level.txt +venv/Lib/site-packages/pip-22.3.1.dist-info/WHEEL +venv/Lib/site-packages/pkg_resources/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/appdirs.py +venv/Lib/site-packages/pkg_resources/_vendor/zipp.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_adapters.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_common.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_compat.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_itertools.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_legacy.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/abc.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/readers.py +venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/simple.py +venv/Lib/site-packages/pkg_resources/_vendor/jaraco/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/jaraco/context.py +venv/Lib/site-packages/pkg_resources/_vendor/jaraco/functools.py +venv/Lib/site-packages/pkg_resources/_vendor/jaraco/text/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/more.py +venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/recipes.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/__about__.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/_manylinux.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/_musllinux.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/_structures.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/markers.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/requirements.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/specifiers.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/tags.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/utils.py +venv/Lib/site-packages/pkg_resources/_vendor/packaging/version.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/__init__.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/actions.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/common.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/core.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/exceptions.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/helpers.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/results.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/testing.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/unicode.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/util.py +venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/diagram/__init__.py +venv/Lib/site-packages/pkg_resources/extern/__init__.py +venv/Lib/site-packages/setuptools/__init__.py +venv/Lib/site-packages/setuptools/_deprecation_warning.py +venv/Lib/site-packages/setuptools/_entry_points.py +venv/Lib/site-packages/setuptools/_imp.py +venv/Lib/site-packages/setuptools/_importlib.py +venv/Lib/site-packages/setuptools/_itertools.py +venv/Lib/site-packages/setuptools/_path.py +venv/Lib/site-packages/setuptools/_reqs.py +venv/Lib/site-packages/setuptools/archive_util.py +venv/Lib/site-packages/setuptools/build_meta.py +venv/Lib/site-packages/setuptools/cli-32.exe +venv/Lib/site-packages/setuptools/cli-64.exe +venv/Lib/site-packages/setuptools/cli-arm64.exe +venv/Lib/site-packages/setuptools/cli.exe +venv/Lib/site-packages/setuptools/dep_util.py +venv/Lib/site-packages/setuptools/depends.py +venv/Lib/site-packages/setuptools/discovery.py +venv/Lib/site-packages/setuptools/dist.py +venv/Lib/site-packages/setuptools/errors.py +venv/Lib/site-packages/setuptools/extension.py +venv/Lib/site-packages/setuptools/glob.py +venv/Lib/site-packages/setuptools/gui-32.exe +venv/Lib/site-packages/setuptools/gui-64.exe +venv/Lib/site-packages/setuptools/gui-arm64.exe +venv/Lib/site-packages/setuptools/gui.exe +venv/Lib/site-packages/setuptools/installer.py +venv/Lib/site-packages/setuptools/launch.py +venv/Lib/site-packages/setuptools/logging.py +venv/Lib/site-packages/setuptools/monkey.py +venv/Lib/site-packages/setuptools/msvc.py +venv/Lib/site-packages/setuptools/namespaces.py +venv/Lib/site-packages/setuptools/package_index.py +venv/Lib/site-packages/setuptools/py34compat.py +venv/Lib/site-packages/setuptools/sandbox.py +venv/Lib/site-packages/setuptools/script (dev).tmpl +venv/Lib/site-packages/setuptools/script.tmpl +venv/Lib/site-packages/setuptools/unicode_utils.py +venv/Lib/site-packages/setuptools/version.py +venv/Lib/site-packages/setuptools/wheel.py +venv/Lib/site-packages/setuptools/windows_support.py +venv/Lib/site-packages/setuptools/_distutils/__init__.py +venv/Lib/site-packages/setuptools/_distutils/_collections.py +venv/Lib/site-packages/setuptools/_distutils/_functools.py +venv/Lib/site-packages/setuptools/_distutils/_macos_compat.py +venv/Lib/site-packages/setuptools/_distutils/_msvccompiler.py +venv/Lib/site-packages/setuptools/_distutils/archive_util.py +venv/Lib/site-packages/setuptools/_distutils/bcppcompiler.py +venv/Lib/site-packages/setuptools/_distutils/ccompiler.py +venv/Lib/site-packages/setuptools/_distutils/cmd.py +venv/Lib/site-packages/setuptools/_distutils/config.py +venv/Lib/site-packages/setuptools/_distutils/core.py +venv/Lib/site-packages/setuptools/_distutils/cygwinccompiler.py +venv/Lib/site-packages/setuptools/_distutils/debug.py +venv/Lib/site-packages/setuptools/_distutils/dep_util.py +venv/Lib/site-packages/setuptools/_distutils/dir_util.py +venv/Lib/site-packages/setuptools/_distutils/dist.py +venv/Lib/site-packages/setuptools/_distutils/errors.py +venv/Lib/site-packages/setuptools/_distutils/extension.py +venv/Lib/site-packages/setuptools/_distutils/fancy_getopt.py +venv/Lib/site-packages/setuptools/_distutils/file_util.py +venv/Lib/site-packages/setuptools/_distutils/filelist.py +venv/Lib/site-packages/setuptools/_distutils/log.py +venv/Lib/site-packages/setuptools/_distutils/msvc9compiler.py +venv/Lib/site-packages/setuptools/_distutils/msvccompiler.py +venv/Lib/site-packages/setuptools/_distutils/py38compat.py +venv/Lib/site-packages/setuptools/_distutils/py39compat.py +venv/Lib/site-packages/setuptools/_distutils/spawn.py +venv/Lib/site-packages/setuptools/_distutils/sysconfig.py +venv/Lib/site-packages/setuptools/_distutils/text_file.py +venv/Lib/site-packages/setuptools/_distutils/unixccompiler.py +venv/Lib/site-packages/setuptools/_distutils/util.py +venv/Lib/site-packages/setuptools/_distutils/version.py +venv/Lib/site-packages/setuptools/_distutils/versionpredicate.py +venv/Lib/site-packages/setuptools/_distutils/command/__init__.py +venv/Lib/site-packages/setuptools/_distutils/command/_framework_compat.py +venv/Lib/site-packages/setuptools/_distutils/command/bdist_dumb.py +venv/Lib/site-packages/setuptools/_distutils/command/bdist_rpm.py +venv/Lib/site-packages/setuptools/_distutils/command/bdist.py +venv/Lib/site-packages/setuptools/_distutils/command/build_clib.py +venv/Lib/site-packages/setuptools/_distutils/command/build_ext.py +venv/Lib/site-packages/setuptools/_distutils/command/build_py.py +venv/Lib/site-packages/setuptools/_distutils/command/build_scripts.py +venv/Lib/site-packages/setuptools/_distutils/command/build.py +venv/Lib/site-packages/setuptools/_distutils/command/check.py +venv/Lib/site-packages/setuptools/_distutils/command/clean.py +venv/Lib/site-packages/setuptools/_distutils/command/config.py +venv/Lib/site-packages/setuptools/_distutils/command/install_data.py +venv/Lib/site-packages/setuptools/_distutils/command/install_egg_info.py +venv/Lib/site-packages/setuptools/_distutils/command/install_headers.py +venv/Lib/site-packages/setuptools/_distutils/command/install_lib.py +venv/Lib/site-packages/setuptools/_distutils/command/install_scripts.py +venv/Lib/site-packages/setuptools/_distutils/command/install.py +venv/Lib/site-packages/setuptools/_distutils/command/py37compat.py +venv/Lib/site-packages/setuptools/_distutils/command/register.py +venv/Lib/site-packages/setuptools/_distutils/command/sdist.py +venv/Lib/site-packages/setuptools/_distutils/command/upload.py +venv/Lib/site-packages/setuptools/_vendor/__init__.py +venv/Lib/site-packages/setuptools/_vendor/ordered_set.py +venv/Lib/site-packages/setuptools/_vendor/typing_extensions.py +venv/Lib/site-packages/setuptools/_vendor/zipp.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/__init__.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_adapters.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_collections.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_compat.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_functools.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_itertools.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_meta.py +venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_text.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/__init__.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_adapters.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_common.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_compat.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_itertools.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_legacy.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/abc.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/readers.py +venv/Lib/site-packages/setuptools/_vendor/importlib_resources/simple.py +venv/Lib/site-packages/setuptools/_vendor/jaraco/__init__.py +venv/Lib/site-packages/setuptools/_vendor/jaraco/context.py +venv/Lib/site-packages/setuptools/_vendor/jaraco/functools.py +venv/Lib/site-packages/setuptools/_vendor/jaraco/text/__init__.py +venv/Lib/site-packages/setuptools/_vendor/more_itertools/__init__.py +venv/Lib/site-packages/setuptools/_vendor/more_itertools/more.py +venv/Lib/site-packages/setuptools/_vendor/more_itertools/recipes.py +venv/Lib/site-packages/setuptools/_vendor/packaging/__about__.py +venv/Lib/site-packages/setuptools/_vendor/packaging/__init__.py +venv/Lib/site-packages/setuptools/_vendor/packaging/_manylinux.py +venv/Lib/site-packages/setuptools/_vendor/packaging/_musllinux.py +venv/Lib/site-packages/setuptools/_vendor/packaging/_structures.py +venv/Lib/site-packages/setuptools/_vendor/packaging/markers.py +venv/Lib/site-packages/setuptools/_vendor/packaging/requirements.py +venv/Lib/site-packages/setuptools/_vendor/packaging/specifiers.py +venv/Lib/site-packages/setuptools/_vendor/packaging/tags.py +venv/Lib/site-packages/setuptools/_vendor/packaging/utils.py +venv/Lib/site-packages/setuptools/_vendor/packaging/version.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/__init__.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/actions.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/common.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/core.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/exceptions.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/helpers.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/results.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/testing.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/unicode.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/util.py +venv/Lib/site-packages/setuptools/_vendor/pyparsing/diagram/__init__.py +venv/Lib/site-packages/setuptools/_vendor/tomli/__init__.py +venv/Lib/site-packages/setuptools/_vendor/tomli/_parser.py +venv/Lib/site-packages/setuptools/_vendor/tomli/_re.py +venv/Lib/site-packages/setuptools/_vendor/tomli/_types.py +venv/Lib/site-packages/setuptools/command/__init__.py +venv/Lib/site-packages/setuptools/command/alias.py +venv/Lib/site-packages/setuptools/command/bdist_egg.py +venv/Lib/site-packages/setuptools/command/bdist_rpm.py +venv/Lib/site-packages/setuptools/command/build_clib.py +venv/Lib/site-packages/setuptools/command/build_ext.py +venv/Lib/site-packages/setuptools/command/build_py.py +venv/Lib/site-packages/setuptools/command/build.py +venv/Lib/site-packages/setuptools/command/develop.py +venv/Lib/site-packages/setuptools/command/dist_info.py +venv/Lib/site-packages/setuptools/command/easy_install.py +venv/Lib/site-packages/setuptools/command/editable_wheel.py +venv/Lib/site-packages/setuptools/command/egg_info.py +venv/Lib/site-packages/setuptools/command/install_egg_info.py +venv/Lib/site-packages/setuptools/command/install_lib.py +venv/Lib/site-packages/setuptools/command/install_scripts.py +venv/Lib/site-packages/setuptools/command/install.py +venv/Lib/site-packages/setuptools/command/launcher manifest.xml +venv/Lib/site-packages/setuptools/command/py36compat.py +venv/Lib/site-packages/setuptools/command/register.py +venv/Lib/site-packages/setuptools/command/rotate.py +venv/Lib/site-packages/setuptools/command/saveopts.py +venv/Lib/site-packages/setuptools/command/sdist.py +venv/Lib/site-packages/setuptools/command/setopt.py +venv/Lib/site-packages/setuptools/command/test.py +venv/Lib/site-packages/setuptools/command/upload_docs.py +venv/Lib/site-packages/setuptools/command/upload.py +venv/Lib/site-packages/setuptools/config/__init__.py +venv/Lib/site-packages/setuptools/config/_apply_pyprojecttoml.py +venv/Lib/site-packages/setuptools/config/expand.py +venv/Lib/site-packages/setuptools/config/pyprojecttoml.py +venv/Lib/site-packages/setuptools/config/setupcfg.py +venv/Lib/site-packages/setuptools/config/_validate_pyproject/__init__.py +venv/Lib/site-packages/setuptools/config/_validate_pyproject/error_reporting.py +venv/Lib/site-packages/setuptools/config/_validate_pyproject/extra_validations.py +venv/Lib/site-packages/setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py +venv/Lib/site-packages/setuptools/config/_validate_pyproject/fastjsonschema_validations.py +venv/Lib/site-packages/setuptools/config/_validate_pyproject/formats.py +venv/Lib/site-packages/setuptools/extern/__init__.py +venv/Lib/site-packages/setuptools-65.5.0.dist-info/entry_points.txt +venv/Lib/site-packages/setuptools-65.5.0.dist-info/INSTALLER +venv/Lib/site-packages/setuptools-65.5.0.dist-info/LICENSE +venv/Lib/site-packages/setuptools-65.5.0.dist-info/METADATA +venv/Lib/site-packages/setuptools-65.5.0.dist-info/RECORD +venv/Lib/site-packages/setuptools-65.5.0.dist-info/REQUESTED +venv/Lib/site-packages/setuptools-65.5.0.dist-info/top_level.txt +venv/Lib/site-packages/setuptools-65.5.0.dist-info/WHEEL +venv/Scripts/activate +venv/Scripts/activate.bat +venv/Scripts/Activate.ps1 +venv/Scripts/deactivate.bat +venv/Scripts/pip.exe +venv/Scripts/pip3.11.exe +venv/Scripts/pip3.exe +venv/Scripts/python.exe +venv/Scripts/pythonw.exe From 948ae43f5beacddc847954977b030d154da6bf3e Mon Sep 17 00:00:00 2001 From: amochin Date: Sun, 4 Jun 2023 11:30:30 +0200 Subject: [PATCH 008/266] ignore entire venv folder --- .gitignore | 749 +---------------------------------------------------- 1 file changed, 1 insertion(+), 748 deletions(-) diff --git a/.gitignore b/.gitignore index f4f0807..5d9f69a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,751 +8,4 @@ test/my_db_test.db test/output.xml test/report.html test/logs/ -venv/pyvenv.cfg -venv/Lib/site-packages/distutils-precedence.pth -venv/Lib/site-packages/_distutils_hack/__init__.py -venv/Lib/site-packages/_distutils_hack/override.py -venv/Lib/site-packages/pip/__init__.py -venv/Lib/site-packages/pip/__main__.py -venv/Lib/site-packages/pip/__pip-runner__.py -venv/Lib/site-packages/pip/py.typed -venv/Lib/site-packages/pip/_internal/__init__.py -venv/Lib/site-packages/pip/_internal/build_env.py -venv/Lib/site-packages/pip/_internal/cache.py -venv/Lib/site-packages/pip/_internal/configuration.py -venv/Lib/site-packages/pip/_internal/exceptions.py -venv/Lib/site-packages/pip/_internal/main.py -venv/Lib/site-packages/pip/_internal/pyproject.py -venv/Lib/site-packages/pip/_internal/self_outdated_check.py -venv/Lib/site-packages/pip/_internal/wheel_builder.py -venv/Lib/site-packages/pip/_internal/cli/__init__.py -venv/Lib/site-packages/pip/_internal/cli/autocompletion.py -venv/Lib/site-packages/pip/_internal/cli/base_command.py -venv/Lib/site-packages/pip/_internal/cli/cmdoptions.py -venv/Lib/site-packages/pip/_internal/cli/command_context.py -venv/Lib/site-packages/pip/_internal/cli/main_parser.py -venv/Lib/site-packages/pip/_internal/cli/main.py -venv/Lib/site-packages/pip/_internal/cli/parser.py -venv/Lib/site-packages/pip/_internal/cli/progress_bars.py -venv/Lib/site-packages/pip/_internal/cli/req_command.py -venv/Lib/site-packages/pip/_internal/cli/spinners.py -venv/Lib/site-packages/pip/_internal/cli/status_codes.py -venv/Lib/site-packages/pip/_internal/commands/__init__.py -venv/Lib/site-packages/pip/_internal/commands/cache.py -venv/Lib/site-packages/pip/_internal/commands/check.py -venv/Lib/site-packages/pip/_internal/commands/completion.py -venv/Lib/site-packages/pip/_internal/commands/configuration.py -venv/Lib/site-packages/pip/_internal/commands/debug.py -venv/Lib/site-packages/pip/_internal/commands/download.py -venv/Lib/site-packages/pip/_internal/commands/freeze.py -venv/Lib/site-packages/pip/_internal/commands/hash.py -venv/Lib/site-packages/pip/_internal/commands/help.py -venv/Lib/site-packages/pip/_internal/commands/index.py -venv/Lib/site-packages/pip/_internal/commands/inspect.py -venv/Lib/site-packages/pip/_internal/commands/install.py -venv/Lib/site-packages/pip/_internal/commands/list.py -venv/Lib/site-packages/pip/_internal/commands/search.py -venv/Lib/site-packages/pip/_internal/commands/show.py -venv/Lib/site-packages/pip/_internal/commands/uninstall.py -venv/Lib/site-packages/pip/_internal/commands/wheel.py -venv/Lib/site-packages/pip/_internal/distributions/__init__.py -venv/Lib/site-packages/pip/_internal/distributions/base.py -venv/Lib/site-packages/pip/_internal/distributions/installed.py -venv/Lib/site-packages/pip/_internal/distributions/sdist.py -venv/Lib/site-packages/pip/_internal/distributions/wheel.py -venv/Lib/site-packages/pip/_internal/index/__init__.py -venv/Lib/site-packages/pip/_internal/index/collector.py -venv/Lib/site-packages/pip/_internal/index/package_finder.py -venv/Lib/site-packages/pip/_internal/index/sources.py -venv/Lib/site-packages/pip/_internal/locations/__init__.py -venv/Lib/site-packages/pip/_internal/locations/_distutils.py -venv/Lib/site-packages/pip/_internal/locations/_sysconfig.py -venv/Lib/site-packages/pip/_internal/locations/base.py -venv/Lib/site-packages/pip/_internal/metadata/__init__.py -venv/Lib/site-packages/pip/_internal/metadata/_json.py -venv/Lib/site-packages/pip/_internal/metadata/base.py -venv/Lib/site-packages/pip/_internal/metadata/pkg_resources.py -venv/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py -venv/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py -venv/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py -venv/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py -venv/Lib/site-packages/pip/_internal/models/__init__.py -venv/Lib/site-packages/pip/_internal/models/candidate.py -venv/Lib/site-packages/pip/_internal/models/direct_url.py -venv/Lib/site-packages/pip/_internal/models/format_control.py -venv/Lib/site-packages/pip/_internal/models/index.py -venv/Lib/site-packages/pip/_internal/models/installation_report.py -venv/Lib/site-packages/pip/_internal/models/link.py -venv/Lib/site-packages/pip/_internal/models/scheme.py -venv/Lib/site-packages/pip/_internal/models/search_scope.py -venv/Lib/site-packages/pip/_internal/models/selection_prefs.py -venv/Lib/site-packages/pip/_internal/models/target_python.py -venv/Lib/site-packages/pip/_internal/models/wheel.py -venv/Lib/site-packages/pip/_internal/network/__init__.py -venv/Lib/site-packages/pip/_internal/network/auth.py -venv/Lib/site-packages/pip/_internal/network/cache.py -venv/Lib/site-packages/pip/_internal/network/download.py -venv/Lib/site-packages/pip/_internal/network/lazy_wheel.py -venv/Lib/site-packages/pip/_internal/network/session.py -venv/Lib/site-packages/pip/_internal/network/utils.py -venv/Lib/site-packages/pip/_internal/network/xmlrpc.py -venv/Lib/site-packages/pip/_internal/operations/__init__.py -venv/Lib/site-packages/pip/_internal/operations/check.py -venv/Lib/site-packages/pip/_internal/operations/freeze.py -venv/Lib/site-packages/pip/_internal/operations/prepare.py -venv/Lib/site-packages/pip/_internal/operations/install/__init__.py -venv/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py -venv/Lib/site-packages/pip/_internal/operations/install/legacy.py -venv/Lib/site-packages/pip/_internal/operations/install/wheel.py -venv/Lib/site-packages/pip/_internal/req/__init__.py -venv/Lib/site-packages/pip/_internal/req/constructors.py -venv/Lib/site-packages/pip/_internal/req/req_file.py -venv/Lib/site-packages/pip/_internal/req/req_install.py -venv/Lib/site-packages/pip/_internal/req/req_set.py -venv/Lib/site-packages/pip/_internal/req/req_uninstall.py -venv/Lib/site-packages/pip/_internal/resolution/__init__.py -venv/Lib/site-packages/pip/_internal/resolution/base.py -venv/Lib/site-packages/pip/_internal/resolution/legacy/__init__.py -venv/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__init__.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/base.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/candidates.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/factory.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/provider.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/reporter.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/requirements.py -venv/Lib/site-packages/pip/_internal/resolution/resolvelib/resolver.py -venv/Lib/site-packages/pip/_internal/utils/__init__.py -venv/Lib/site-packages/pip/_internal/utils/_log.py -venv/Lib/site-packages/pip/_internal/utils/appdirs.py -venv/Lib/site-packages/pip/_internal/utils/compat.py -venv/Lib/site-packages/pip/_internal/utils/compatibility_tags.py -venv/Lib/site-packages/pip/_internal/utils/datetime.py -venv/Lib/site-packages/pip/_internal/utils/deprecation.py -venv/Lib/site-packages/pip/_internal/utils/direct_url_helpers.py -venv/Lib/site-packages/pip/_internal/utils/distutils_args.py -venv/Lib/site-packages/pip/_internal/utils/egg_link.py -venv/Lib/site-packages/pip/_internal/utils/encoding.py -venv/Lib/site-packages/pip/_internal/utils/entrypoints.py -venv/Lib/site-packages/pip/_internal/utils/filesystem.py -venv/Lib/site-packages/pip/_internal/utils/filetypes.py -venv/Lib/site-packages/pip/_internal/utils/glibc.py -venv/Lib/site-packages/pip/_internal/utils/hashes.py -venv/Lib/site-packages/pip/_internal/utils/inject_securetransport.py -venv/Lib/site-packages/pip/_internal/utils/logging.py -venv/Lib/site-packages/pip/_internal/utils/misc.py -venv/Lib/site-packages/pip/_internal/utils/models.py -venv/Lib/site-packages/pip/_internal/utils/packaging.py -venv/Lib/site-packages/pip/_internal/utils/setuptools_build.py -venv/Lib/site-packages/pip/_internal/utils/subprocess.py -venv/Lib/site-packages/pip/_internal/utils/temp_dir.py -venv/Lib/site-packages/pip/_internal/utils/unpacking.py -venv/Lib/site-packages/pip/_internal/utils/urls.py -venv/Lib/site-packages/pip/_internal/utils/virtualenv.py -venv/Lib/site-packages/pip/_internal/utils/wheel.py -venv/Lib/site-packages/pip/_internal/vcs/__init__.py -venv/Lib/site-packages/pip/_internal/vcs/bazaar.py -venv/Lib/site-packages/pip/_internal/vcs/git.py -venv/Lib/site-packages/pip/_internal/vcs/mercurial.py -venv/Lib/site-packages/pip/_internal/vcs/subversion.py -venv/Lib/site-packages/pip/_internal/vcs/versioncontrol.py -venv/Lib/site-packages/pip/_vendor/__init__.py -venv/Lib/site-packages/pip/_vendor/six.py -venv/Lib/site-packages/pip/_vendor/typing_extensions.py -venv/Lib/site-packages/pip/_vendor/vendor.txt -venv/Lib/site-packages/pip/_vendor/cachecontrol/__init__.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/_cmd.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/adapter.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/cache.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/compat.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/controller.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/filewrapper.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/heuristics.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/serialize.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/wrapper.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__init__.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py -venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py -venv/Lib/site-packages/pip/_vendor/certifi/__init__.py -venv/Lib/site-packages/pip/_vendor/certifi/__main__.py -venv/Lib/site-packages/pip/_vendor/certifi/cacert.pem -venv/Lib/site-packages/pip/_vendor/certifi/core.py -venv/Lib/site-packages/pip/_vendor/chardet/__init__.py -venv/Lib/site-packages/pip/_vendor/chardet/big5freq.py -venv/Lib/site-packages/pip/_vendor/chardet/big5prober.py -venv/Lib/site-packages/pip/_vendor/chardet/chardistribution.py -venv/Lib/site-packages/pip/_vendor/chardet/charsetgroupprober.py -venv/Lib/site-packages/pip/_vendor/chardet/charsetprober.py -venv/Lib/site-packages/pip/_vendor/chardet/codingstatemachine.py -venv/Lib/site-packages/pip/_vendor/chardet/cp949prober.py -venv/Lib/site-packages/pip/_vendor/chardet/enums.py -venv/Lib/site-packages/pip/_vendor/chardet/escprober.py -venv/Lib/site-packages/pip/_vendor/chardet/escsm.py -venv/Lib/site-packages/pip/_vendor/chardet/eucjpprober.py -venv/Lib/site-packages/pip/_vendor/chardet/euckrfreq.py -venv/Lib/site-packages/pip/_vendor/chardet/euckrprober.py -venv/Lib/site-packages/pip/_vendor/chardet/euctwfreq.py -venv/Lib/site-packages/pip/_vendor/chardet/euctwprober.py -venv/Lib/site-packages/pip/_vendor/chardet/gb2312freq.py -venv/Lib/site-packages/pip/_vendor/chardet/gb2312prober.py -venv/Lib/site-packages/pip/_vendor/chardet/hebrewprober.py -venv/Lib/site-packages/pip/_vendor/chardet/jisfreq.py -venv/Lib/site-packages/pip/_vendor/chardet/johabfreq.py -venv/Lib/site-packages/pip/_vendor/chardet/johabprober.py -venv/Lib/site-packages/pip/_vendor/chardet/jpcntx.py -venv/Lib/site-packages/pip/_vendor/chardet/langbulgarianmodel.py -venv/Lib/site-packages/pip/_vendor/chardet/langgreekmodel.py -venv/Lib/site-packages/pip/_vendor/chardet/langhebrewmodel.py -venv/Lib/site-packages/pip/_vendor/chardet/langhungarianmodel.py -venv/Lib/site-packages/pip/_vendor/chardet/langrussianmodel.py -venv/Lib/site-packages/pip/_vendor/chardet/langthaimodel.py -venv/Lib/site-packages/pip/_vendor/chardet/langturkishmodel.py -venv/Lib/site-packages/pip/_vendor/chardet/latin1prober.py -venv/Lib/site-packages/pip/_vendor/chardet/mbcharsetprober.py -venv/Lib/site-packages/pip/_vendor/chardet/mbcsgroupprober.py -venv/Lib/site-packages/pip/_vendor/chardet/mbcssm.py -venv/Lib/site-packages/pip/_vendor/chardet/sbcharsetprober.py -venv/Lib/site-packages/pip/_vendor/chardet/sbcsgroupprober.py -venv/Lib/site-packages/pip/_vendor/chardet/sjisprober.py -venv/Lib/site-packages/pip/_vendor/chardet/universaldetector.py -venv/Lib/site-packages/pip/_vendor/chardet/utf8prober.py -venv/Lib/site-packages/pip/_vendor/chardet/utf1632prober.py -venv/Lib/site-packages/pip/_vendor/chardet/version.py -venv/Lib/site-packages/pip/_vendor/chardet/cli/__init__.py -venv/Lib/site-packages/pip/_vendor/chardet/cli/chardetect.py -venv/Lib/site-packages/pip/_vendor/chardet/metadata/__init__.py -venv/Lib/site-packages/pip/_vendor/chardet/metadata/languages.py -venv/Lib/site-packages/pip/_vendor/colorama/__init__.py -venv/Lib/site-packages/pip/_vendor/colorama/ansi.py -venv/Lib/site-packages/pip/_vendor/colorama/ansitowin32.py -venv/Lib/site-packages/pip/_vendor/colorama/initialise.py -venv/Lib/site-packages/pip/_vendor/colorama/win32.py -venv/Lib/site-packages/pip/_vendor/colorama/winterm.py -venv/Lib/site-packages/pip/_vendor/distlib/__init__.py -venv/Lib/site-packages/pip/_vendor/distlib/compat.py -venv/Lib/site-packages/pip/_vendor/distlib/database.py -venv/Lib/site-packages/pip/_vendor/distlib/index.py -venv/Lib/site-packages/pip/_vendor/distlib/locators.py -venv/Lib/site-packages/pip/_vendor/distlib/manifest.py -venv/Lib/site-packages/pip/_vendor/distlib/markers.py -venv/Lib/site-packages/pip/_vendor/distlib/metadata.py -venv/Lib/site-packages/pip/_vendor/distlib/resources.py -venv/Lib/site-packages/pip/_vendor/distlib/scripts.py -venv/Lib/site-packages/pip/_vendor/distlib/t32.exe -venv/Lib/site-packages/pip/_vendor/distlib/t64-arm.exe -venv/Lib/site-packages/pip/_vendor/distlib/t64.exe -venv/Lib/site-packages/pip/_vendor/distlib/util.py -venv/Lib/site-packages/pip/_vendor/distlib/version.py -venv/Lib/site-packages/pip/_vendor/distlib/w32.exe -venv/Lib/site-packages/pip/_vendor/distlib/w64-arm.exe -venv/Lib/site-packages/pip/_vendor/distlib/w64.exe -venv/Lib/site-packages/pip/_vendor/distlib/wheel.py -venv/Lib/site-packages/pip/_vendor/distro/__init__.py -venv/Lib/site-packages/pip/_vendor/distro/__main__.py -venv/Lib/site-packages/pip/_vendor/distro/distro.py -venv/Lib/site-packages/pip/_vendor/idna/__init__.py -venv/Lib/site-packages/pip/_vendor/idna/codec.py -venv/Lib/site-packages/pip/_vendor/idna/compat.py -venv/Lib/site-packages/pip/_vendor/idna/core.py -venv/Lib/site-packages/pip/_vendor/idna/idnadata.py -venv/Lib/site-packages/pip/_vendor/idna/intranges.py -venv/Lib/site-packages/pip/_vendor/idna/package_data.py -venv/Lib/site-packages/pip/_vendor/idna/uts46data.py -venv/Lib/site-packages/pip/_vendor/msgpack/__init__.py -venv/Lib/site-packages/pip/_vendor/msgpack/exceptions.py -venv/Lib/site-packages/pip/_vendor/msgpack/ext.py -venv/Lib/site-packages/pip/_vendor/msgpack/fallback.py -venv/Lib/site-packages/pip/_vendor/packaging/__about__.py -venv/Lib/site-packages/pip/_vendor/packaging/__init__.py -venv/Lib/site-packages/pip/_vendor/packaging/_manylinux.py -venv/Lib/site-packages/pip/_vendor/packaging/_musllinux.py -venv/Lib/site-packages/pip/_vendor/packaging/_structures.py -venv/Lib/site-packages/pip/_vendor/packaging/markers.py -venv/Lib/site-packages/pip/_vendor/packaging/requirements.py -venv/Lib/site-packages/pip/_vendor/packaging/specifiers.py -venv/Lib/site-packages/pip/_vendor/packaging/tags.py -venv/Lib/site-packages/pip/_vendor/packaging/utils.py -venv/Lib/site-packages/pip/_vendor/packaging/version.py -venv/Lib/site-packages/pip/_vendor/pep517/__init__.py -venv/Lib/site-packages/pip/_vendor/pep517/_compat.py -venv/Lib/site-packages/pip/_vendor/pep517/build.py -venv/Lib/site-packages/pip/_vendor/pep517/check.py -venv/Lib/site-packages/pip/_vendor/pep517/colorlog.py -venv/Lib/site-packages/pip/_vendor/pep517/dirtools.py -venv/Lib/site-packages/pip/_vendor/pep517/envbuild.py -venv/Lib/site-packages/pip/_vendor/pep517/meta.py -venv/Lib/site-packages/pip/_vendor/pep517/wrappers.py -venv/Lib/site-packages/pip/_vendor/pep517/in_process/__init__.py -venv/Lib/site-packages/pip/_vendor/pep517/in_process/_in_process.py -venv/Lib/site-packages/pip/_vendor/pkg_resources/__init__.py -venv/Lib/site-packages/pip/_vendor/pkg_resources/py31compat.py -venv/Lib/site-packages/pip/_vendor/platformdirs/__init__.py -venv/Lib/site-packages/pip/_vendor/platformdirs/__main__.py -venv/Lib/site-packages/pip/_vendor/platformdirs/android.py -venv/Lib/site-packages/pip/_vendor/platformdirs/api.py -venv/Lib/site-packages/pip/_vendor/platformdirs/macos.py -venv/Lib/site-packages/pip/_vendor/platformdirs/unix.py -venv/Lib/site-packages/pip/_vendor/platformdirs/version.py -venv/Lib/site-packages/pip/_vendor/platformdirs/windows.py -venv/Lib/site-packages/pip/_vendor/pygments/__init__.py -venv/Lib/site-packages/pip/_vendor/pygments/__main__.py -venv/Lib/site-packages/pip/_vendor/pygments/cmdline.py -venv/Lib/site-packages/pip/_vendor/pygments/console.py -venv/Lib/site-packages/pip/_vendor/pygments/filter.py -venv/Lib/site-packages/pip/_vendor/pygments/formatter.py -venv/Lib/site-packages/pip/_vendor/pygments/lexer.py -venv/Lib/site-packages/pip/_vendor/pygments/modeline.py -venv/Lib/site-packages/pip/_vendor/pygments/plugin.py -venv/Lib/site-packages/pip/_vendor/pygments/regexopt.py -venv/Lib/site-packages/pip/_vendor/pygments/scanner.py -venv/Lib/site-packages/pip/_vendor/pygments/sphinxext.py -venv/Lib/site-packages/pip/_vendor/pygments/style.py -venv/Lib/site-packages/pip/_vendor/pygments/token.py -venv/Lib/site-packages/pip/_vendor/pygments/unistring.py -venv/Lib/site-packages/pip/_vendor/pygments/util.py -venv/Lib/site-packages/pip/_vendor/pygments/filters/__init__.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/__init__.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/_mapping.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/bbcode.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/groff.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/html.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/img.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/irc.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/latex.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/other.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/rtf.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/svg.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal.py -venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal256.py -venv/Lib/site-packages/pip/_vendor/pygments/lexers/__init__.py -venv/Lib/site-packages/pip/_vendor/pygments/lexers/_mapping.py -venv/Lib/site-packages/pip/_vendor/pygments/lexers/python.py -venv/Lib/site-packages/pip/_vendor/pygments/styles/__init__.py -venv/Lib/site-packages/pip/_vendor/pyparsing/__init__.py -venv/Lib/site-packages/pip/_vendor/pyparsing/actions.py -venv/Lib/site-packages/pip/_vendor/pyparsing/common.py -venv/Lib/site-packages/pip/_vendor/pyparsing/core.py -venv/Lib/site-packages/pip/_vendor/pyparsing/exceptions.py -venv/Lib/site-packages/pip/_vendor/pyparsing/helpers.py -venv/Lib/site-packages/pip/_vendor/pyparsing/results.py -venv/Lib/site-packages/pip/_vendor/pyparsing/testing.py -venv/Lib/site-packages/pip/_vendor/pyparsing/unicode.py -venv/Lib/site-packages/pip/_vendor/pyparsing/util.py -venv/Lib/site-packages/pip/_vendor/pyparsing/diagram/__init__.py -venv/Lib/site-packages/pip/_vendor/requests/__init__.py -venv/Lib/site-packages/pip/_vendor/requests/__version__.py -venv/Lib/site-packages/pip/_vendor/requests/_internal_utils.py -venv/Lib/site-packages/pip/_vendor/requests/adapters.py -venv/Lib/site-packages/pip/_vendor/requests/api.py -venv/Lib/site-packages/pip/_vendor/requests/auth.py -venv/Lib/site-packages/pip/_vendor/requests/certs.py -venv/Lib/site-packages/pip/_vendor/requests/compat.py -venv/Lib/site-packages/pip/_vendor/requests/cookies.py -venv/Lib/site-packages/pip/_vendor/requests/exceptions.py -venv/Lib/site-packages/pip/_vendor/requests/help.py -venv/Lib/site-packages/pip/_vendor/requests/hooks.py -venv/Lib/site-packages/pip/_vendor/requests/models.py -venv/Lib/site-packages/pip/_vendor/requests/packages.py -venv/Lib/site-packages/pip/_vendor/requests/sessions.py -venv/Lib/site-packages/pip/_vendor/requests/status_codes.py -venv/Lib/site-packages/pip/_vendor/requests/structures.py -venv/Lib/site-packages/pip/_vendor/requests/utils.py -venv/Lib/site-packages/pip/_vendor/resolvelib/__init__.py -venv/Lib/site-packages/pip/_vendor/resolvelib/providers.py -venv/Lib/site-packages/pip/_vendor/resolvelib/reporters.py -venv/Lib/site-packages/pip/_vendor/resolvelib/resolvers.py -venv/Lib/site-packages/pip/_vendor/resolvelib/structs.py -venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__init__.py -venv/Lib/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py -venv/Lib/site-packages/pip/_vendor/rich/__init__.py -venv/Lib/site-packages/pip/_vendor/rich/__main__.py -venv/Lib/site-packages/pip/_vendor/rich/_cell_widths.py -venv/Lib/site-packages/pip/_vendor/rich/_emoji_codes.py -venv/Lib/site-packages/pip/_vendor/rich/_emoji_replace.py -venv/Lib/site-packages/pip/_vendor/rich/_export_format.py -venv/Lib/site-packages/pip/_vendor/rich/_extension.py -venv/Lib/site-packages/pip/_vendor/rich/_inspect.py -venv/Lib/site-packages/pip/_vendor/rich/_log_render.py -venv/Lib/site-packages/pip/_vendor/rich/_loop.py -venv/Lib/site-packages/pip/_vendor/rich/_palettes.py -venv/Lib/site-packages/pip/_vendor/rich/_pick.py -venv/Lib/site-packages/pip/_vendor/rich/_ratio.py -venv/Lib/site-packages/pip/_vendor/rich/_spinners.py -venv/Lib/site-packages/pip/_vendor/rich/_stack.py -venv/Lib/site-packages/pip/_vendor/rich/_timer.py -venv/Lib/site-packages/pip/_vendor/rich/_win32_console.py -venv/Lib/site-packages/pip/_vendor/rich/_windows_renderer.py -venv/Lib/site-packages/pip/_vendor/rich/_windows.py -venv/Lib/site-packages/pip/_vendor/rich/_wrap.py -venv/Lib/site-packages/pip/_vendor/rich/abc.py -venv/Lib/site-packages/pip/_vendor/rich/align.py -venv/Lib/site-packages/pip/_vendor/rich/ansi.py -venv/Lib/site-packages/pip/_vendor/rich/bar.py -venv/Lib/site-packages/pip/_vendor/rich/box.py -venv/Lib/site-packages/pip/_vendor/rich/cells.py -venv/Lib/site-packages/pip/_vendor/rich/color_triplet.py -venv/Lib/site-packages/pip/_vendor/rich/color.py -venv/Lib/site-packages/pip/_vendor/rich/columns.py -venv/Lib/site-packages/pip/_vendor/rich/console.py -venv/Lib/site-packages/pip/_vendor/rich/constrain.py -venv/Lib/site-packages/pip/_vendor/rich/containers.py -venv/Lib/site-packages/pip/_vendor/rich/control.py -venv/Lib/site-packages/pip/_vendor/rich/default_styles.py -venv/Lib/site-packages/pip/_vendor/rich/diagnose.py -venv/Lib/site-packages/pip/_vendor/rich/emoji.py -venv/Lib/site-packages/pip/_vendor/rich/errors.py -venv/Lib/site-packages/pip/_vendor/rich/file_proxy.py -venv/Lib/site-packages/pip/_vendor/rich/filesize.py -venv/Lib/site-packages/pip/_vendor/rich/highlighter.py -venv/Lib/site-packages/pip/_vendor/rich/json.py -venv/Lib/site-packages/pip/_vendor/rich/jupyter.py -venv/Lib/site-packages/pip/_vendor/rich/layout.py -venv/Lib/site-packages/pip/_vendor/rich/live_render.py -venv/Lib/site-packages/pip/_vendor/rich/live.py -venv/Lib/site-packages/pip/_vendor/rich/logging.py -venv/Lib/site-packages/pip/_vendor/rich/markup.py -venv/Lib/site-packages/pip/_vendor/rich/measure.py -venv/Lib/site-packages/pip/_vendor/rich/padding.py -venv/Lib/site-packages/pip/_vendor/rich/pager.py -venv/Lib/site-packages/pip/_vendor/rich/palette.py -venv/Lib/site-packages/pip/_vendor/rich/panel.py -venv/Lib/site-packages/pip/_vendor/rich/pretty.py -venv/Lib/site-packages/pip/_vendor/rich/progress_bar.py -venv/Lib/site-packages/pip/_vendor/rich/progress.py -venv/Lib/site-packages/pip/_vendor/rich/prompt.py -venv/Lib/site-packages/pip/_vendor/rich/protocol.py -venv/Lib/site-packages/pip/_vendor/rich/region.py -venv/Lib/site-packages/pip/_vendor/rich/repr.py -venv/Lib/site-packages/pip/_vendor/rich/rule.py -venv/Lib/site-packages/pip/_vendor/rich/scope.py -venv/Lib/site-packages/pip/_vendor/rich/screen.py -venv/Lib/site-packages/pip/_vendor/rich/segment.py -venv/Lib/site-packages/pip/_vendor/rich/spinner.py -venv/Lib/site-packages/pip/_vendor/rich/status.py -venv/Lib/site-packages/pip/_vendor/rich/style.py -venv/Lib/site-packages/pip/_vendor/rich/styled.py -venv/Lib/site-packages/pip/_vendor/rich/syntax.py -venv/Lib/site-packages/pip/_vendor/rich/table.py -venv/Lib/site-packages/pip/_vendor/rich/terminal_theme.py -venv/Lib/site-packages/pip/_vendor/rich/text.py -venv/Lib/site-packages/pip/_vendor/rich/theme.py -venv/Lib/site-packages/pip/_vendor/rich/themes.py -venv/Lib/site-packages/pip/_vendor/rich/traceback.py -venv/Lib/site-packages/pip/_vendor/rich/tree.py -venv/Lib/site-packages/pip/_vendor/tenacity/__init__.py -venv/Lib/site-packages/pip/_vendor/tenacity/_asyncio.py -venv/Lib/site-packages/pip/_vendor/tenacity/_utils.py -venv/Lib/site-packages/pip/_vendor/tenacity/after.py -venv/Lib/site-packages/pip/_vendor/tenacity/before_sleep.py -venv/Lib/site-packages/pip/_vendor/tenacity/before.py -venv/Lib/site-packages/pip/_vendor/tenacity/nap.py -venv/Lib/site-packages/pip/_vendor/tenacity/retry.py -venv/Lib/site-packages/pip/_vendor/tenacity/stop.py -venv/Lib/site-packages/pip/_vendor/tenacity/tornadoweb.py -venv/Lib/site-packages/pip/_vendor/tenacity/wait.py -venv/Lib/site-packages/pip/_vendor/tomli/__init__.py -venv/Lib/site-packages/pip/_vendor/tomli/_parser.py -venv/Lib/site-packages/pip/_vendor/tomli/_re.py -venv/Lib/site-packages/pip/_vendor/tomli/_types.py -venv/Lib/site-packages/pip/_vendor/urllib3/__init__.py -venv/Lib/site-packages/pip/_vendor/urllib3/_collections.py -venv/Lib/site-packages/pip/_vendor/urllib3/_version.py -venv/Lib/site-packages/pip/_vendor/urllib3/connection.py -venv/Lib/site-packages/pip/_vendor/urllib3/connectionpool.py -venv/Lib/site-packages/pip/_vendor/urllib3/exceptions.py -venv/Lib/site-packages/pip/_vendor/urllib3/fields.py -venv/Lib/site-packages/pip/_vendor/urllib3/filepost.py -venv/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py -venv/Lib/site-packages/pip/_vendor/urllib3/request.py -venv/Lib/site-packages/pip/_vendor/urllib3/response.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__init__.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/appengine.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/securetransport.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/socks.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py -venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py -venv/Lib/site-packages/pip/_vendor/urllib3/packages/__init__.py -venv/Lib/site-packages/pip/_vendor/urllib3/packages/six.py -venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py -venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/__init__.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/connection.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/proxy.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/queue.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/request.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/response.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/retry.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/ssltransport.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/timeout.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/url.py -venv/Lib/site-packages/pip/_vendor/urllib3/util/wait.py -venv/Lib/site-packages/pip/_vendor/webencodings/__init__.py -venv/Lib/site-packages/pip/_vendor/webencodings/labels.py -venv/Lib/site-packages/pip/_vendor/webencodings/mklabels.py -venv/Lib/site-packages/pip/_vendor/webencodings/tests.py -venv/Lib/site-packages/pip/_vendor/webencodings/x_user_defined.py -venv/Lib/site-packages/pip-22.3.1.dist-info/entry_points.txt -venv/Lib/site-packages/pip-22.3.1.dist-info/INSTALLER -venv/Lib/site-packages/pip-22.3.1.dist-info/LICENSE.txt -venv/Lib/site-packages/pip-22.3.1.dist-info/METADATA -venv/Lib/site-packages/pip-22.3.1.dist-info/RECORD -venv/Lib/site-packages/pip-22.3.1.dist-info/REQUESTED -venv/Lib/site-packages/pip-22.3.1.dist-info/top_level.txt -venv/Lib/site-packages/pip-22.3.1.dist-info/WHEEL -venv/Lib/site-packages/pkg_resources/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/appdirs.py -venv/Lib/site-packages/pkg_resources/_vendor/zipp.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_adapters.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_common.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_compat.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_itertools.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_legacy.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/abc.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/readers.py -venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/simple.py -venv/Lib/site-packages/pkg_resources/_vendor/jaraco/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/jaraco/context.py -venv/Lib/site-packages/pkg_resources/_vendor/jaraco/functools.py -venv/Lib/site-packages/pkg_resources/_vendor/jaraco/text/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/more.py -venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/recipes.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/__about__.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/_manylinux.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/_musllinux.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/_structures.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/markers.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/requirements.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/specifiers.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/tags.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/utils.py -venv/Lib/site-packages/pkg_resources/_vendor/packaging/version.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/__init__.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/actions.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/common.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/core.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/exceptions.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/helpers.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/results.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/testing.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/unicode.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/util.py -venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/diagram/__init__.py -venv/Lib/site-packages/pkg_resources/extern/__init__.py -venv/Lib/site-packages/setuptools/__init__.py -venv/Lib/site-packages/setuptools/_deprecation_warning.py -venv/Lib/site-packages/setuptools/_entry_points.py -venv/Lib/site-packages/setuptools/_imp.py -venv/Lib/site-packages/setuptools/_importlib.py -venv/Lib/site-packages/setuptools/_itertools.py -venv/Lib/site-packages/setuptools/_path.py -venv/Lib/site-packages/setuptools/_reqs.py -venv/Lib/site-packages/setuptools/archive_util.py -venv/Lib/site-packages/setuptools/build_meta.py -venv/Lib/site-packages/setuptools/cli-32.exe -venv/Lib/site-packages/setuptools/cli-64.exe -venv/Lib/site-packages/setuptools/cli-arm64.exe -venv/Lib/site-packages/setuptools/cli.exe -venv/Lib/site-packages/setuptools/dep_util.py -venv/Lib/site-packages/setuptools/depends.py -venv/Lib/site-packages/setuptools/discovery.py -venv/Lib/site-packages/setuptools/dist.py -venv/Lib/site-packages/setuptools/errors.py -venv/Lib/site-packages/setuptools/extension.py -venv/Lib/site-packages/setuptools/glob.py -venv/Lib/site-packages/setuptools/gui-32.exe -venv/Lib/site-packages/setuptools/gui-64.exe -venv/Lib/site-packages/setuptools/gui-arm64.exe -venv/Lib/site-packages/setuptools/gui.exe -venv/Lib/site-packages/setuptools/installer.py -venv/Lib/site-packages/setuptools/launch.py -venv/Lib/site-packages/setuptools/logging.py -venv/Lib/site-packages/setuptools/monkey.py -venv/Lib/site-packages/setuptools/msvc.py -venv/Lib/site-packages/setuptools/namespaces.py -venv/Lib/site-packages/setuptools/package_index.py -venv/Lib/site-packages/setuptools/py34compat.py -venv/Lib/site-packages/setuptools/sandbox.py -venv/Lib/site-packages/setuptools/script (dev).tmpl -venv/Lib/site-packages/setuptools/script.tmpl -venv/Lib/site-packages/setuptools/unicode_utils.py -venv/Lib/site-packages/setuptools/version.py -venv/Lib/site-packages/setuptools/wheel.py -venv/Lib/site-packages/setuptools/windows_support.py -venv/Lib/site-packages/setuptools/_distutils/__init__.py -venv/Lib/site-packages/setuptools/_distutils/_collections.py -venv/Lib/site-packages/setuptools/_distutils/_functools.py -venv/Lib/site-packages/setuptools/_distutils/_macos_compat.py -venv/Lib/site-packages/setuptools/_distutils/_msvccompiler.py -venv/Lib/site-packages/setuptools/_distutils/archive_util.py -venv/Lib/site-packages/setuptools/_distutils/bcppcompiler.py -venv/Lib/site-packages/setuptools/_distutils/ccompiler.py -venv/Lib/site-packages/setuptools/_distutils/cmd.py -venv/Lib/site-packages/setuptools/_distutils/config.py -venv/Lib/site-packages/setuptools/_distutils/core.py -venv/Lib/site-packages/setuptools/_distutils/cygwinccompiler.py -venv/Lib/site-packages/setuptools/_distutils/debug.py -venv/Lib/site-packages/setuptools/_distutils/dep_util.py -venv/Lib/site-packages/setuptools/_distutils/dir_util.py -venv/Lib/site-packages/setuptools/_distutils/dist.py -venv/Lib/site-packages/setuptools/_distutils/errors.py -venv/Lib/site-packages/setuptools/_distutils/extension.py -venv/Lib/site-packages/setuptools/_distutils/fancy_getopt.py -venv/Lib/site-packages/setuptools/_distutils/file_util.py -venv/Lib/site-packages/setuptools/_distutils/filelist.py -venv/Lib/site-packages/setuptools/_distutils/log.py -venv/Lib/site-packages/setuptools/_distutils/msvc9compiler.py -venv/Lib/site-packages/setuptools/_distutils/msvccompiler.py -venv/Lib/site-packages/setuptools/_distutils/py38compat.py -venv/Lib/site-packages/setuptools/_distutils/py39compat.py -venv/Lib/site-packages/setuptools/_distutils/spawn.py -venv/Lib/site-packages/setuptools/_distutils/sysconfig.py -venv/Lib/site-packages/setuptools/_distutils/text_file.py -venv/Lib/site-packages/setuptools/_distutils/unixccompiler.py -venv/Lib/site-packages/setuptools/_distutils/util.py -venv/Lib/site-packages/setuptools/_distutils/version.py -venv/Lib/site-packages/setuptools/_distutils/versionpredicate.py -venv/Lib/site-packages/setuptools/_distutils/command/__init__.py -venv/Lib/site-packages/setuptools/_distutils/command/_framework_compat.py -venv/Lib/site-packages/setuptools/_distutils/command/bdist_dumb.py -venv/Lib/site-packages/setuptools/_distutils/command/bdist_rpm.py -venv/Lib/site-packages/setuptools/_distutils/command/bdist.py -venv/Lib/site-packages/setuptools/_distutils/command/build_clib.py -venv/Lib/site-packages/setuptools/_distutils/command/build_ext.py -venv/Lib/site-packages/setuptools/_distutils/command/build_py.py -venv/Lib/site-packages/setuptools/_distutils/command/build_scripts.py -venv/Lib/site-packages/setuptools/_distutils/command/build.py -venv/Lib/site-packages/setuptools/_distutils/command/check.py -venv/Lib/site-packages/setuptools/_distutils/command/clean.py -venv/Lib/site-packages/setuptools/_distutils/command/config.py -venv/Lib/site-packages/setuptools/_distutils/command/install_data.py -venv/Lib/site-packages/setuptools/_distutils/command/install_egg_info.py -venv/Lib/site-packages/setuptools/_distutils/command/install_headers.py -venv/Lib/site-packages/setuptools/_distutils/command/install_lib.py -venv/Lib/site-packages/setuptools/_distutils/command/install_scripts.py -venv/Lib/site-packages/setuptools/_distutils/command/install.py -venv/Lib/site-packages/setuptools/_distutils/command/py37compat.py -venv/Lib/site-packages/setuptools/_distutils/command/register.py -venv/Lib/site-packages/setuptools/_distutils/command/sdist.py -venv/Lib/site-packages/setuptools/_distutils/command/upload.py -venv/Lib/site-packages/setuptools/_vendor/__init__.py -venv/Lib/site-packages/setuptools/_vendor/ordered_set.py -venv/Lib/site-packages/setuptools/_vendor/typing_extensions.py -venv/Lib/site-packages/setuptools/_vendor/zipp.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/__init__.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_adapters.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_collections.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_compat.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_functools.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_itertools.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_meta.py -venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_text.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/__init__.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_adapters.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_common.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_compat.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_itertools.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_legacy.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/abc.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/readers.py -venv/Lib/site-packages/setuptools/_vendor/importlib_resources/simple.py -venv/Lib/site-packages/setuptools/_vendor/jaraco/__init__.py -venv/Lib/site-packages/setuptools/_vendor/jaraco/context.py -venv/Lib/site-packages/setuptools/_vendor/jaraco/functools.py -venv/Lib/site-packages/setuptools/_vendor/jaraco/text/__init__.py -venv/Lib/site-packages/setuptools/_vendor/more_itertools/__init__.py -venv/Lib/site-packages/setuptools/_vendor/more_itertools/more.py -venv/Lib/site-packages/setuptools/_vendor/more_itertools/recipes.py -venv/Lib/site-packages/setuptools/_vendor/packaging/__about__.py -venv/Lib/site-packages/setuptools/_vendor/packaging/__init__.py -venv/Lib/site-packages/setuptools/_vendor/packaging/_manylinux.py -venv/Lib/site-packages/setuptools/_vendor/packaging/_musllinux.py -venv/Lib/site-packages/setuptools/_vendor/packaging/_structures.py -venv/Lib/site-packages/setuptools/_vendor/packaging/markers.py -venv/Lib/site-packages/setuptools/_vendor/packaging/requirements.py -venv/Lib/site-packages/setuptools/_vendor/packaging/specifiers.py -venv/Lib/site-packages/setuptools/_vendor/packaging/tags.py -venv/Lib/site-packages/setuptools/_vendor/packaging/utils.py -venv/Lib/site-packages/setuptools/_vendor/packaging/version.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/__init__.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/actions.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/common.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/core.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/exceptions.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/helpers.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/results.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/testing.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/unicode.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/util.py -venv/Lib/site-packages/setuptools/_vendor/pyparsing/diagram/__init__.py -venv/Lib/site-packages/setuptools/_vendor/tomli/__init__.py -venv/Lib/site-packages/setuptools/_vendor/tomli/_parser.py -venv/Lib/site-packages/setuptools/_vendor/tomli/_re.py -venv/Lib/site-packages/setuptools/_vendor/tomli/_types.py -venv/Lib/site-packages/setuptools/command/__init__.py -venv/Lib/site-packages/setuptools/command/alias.py -venv/Lib/site-packages/setuptools/command/bdist_egg.py -venv/Lib/site-packages/setuptools/command/bdist_rpm.py -venv/Lib/site-packages/setuptools/command/build_clib.py -venv/Lib/site-packages/setuptools/command/build_ext.py -venv/Lib/site-packages/setuptools/command/build_py.py -venv/Lib/site-packages/setuptools/command/build.py -venv/Lib/site-packages/setuptools/command/develop.py -venv/Lib/site-packages/setuptools/command/dist_info.py -venv/Lib/site-packages/setuptools/command/easy_install.py -venv/Lib/site-packages/setuptools/command/editable_wheel.py -venv/Lib/site-packages/setuptools/command/egg_info.py -venv/Lib/site-packages/setuptools/command/install_egg_info.py -venv/Lib/site-packages/setuptools/command/install_lib.py -venv/Lib/site-packages/setuptools/command/install_scripts.py -venv/Lib/site-packages/setuptools/command/install.py -venv/Lib/site-packages/setuptools/command/launcher manifest.xml -venv/Lib/site-packages/setuptools/command/py36compat.py -venv/Lib/site-packages/setuptools/command/register.py -venv/Lib/site-packages/setuptools/command/rotate.py -venv/Lib/site-packages/setuptools/command/saveopts.py -venv/Lib/site-packages/setuptools/command/sdist.py -venv/Lib/site-packages/setuptools/command/setopt.py -venv/Lib/site-packages/setuptools/command/test.py -venv/Lib/site-packages/setuptools/command/upload_docs.py -venv/Lib/site-packages/setuptools/command/upload.py -venv/Lib/site-packages/setuptools/config/__init__.py -venv/Lib/site-packages/setuptools/config/_apply_pyprojecttoml.py -venv/Lib/site-packages/setuptools/config/expand.py -venv/Lib/site-packages/setuptools/config/pyprojecttoml.py -venv/Lib/site-packages/setuptools/config/setupcfg.py -venv/Lib/site-packages/setuptools/config/_validate_pyproject/__init__.py -venv/Lib/site-packages/setuptools/config/_validate_pyproject/error_reporting.py -venv/Lib/site-packages/setuptools/config/_validate_pyproject/extra_validations.py -venv/Lib/site-packages/setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py -venv/Lib/site-packages/setuptools/config/_validate_pyproject/fastjsonschema_validations.py -venv/Lib/site-packages/setuptools/config/_validate_pyproject/formats.py -venv/Lib/site-packages/setuptools/extern/__init__.py -venv/Lib/site-packages/setuptools-65.5.0.dist-info/entry_points.txt -venv/Lib/site-packages/setuptools-65.5.0.dist-info/INSTALLER -venv/Lib/site-packages/setuptools-65.5.0.dist-info/LICENSE -venv/Lib/site-packages/setuptools-65.5.0.dist-info/METADATA -venv/Lib/site-packages/setuptools-65.5.0.dist-info/RECORD -venv/Lib/site-packages/setuptools-65.5.0.dist-info/REQUESTED -venv/Lib/site-packages/setuptools-65.5.0.dist-info/top_level.txt -venv/Lib/site-packages/setuptools-65.5.0.dist-info/WHEEL -venv/Scripts/activate -venv/Scripts/activate.bat -venv/Scripts/Activate.ps1 -venv/Scripts/deactivate.bat -venv/Scripts/pip.exe -venv/Scripts/pip3.11.exe -venv/Scripts/pip3.exe -venv/Scripts/python.exe -venv/Scripts/pythonw.exe +venv \ No newline at end of file From e2f8e3a86159c6823482a7ff91ac63be7a1fd74d Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 5 Jun 2023 14:54:38 +0200 Subject: [PATCH 009/266] Tests and pipeline for Oracle - first version --- .github/workflows/Oracle-tests.yml | 54 ++++++ test/Oracle_Custom_Params_Tests.robot | 261 ++++++++++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 .github/workflows/Oracle-tests.yml create mode 100644 test/Oracle_Custom_Params_Tests.robot diff --git a/.github/workflows/Oracle-tests.yml b/.github/workflows/Oracle-tests.yml new file mode 100644 index 0000000..89fc91f --- /dev/null +++ b/.github/workflows/Oracle-tests.yml @@ -0,0 +1,54 @@ +name: Oracle Tests + +on: [push, pull_request] + +env: + DB_DATABASE: FREEPDB1 + DB_USER: db_user + DB_PASSWORD: pass + DB_MODULE: oracledb + +jobs: + run-robotframework-tests-psycopg2: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + services: + oracle: + image: gvenzl/oracle-free:latest + env: + APP_USER: db_user + APP_USER_PASSWORD: pass + ports: + - 1521:1521 + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install ${{ env.DB_MODULE }} + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Run Robot Framework tests using oracledb + working-directory: ./test + run: | + robot -T --xunit result.xml -d results/ -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} ${{ github.workspace }}/test/Oracle_Custom_Params_Tests.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/results/ diff --git a/test/Oracle_Custom_Params_Tests.robot b/test/Oracle_Custom_Params_Tests.robot new file mode 100644 index 0000000..374b20f --- /dev/null +++ b/test/Oracle_Custom_Params_Tests.robot @@ -0,0 +1,261 @@ +*** Settings *** +Library DatabaseLibrary + +Suite Setup Connect To DB +Suite Teardown Disconnect From Database +Test Setup Create Person Table And Insert Data +Test Teardown Drop Tables + + +*** Variables *** +${DBHost} 127.0.0.1 +${DBName} FREEPDB1 +${DBPass} pass +${DBPort} 1521 +${DBUser} db_user + + +*** Test Cases *** +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 person table + [Tags] db smoke + [Setup] Create Person Table + ${output} = Execute SQL Script ${CURDIR}/my_db_test_insertData.sql + Log ${output} + Should Be Equal As Strings ${output} None + +Execute SQL String - Create Table + [Tags] db smoke + [Setup] Log No setup for this test + ${output} = Create Foobar Table + 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 bug + 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 FETCH FIRST 1 ROWS ONLY + Log Many @{queryResults} + ${output} = Set Variable ${queryResults[0]} + Should Be Equal As Strings ${output} ('ID', , 39, None, 38, 0, False) + ${output} = Set Variable ${queryResults[1]} + Should Be Equal As Strings ${output} ('FIRST_NAME', , 20, 20, None, None, False) + ${output} = Set Variable ${queryResults[2]} + Should Be Equal As Strings ${output} ('LAST_NAME', , 20, 20, None, None, False) + ${NumColumns} = Get Length ${queryResults} + Should Be Equal As Integers ${NumColumns} 3 + +Verify foobar Description + [Tags] db smoke + [Setup] Create Foobar Table + Comment Query db for table column descriptions + @{queryResults} = Description SELECT * FROM foobar FETCH FIRST 1 ROWS ONLY + Log Many @{queryResults} + ${output} = Set Variable ${queryResults[0]} + Should Be Equal As Strings ${output} ('ID', , 39, None, 38, 0, False) + ${output} = Set Variable ${queryResults[1]} + Should Be Equal As Strings ${output} ('FIRST_NAME', , 20, 20, None, None, False) + ${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 + [Setup] Create 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 + +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 + [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 + [Tags] db smoke + [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 + [Tags] db smoke + [Setup] Create Foobar Table And Insert Data + ${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 + [Setup] Create Foobar Table And Insert Data + Delete All Rows From Table foobar + Comment Sleep 2s + +Verify Query - Row Count foobar table 0 row + [Tags] db smoke + [Setup] Create Foobar Table And Insert Data + Delete All Rows From Table foobar + 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,)] + +Transaction + [Tags] db smoke + [Setup] Create Person Table + 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 *** +Connect To DB + ${con_str} = Catenate SEPARATOR=${EMPTY} + ... ${DBUser}/${DBPass}@ + ... ${DBHost}:${DBPort}/${DBName} + Connect To Database Using Custom Params oracledb "${con_str}" + +Create Person Table + ${sql} = Catenate + ... CREATE TABLE person + ... (id INT GENERATED BY DEFAULT AS IDENTITY, + ... first_name varchar2(20) NOT NULL, + ... last_name varchar2(20) NOT NULL, + ... PRIMARY KEY(id)) + ${output} = Execute Sql String ${sql} + RETURN ${output} + +Create Person Table And Insert Data + Create Person Table + Execute SQL Script ${CURDIR}/my_db_test_insertData.sql + +Create Foobar Table + ${sql} = Catenate + ... CREATE TABLE foobar + ... (id INT GENERATED BY DEFAULT AS IDENTITY, + ... first_name varchar2(20) NOT NULL, + ... PRIMARY KEY(id)) + ${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') + +Begin first transaction + ${output} = Execute SQL String SAVEPOINT 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 SAVEPOINT 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 TO SAVEPOINT 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 TO SAVEPOINT 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 Tables + ${sql} = Catenate DROP TABLE IF EXISTS person + Execute SQL String ${sql} + ${sql} = Catenate DROP TABLE IF EXISTS foobar + Execute SQL String ${sql} From 962d9693d83e0db0be0765e71fcf46435d7e5960 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 5 Jun 2023 15:04:42 +0200 Subject: [PATCH 010/266] Trying to fix launching the Oracle container --- .github/workflows/Oracle-tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/Oracle-tests.yml b/.github/workflows/Oracle-tests.yml index 89fc91f..29cc791 100644 --- a/.github/workflows/Oracle-tests.yml +++ b/.github/workflows/Oracle-tests.yml @@ -18,10 +18,17 @@ jobs: oracle: image: gvenzl/oracle-free:latest env: + ORACLE_PASSWORD: pass APP_USER: db_user APP_USER_PASSWORD: pass ports: - 1521:1521 + # Provide healthcheck script options for startup + options: >- + --health-cmd healthcheck.sh + --health-interval 10s + --health-timeout 5s + --health-retries 10 steps: - name: Check out repository code From 0aea0ee6582dce0f888f24f6e9987de8127dd10e Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:19:20 +0200 Subject: [PATCH 011/266] put status of Oracle tests in readme --- .github/workflows/README.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/README.rst b/.github/workflows/README.rst index d407a8e..0d38f53 100644 --- a/.github/workflows/README.rst +++ b/.github/workflows/README.rst @@ -36,6 +36,12 @@ PostgreSQL psycopg2 Completed Pos \ psycopg3 Not Yet Implemented \ pyodbc Not Yet Implemented SQLite sqlite3 Completed SQLite-tests.yml +Oracle - "custom params" oracledb Workflow is done, Oracle-tests.yml + but some tests are failing + bugs have to be fixed + in the library, + tests are to be checked + and probably extended ================================== =========== ========================== ======================================= @@ -50,4 +56,6 @@ References: `PEP 249 - Python Database API Specification v2.0`_ -`Database interfaces available for Python`_ \ No newline at end of file +`Database interfaces available for Python`_ + +Docker container with Oracle DB: https://github.com/gvenzl/oci-oracle-free From 388dd8863e0a1a6fdac3877f43b325a4b2895623 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 9 Jun 2023 22:04:01 +0200 Subject: [PATCH 012/266] fix #178 - keyword 'table must exist' can now handle DB's that don't support information schema --- src/DatabaseLibrary/assertion.py | 20 ++++++++++++++++---- test/Oracle_Custom_Params_Tests.robot | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 70ed6dd..aff1b72 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -206,14 +206,26 @@ def table_must_exist(self, tableName, sansTran=False): 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) + table_exists = self.row_count(selectStatement, sansTran) > 0 elif self.db_api_module_name in ["sqlite3"]: selectStatement = ("SELECT name FROM sqlite_master WHERE type='table' AND name='%s' COLLATE NOCASE" % tableName) + table_exists = self.row_count(selectStatement, sansTran) > 0 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) + table_exists = self.row_count(selectStatement, sansTran) > 0 elif self.db_api_module_name in ["teradata"]: selectStatement = ("SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='%s'" % tableName) + table_exists = self.row_count(selectStatement, sansTran) > 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: + selectStatement = (f"SELECT * FROM information_schema.tables WHERE table_name='{tableName}'") + table_exists = self.row_count(selectStatement, sansTran) > 0 + except: + logger.info("Database doesn't support information schema, try using a simple SQL request") + try: + selectStatement = (f"SELECT 1 from {tableName} where 1=0") + num_rows = self.row_count(selectStatement, sansTran) + table_exists = True + except: + table_exists = False + assert table_exists, f"Table '{tableName}' does not exist in the db" diff --git a/test/Oracle_Custom_Params_Tests.robot b/test/Oracle_Custom_Params_Tests.robot index 374b20f..aeaee56 100644 --- a/test/Oracle_Custom_Params_Tests.robot +++ b/test/Oracle_Custom_Params_Tests.robot @@ -43,7 +43,7 @@ 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 - [Tags] db smoke bug + [Tags] db smoke Table Must Exist person Verify Row Count is 0 From adeacc9080092ed10ad854fe45a5c19f5b9dc59e Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 09:18:23 +0200 Subject: [PATCH 013/266] remove semicolon in keyword 'Delete All Rows From Table' to prevent failing in Oracle --- src/DatabaseLibrary/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 289d048..c99e6c2 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -178,7 +178,7 @@ def delete_all_rows_from_table(self, tableName, sansTran=False): | Delete All Rows From Table | person | True | """ cur = None - selectStatement = ("DELETE FROM %s;" % tableName) + selectStatement = ("DELETE FROM %s" % tableName) try: cur = self._dbconnection.cursor() logger.info('Executing : Delete All Rows From Table | %s ' % selectStatement) From 25cb4815e6ee28f17bec891159d3ac75bf8c93bd Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 09:30:58 +0200 Subject: [PATCH 014/266] fix tests for Oracle DB with "custom params" connection --- test/Oracle_Custom_Params_Tests.robot | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/Oracle_Custom_Params_Tests.robot b/test/Oracle_Custom_Params_Tests.robot index aeaee56..0af1b27 100644 --- a/test/Oracle_Custom_Params_Tests.robot +++ b/test/Oracle_Custom_Params_Tests.robot @@ -1,5 +1,6 @@ *** Settings *** Library DatabaseLibrary +Library Collections Suite Setup Connect To DB Suite Teardown Disconnect From Database @@ -105,21 +106,27 @@ Verify Query - Row Count person table [Tags] db smoke ${output} = Query SELECT COUNT(*) FROM person Log ${output} - Should Be Equal As Strings ${output} ((2,),) + ${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 [Tags] db smoke [Setup] Create Foobar Table ${output} = Query SELECT COUNT(*) FROM foobar Log ${output} - Should Be Equal As Strings ${output} ((0,),) + ${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 + 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 @@ -146,7 +153,10 @@ 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 Strings ${output} ((1,),) + ${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 [Tags] db smoke From eefee46f202f3ee812b27441e28d81af67992a08 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 12:47:29 +0200 Subject: [PATCH 015/266] PostgreSQL tests - use same DB params as in CI + fix path to the SQL script for local execution --- test/PostgreSQL_DB_Tests.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/PostgreSQL_DB_Tests.robot b/test/PostgreSQL_DB_Tests.robot index 9533466..9d9acdd 100644 --- a/test/PostgreSQL_DB_Tests.robot +++ b/test/PostgreSQL_DB_Tests.robot @@ -7,8 +7,8 @@ Library Collections *** Variables *** ${DBHost} localhost -${DBName} travis_ci_test -${DBPass} "" +${DBName} db +${DBPass} postgres ${DBPort} 5432 ${DBUser} postgres @@ -20,7 +20,7 @@ Create person table Execute SQL Script - Insert Data person table Comment ${output} = Execute SQL Script ./${DBName}_insertData.sql - ${output} = Execute SQL Script ./my_db_test_insertData.sql + ${output} = Execute SQL Script ${CURDIR}/my_db_test_insertData.sql Log ${output} Should Be Equal As Strings ${output} None From ba1ff09e7e15849fbffce3d76b2d82582e622eb7 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 14:02:18 +0200 Subject: [PATCH 016/266] Keyword 'Disconnect From Database' can be configured to fail if there was no connection --- src/DatabaseLibrary/connection_manager.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 6801ef4..aa120d2 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -173,16 +173,22 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne logger.info('Executing : Connect To Database Using Custom Params : %s.connect(%s) ' % (dbapiModuleName, db_connect_string)) self._dbconnection = eval(db_connect_string) - def disconnect_from_database(self): + def disconnect_from_database(self, error_if_no_connection=False): """ Disconnects from 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. For example: | Disconnect From Database | # disconnects from current connection to the database | """ logger.info('Executing : Disconnect From Database') - if self._dbconnection==None: - return 'No open connection to close' + if self._dbconnection is None: + log_msg = "No open database connection to close" + if error_if_no_connection: + raise ConnectionError(log_msg) + logger.info(log_msg) else: self._dbconnection.close() From 9b70442e2fed2c70c057a220e20ec9d779fac62a Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 14:05:02 +0200 Subject: [PATCH 017/266] Set the 'connection' to None in 'Disconnect From Database', otherwise it can stay as object with the value 'disconnected' (e.g. in Oracle) - in this case a next call of the keyword 'Disconnect From Database' won't work properly --- src/DatabaseLibrary/connection_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index aa120d2..0f6248f 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -191,6 +191,7 @@ def disconnect_from_database(self, error_if_no_connection=False): logger.info(log_msg) else: self._dbconnection.close() + self._dbconnection = None def set_auto_commit(self, autoCommit=True): """ From 17a07047bc6b6efc227c6a2bf349c724b9dbb1c0 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 15:04:37 +0200 Subject: [PATCH 018/266] [CI] MySQL tests need no ODBC driver for PostgreSQL --- .github/workflows/MySQL-tests.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/MySQL-tests.yml b/.github/workflows/MySQL-tests.yml index c85b78d..f71ae49 100644 --- a/.github/workflows/MySQL-tests.yml +++ b/.github/workflows/MySQL-tests.yml @@ -39,17 +39,6 @@ jobs: mysql -e "SHOW DATABASES;" -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} netstat -ano - - name: Install ODBC driver for PostgreSQL - run: | - echo "*** apt-get install the driver" - sudo apt-get install --yes odbc-postgresql - echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' - ls -l /usr/lib/x86_64-linux-gnu/odbc || true - echo '*** add full paths to Postgres .so files in /etc/odbcinst.ini' - sudo sed -i 's|Driver=psqlodbca.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbca.so|g' /etc/odbcinst.ini - sudo sed -i 's|Driver=psqlodbcw.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so|g' /etc/odbcinst.ini - sudo sed -i 's|Setup=libodbcpsqlS.so|Setup=/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so|g' /etc/odbcinst.ini - - name: Install ODBC driver for MySQL run: | cd "$RUNNER_TEMP" From 1515335c67ac3f65bc44c056022362e43d321d45 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 15:07:45 +0200 Subject: [PATCH 019/266] [CI] Remove unnecessary "ignore-all-modules" job from MySQL tests workflow --- .github/workflows/MySQL-tests.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/MySQL-tests.yml b/.github/workflows/MySQL-tests.yml index f71ae49..d405d40 100644 --- a/.github/workflows/MySQL-tests.yml +++ b/.github/workflows/MySQL-tests.yml @@ -106,12 +106,6 @@ jobs: python3 -c "import pyodbc; print(pyodbc.drivers())" robot --loglevel TRACE -d pyodbc/ -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:localhost -v DBPort:${{ env.DB_PORT }} -v DBCharset:utf8mb4 -v dbDriver:"{MySQL ODBC 8.0 ANSI Driver}" ${{ github.workspace }}/test/PyODBC_DB_Tests.robot - - name: Run Robot Framework tests using ${{ matrix.py-db-module }} - if: matrix.py-db-module == 'ignore-all-modules' - working-directory: ./test - run: | - robot -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/PyODBC_DB_Tests.robot - - name: Upload Robot Logs if: always() uses: actions/upload-artifact@v3 From 0902496f96b1d886116217b017acd60466012d9f Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 15:19:07 +0200 Subject: [PATCH 020/266] Revert "[CI] MySQL tests need no ODBC driver for PostgreSQL" This reverts commit 17a07047bc6b6efc227c6a2bf349c724b9dbb1c0. --- .github/workflows/MySQL-tests.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/MySQL-tests.yml b/.github/workflows/MySQL-tests.yml index d405d40..7a3ae4e 100644 --- a/.github/workflows/MySQL-tests.yml +++ b/.github/workflows/MySQL-tests.yml @@ -39,6 +39,17 @@ jobs: mysql -e "SHOW DATABASES;" -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} netstat -ano + - name: Install ODBC driver for PostgreSQL + run: | + echo "*** apt-get install the driver" + sudo apt-get install --yes odbc-postgresql + echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' + ls -l /usr/lib/x86_64-linux-gnu/odbc || true + echo '*** add full paths to Postgres .so files in /etc/odbcinst.ini' + sudo sed -i 's|Driver=psqlodbca.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbca.so|g' /etc/odbcinst.ini + sudo sed -i 's|Driver=psqlodbcw.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so|g' /etc/odbcinst.ini + sudo sed -i 's|Setup=libodbcpsqlS.so|Setup=/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so|g' /etc/odbcinst.ini + - name: Install ODBC driver for MySQL run: | cd "$RUNNER_TEMP" From ecc195eb5d2aadafeb04a7d9652298be78c8c64c Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 15:21:23 +0200 Subject: [PATCH 021/266] delete all IDE project files --- .project | 17 ----------------- .pydevproject | 10 ---------- 2 files changed, 27 deletions(-) delete mode 100644 .project delete mode 100644 .pydevproject diff --git a/.project b/.project deleted file mode 100644 index 2e6cf0a..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - robotframework-database-library - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index a090aef..0000000 --- a/.pydevproject +++ /dev/null @@ -1,10 +0,0 @@ - - - - -/opt/python-virtualenvs/robot/bin/python -python 2.6 - -/robotframework-database-library/src - - From 55092ff96b4dd2d417e06ee619026a5d5dd8a5b1 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 15:36:18 +0200 Subject: [PATCH 022/266] vscode settings and launch configs --- .vscode/extensions.json | 10 ++++++ .vscode/launch.json | 68 +++++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 15 +++++++++ 3 files changed, 93 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..eeb930b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "ms-python.python", + "ms-python.vscode-pylance", + "ms-python.pylint", + "robocorp.robotframework-lsp", + "techer.open-in-browser", + "eamodio.gitlens" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..03efdff --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,68 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "robotframework-lsp", + "name": "Launch all tests in Dryrun mode", + "request": "launch", + "cwd": "${workspaceFolder}", + "target": "${workspaceFolder}/test", + "terminal": "integrated", + "args": [ + "--outputdir", "logs", + "--loglevel", "DEBUG:INFO", + "--dryrun" + ], + }, + { + "type": "robotframework-lsp", + "name": "Launch .robot file in Dryrun mode", + "request": "launch", + "cwd": "${workspaceFolder}", + "target": "${file}", + "terminal": "integrated", + "args": [ + "--outputdir", "logs", + "--loglevel", "DEBUG:INFO", + "--dryrun" + ], + }, + { + "type": "robotframework-lsp", + "name": "Launch .robot file for DB", + "request": "launch", + "cwd": "${workspaceFolder}", + "target": "${file}", + "terminal": "integrated", + "args": [ + "--outputdir", "logs", + "--loglevel", "DEBUG:INFO", + "-v DB_MODULE:${input:DB_MODULE}", + ], + }, + { + "type": "robotframework-lsp", + "name": "Robot Framework: Launch template", + "request": "launch", + "cwd": "^\"\\${workspaceFolder}\"", + "target": "^\"\\${file}\"", + "terminal": "integrated", + "args": [ + "--outputdir", "logs", + "--loglevel", "DEBUG:INFO", + ], + }, + ], + "inputs": [ + { + "type": "pickString", + "id": "DB_MODULE", + "description": "Database to run the tests for", + "options": ["oracledb"], + "default": "oracledb" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c6a49df --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "files.exclude": { + "**/.git": true, + "**/__pycache__": true + }, + "robot.lint.robocop.enabled": true, + "robot.lint.unusedKeyword":false, + "robot.interactiveConsole.arguments": [ + "--output", "${workspaceRoot}/logs/interactive_console.xml" + ], + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.formatting.provider": "black", + "python.analysis.completeFunctionParens": true, +} \ No newline at end of file From 042c08f733b8c9d40e4ebf7c027a8de769574c0c Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 21:17:09 +0200 Subject: [PATCH 023/266] fix #173 - keyword 'Connect using custom params' handles now connection strings with and without quotes --- src/DatabaseLibrary/connection_manager.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 0f6248f..772623c 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -167,11 +167,14 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne """ db_api_2 = importlib.import_module(dbapiModuleName) - db_connect_string = 'db_api_2.connect(%s)' % db_connect_string + # delete possible quotation marks deliberately provided in RF- it was necessary previously. + # so this should stay for backwards compatibility. + db_connect_string = db_connect_string.strip('"') + db_connect_string = db_connect_string.strip("'") 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) + logger.info(f"Executing : Connect To Database Using Custom Params : {dbapiModuleName}.connect('{db_connect_string}')") + self._dbconnection = db_api_2.connect(db_connect_string) def disconnect_from_database(self, error_if_no_connection=False): """ From c96f4642c47357d7675bbca3aaccfa03e69d9cde Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 13 Jun 2023 22:01:52 +0200 Subject: [PATCH 024/266] Revert "fix #173 - keyword 'Connect using custom params' handles now connection strings with and without quotes" This reverts commit 042c08f733b8c9d40e4ebf7c027a8de769574c0c. --- src/DatabaseLibrary/connection_manager.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 772623c..0f6248f 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -167,14 +167,11 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne """ db_api_2 = importlib.import_module(dbapiModuleName) - # delete possible quotation marks deliberately provided in RF- it was necessary previously. - # so this should stay for backwards compatibility. - db_connect_string = db_connect_string.strip('"') - db_connect_string = db_connect_string.strip("'") + db_connect_string = 'db_api_2.connect(%s)' % db_connect_string self.db_api_module_name = dbapiModuleName - logger.info(f"Executing : Connect To Database Using Custom Params : {dbapiModuleName}.connect('{db_connect_string}')") - self._dbconnection = db_api_2.connect(db_connect_string) + logger.info('Executing : Connect To Database Using Custom Params : %s.connect(%s) ' % (dbapiModuleName, db_connect_string)) + self._dbconnection = eval(db_connect_string) def disconnect_from_database(self, error_if_no_connection=False): """ From 29169b734231e914599f5bdfabf1c69c549da596 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 14 Jun 2023 08:13:01 +0200 Subject: [PATCH 025/266] fix #173 - use new keyword "Connect To Database Using Custom Connection String" for connection strings. The original keyword 'Connect using custom params' handles params map strings only, --- src/DatabaseLibrary/connection_manager.py | 35 ++++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 0f6248f..52d02a3 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -156,23 +156,44 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_connect_string=''): """ 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`. + 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. - Example usage: - | # for psycopg2 | + Example usage: | Connect To Database Using Custom Params | psycopg2 | database='my_db_test', user='postgres', password='s3cr3t', host='tiger.foobar.com', port=5432 | - - | # for JayDeBeApi | | 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(dbapiModuleName) + self.db_api_module_name = dbapiModuleName - db_connect_string = 'db_api_2.connect(%s)' % db_connect_string + db_connect_string = f'db_api_2.connect({db_connect_string})' - 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) + def connect_to_database_using_custom_connection_string(self, dbapiModuleName=None, db_connect_string=''): + """ + Loads the DB API 2.0 module given `dbapiModuleName` then uses it to + connect to the database using the `db_connect_string` + (parsed as single connection connection string or URI). + + Use `connect_to_database_using_custom_params` for passing + connection params as named arguments. + + 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(dbapiModuleName) + self.db_api_module_name = dbapiModuleName + logger.info(f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect('{db_connect_string}')") + self._dbconnection = db_api_2.connect(db_connect_string) + def disconnect_from_database(self, error_if_no_connection=False): """ Disconnects from the database. From e89db087f3cc49da9db8cc518776b0fd70243dff Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 14 Jun 2023 14:24:43 +0200 Subject: [PATCH 026/266] Extend gitignore --- .gitignore | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 5d9f69a..2b5d413 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,11 @@ build/ .idea .py*/ *.egg-info/ -test/log.html -test/my_db_test.db -test/output.xml -test/report.html -test/logs/ -venv \ No newline at end of file +**/my_db_test.db +logs +**/output.xml +**/interactive_console_output.xml +**/log.html +**/report.html +venv +.runNumber From 2ce84f13c9bcd476de8c7011bf11d2e79efd27c6 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 14 Jun 2023 14:26:10 +0200 Subject: [PATCH 027/266] Update Teradata tests --- test/Teradata_DB_Tests.robot | 53 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/test/Teradata_DB_Tests.robot b/test/Teradata_DB_Tests.robot index 35c7f82..6dc779e 100644 --- a/test/Teradata_DB_Tests.robot +++ b/test/Teradata_DB_Tests.robot @@ -1,32 +1,37 @@ *** Settings *** -Suite Setup Connect To Database teradata ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} -Suite Teardown Disconnect From Database -Library DatabaseLibrary -Library OperatingSystem -Library Collections +Library DatabaseLibrary +Library OperatingSystem +Library Collections + +Suite Setup Connect To Database teradata ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} +Suite Teardown Disconnect From Database + *** Variables *** -${DBHost} 192.168.10.45 -${DBName} ADRIAN -${DBPass} dbc -${DBPort} 1025 -${DBUser} dbc +${DBHost} 192.168.0.231 +${DBName} db +${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)); + ${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 + ${output} = Execute SQL Script ${CURDIR}/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) + ${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 @@ -90,25 +95,25 @@ Verify foobar Description 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 + ${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 + ${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 + 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; @@ -128,9 +133,9 @@ Insert Data Into Table foobar 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 + ${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 From 8fd4032972a9b00983fc142836c809e60c740ad5 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 15:46:47 +0200 Subject: [PATCH 028/266] Update and restructure tests --- test/readme.md | 46 +++ test/resources/common.resource | 94 +++++ .../excel_db_test_insertData.sql | 0 .../insert_data_in_person_table.sql} | 0 test/testing.sql | 2 - test/tests/__init__.robot | 84 ++++ test/{ => tests/_old}/DB2SQL_DB_Conf.txt | 0 test/{ => tests/_old}/DB2SQL_DB_Tests.robot | 0 test/{ => tests/_old}/MSSQL_DB_Tests.robot | 0 test/tests/_old/MySQL-tests.yml | 125 ++++++ test/{ => tests/_old}/MySQL_DB_Tests.robot | 0 test/tests/_old/Oracle-tests.yml | 61 +++ .../_old}/Oracle_Custom_Params_Tests.robot | 25 +- test/tests/_old/PostgreSQL-tests.yml | 71 ++++ .../_old}/PostgreSQL_DB_Tests.robot | 6 +- test/{ => tests/_old}/PyODBC_DB_Tests.robot | 360 +++++++++--------- test/tests/_old/SQLite-tests.yml | 38 ++ test/{ => tests/_old}/SQLite3_DB_Tests.robot | 0 test/{ => tests/_old}/Teradata_DB_Tests.robot | 2 +- .../_old/excel_old.robot} | 4 +- test/tests/_old/my_db_test_insertData.sql | 2 + test/tests/common_tests/basic_tests.robot | 124 ++++++ .../common_tests/custom_connection.robot | 22 ++ test/tests/common_tests/description.robot | 95 +++++ .../common_tests/disconnect_from_db.robot | 33 ++ test/tests/common_tests/transaction.robot | 93 +++++ test/tests/custom_db_tests/excel.robot | 245 ++++++++++++ 27 files changed, 1333 insertions(+), 199 deletions(-) create mode 100644 test/readme.md create mode 100644 test/resources/common.resource rename test/{ => resources}/excel_db_test_insertData.sql (100%) rename test/{my_db_test_insertData.sql => resources/insert_data_in_person_table.sql} (100%) delete mode 100644 test/testing.sql create mode 100644 test/tests/__init__.robot rename test/{ => tests/_old}/DB2SQL_DB_Conf.txt (100%) rename test/{ => tests/_old}/DB2SQL_DB_Tests.robot (100%) rename test/{ => tests/_old}/MSSQL_DB_Tests.robot (100%) create mode 100644 test/tests/_old/MySQL-tests.yml rename test/{ => tests/_old}/MySQL_DB_Tests.robot (100%) create mode 100644 test/tests/_old/Oracle-tests.yml rename test/{ => tests/_old}/Oracle_Custom_Params_Tests.robot (93%) create mode 100644 test/tests/_old/PostgreSQL-tests.yml rename test/{ => tests/_old}/PostgreSQL_DB_Tests.robot (97%) rename test/{ => tests/_old}/PyODBC_DB_Tests.robot (97%) create mode 100644 test/tests/_old/SQLite-tests.yml rename test/{ => tests/_old}/SQLite3_DB_Tests.robot (100%) rename test/{ => tests/_old}/Teradata_DB_Tests.robot (99%) rename test/{Excel_DB_Test.robot => tests/_old/excel_old.robot} (98%) create mode 100644 test/tests/_old/my_db_test_insertData.sql create mode 100644 test/tests/common_tests/basic_tests.robot create mode 100644 test/tests/common_tests/custom_connection.robot create mode 100644 test/tests/common_tests/description.robot create mode 100644 test/tests/common_tests/disconnect_from_db.robot create mode 100644 test/tests/common_tests/transaction.robot create mode 100644 test/tests/custom_db_tests/excel.robot diff --git a/test/readme.md b/test/readme.md new file mode 100644 index 0000000..df468b9 --- /dev/null +++ b/test/readme.md @@ -0,0 +1,46 @@ +# 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 0000000..ef37741 --- /dev/null +++ b/test/resources/common.resource @@ -0,0 +1,94 @@ +*** 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 + + +*** 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 + + +*** Keywords *** +Connect To DB + [Documentation] Connects to the database based on the current DB module under test + ... and connection params set in global variables + IF "${DB_MODULE_MODE}" == "custom" + IF "${DB_MODULE}" == "sqlite3" + Remove File ${DBName}.db + Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None + ELSE + ${Connection String}= Build Connection String + Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} + END + ELSE IF "${DB_MODULE_MODE}" == "standard" + Connect To Database + ... ${DB_MODULE} + ... ${DB_NAME} + ... ${DB_USER} + ... ${DB_PASS} + ... ${DB_HOST} + ... ${DB_PORT} + 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 + Fail 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 + ${output}= Execute SQL Script ${CURDIR}/insert_data_in_person_table.sql + 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/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/testing.sql b/test/testing.sql deleted file mode 100644 index d63dd0e..0000000 --- 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__.robot b/test/tests/__init__.robot new file mode 100644 index 0000000..308621e --- /dev/null +++ b/test/tests/__init__.robot @@ -0,0 +1,84 @@ +*** 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}" == "Oracle_Custom" + Set Global Variable ${DB_MODULE_MODE} custom + 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}" == "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}" == "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/DB2SQL_DB_Conf.txt b/test/tests/_old/DB2SQL_DB_Conf.txt similarity index 100% rename from test/DB2SQL_DB_Conf.txt rename to test/tests/_old/DB2SQL_DB_Conf.txt diff --git a/test/DB2SQL_DB_Tests.robot b/test/tests/_old/DB2SQL_DB_Tests.robot similarity index 100% rename from test/DB2SQL_DB_Tests.robot rename to test/tests/_old/DB2SQL_DB_Tests.robot diff --git a/test/MSSQL_DB_Tests.robot b/test/tests/_old/MSSQL_DB_Tests.robot similarity index 100% rename from test/MSSQL_DB_Tests.robot rename to test/tests/_old/MSSQL_DB_Tests.robot diff --git a/test/tests/_old/MySQL-tests.yml b/test/tests/_old/MySQL-tests.yml new file mode 100644 index 0000000..1568794 --- /dev/null +++ b/test/tests/_old/MySQL-tests.yml @@ -0,0 +1,125 @@ +name: MySQL (pymysql, pyodbc) Tests + +on: [push, pull_request] + +env: + DB_DATABASE: my_db_test + DB_USER: root + DB_PASSWORD: root + DB_HOST: 127.0.0.1 + DB_PORT: 3306 + +jobs: + dbsetup: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # py-db-module: ["pymysql", "pyodbc", "pymssql", "MySQLdb"] + include: + - db-name: PyMySQL + py-db-module: pymysql + pip-install: pymysql + - db-name: PyODBC + py-db-module: pyodbc + pip-install: pyodbc + - db-name: MySQLdb + py-db-module: MySQLdb + pip-install: mysqlclient + steps: + - uses: actions/checkout@v3 + + - name: Set up MySQL + run: | + sudo /etc/init.d/mysql start + mysql -e 'CREATE DATABASE ${{ matrix.py-db-module }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} + + - name: Verify MySQL Setup + run: | + mysql -e "SHOW DATABASES;" -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} + netstat -ano + + - name: Install ODBC driver for PostgreSQL + run: | + echo "*** apt-get install the driver" + sudo apt-get install --yes odbc-postgresql + echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' + ls -l /usr/lib/x86_64-linux-gnu/odbc || true + echo '*** add full paths to Postgres .so files in /etc/odbcinst.ini' + sudo sed -i 's|Driver=psqlodbca.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbca.so|g' /etc/odbcinst.ini + sudo sed -i 's|Driver=psqlodbcw.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so|g' /etc/odbcinst.ini + sudo sed -i 's|Setup=libodbcpsqlS.so|Setup=/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so|g' /etc/odbcinst.ini + + - name: Install ODBC driver for MySQL + run: | + cd "$RUNNER_TEMP" + echo "*** download driver zip file" + curl --silent --show-error --write-out "$CURL_OUTPUT_FORMAT" -O "https://www.mirrorservice.org/sites/ftp.mysql.com/Downloads/Connector-ODBC/8.0/${MYSQL_DRIVER}.tar.gz" + ls -l "${MYSQL_DRIVER}.tar.gz" + tar -xz -f "${MYSQL_DRIVER}.tar.gz" + echo "*** copy driver file to /usr/lib" + sudo cp -v "${MYSQL_DRIVER}/lib/libmyodbc8a.so" /usr/lib/x86_64-linux-gnu/odbc/ + sudo chmod a+r /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so + echo "*** create odbcinst.ini entry" + echo '[MySQL ODBC 8.0 ANSI Driver]' > mysql_odbcinst.ini + echo 'Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so' >> mysql_odbcinst.ini + echo 'UsageCount = 1' >> mysql_odbcinst.ini + echo 'Threading = 2' >> mysql_odbcinst.ini + sudo odbcinst -i -d -f mysql_odbcinst.ini + env: + CURL_OUTPUT_FORMAT: '%{http_code} %{filename_effective} %{size_download} %{time_total}\n' + MYSQL_DRIVER: mysql-connector-odbc-8.0.22-linux-glibc2.12-x86-64bit + + - name: Check ODBC setup + run: | + echo "*** odbcinst -j" + odbcinst -j + echo "*** cat /etc/odbcinst.ini" + cat /etc/odbcinst.ini + echo "*** cat /etc/odbc.ini" + cat /etc/odbc.ini + echo '*** ls -l /opt/microsoft/msodbcsql17/lib64' + ls -l /opt/microsoft/msodbcsql17/lib64 || true + echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' + ls -l /usr/lib/x86_64-linux-gnu/odbc || true + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.16' + + - name: Setup Python Packaging and Pip + run: | + python -m pip install --upgrade pip + pip install wheel + + - name: Setup Robot Framework + run: | + pip install robotframework + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install ${{ github.workspace }}/. + + - name: Setup Python DB module + run: | + pip install ${{ matrix.pip-install }} + + - name: Run Robot Framework tests using PyMySQL + if: matrix.py-db-module == 'pymysql' + working-directory: ./test + run: | + robot -d pymysql/ -v DBModule:${{ matrix.py-db-module }} -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/MySQL_DB_Tests.robot + + - name: Run Robot Framework tests using PyODBC + if: matrix.py-db-module == 'pyodbc' + working-directory: ./test + run: | + python3 -c "import pyodbc; print(pyodbc.drivers())" + robot --loglevel TRACE -d pyodbc/ -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:localhost -v DBPort:${{ env.DB_PORT }} -v DBCharset:utf8mb4 -v dbDriver:"{MySQL ODBC 8.0 ANSI Driver}" ${{ github.workspace }}/test/PyODBC_DB_Tests.robot + + - name: Upload Robot Logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/ \ No newline at end of file diff --git a/test/MySQL_DB_Tests.robot b/test/tests/_old/MySQL_DB_Tests.robot similarity index 100% rename from test/MySQL_DB_Tests.robot rename to test/tests/_old/MySQL_DB_Tests.robot diff --git a/test/tests/_old/Oracle-tests.yml b/test/tests/_old/Oracle-tests.yml new file mode 100644 index 0000000..29cc791 --- /dev/null +++ b/test/tests/_old/Oracle-tests.yml @@ -0,0 +1,61 @@ +name: Oracle Tests + +on: [push, pull_request] + +env: + DB_DATABASE: FREEPDB1 + DB_USER: db_user + DB_PASSWORD: pass + DB_MODULE: oracledb + +jobs: + run-robotframework-tests-psycopg2: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + services: + oracle: + image: gvenzl/oracle-free:latest + env: + ORACLE_PASSWORD: pass + APP_USER: db_user + APP_USER_PASSWORD: pass + ports: + - 1521:1521 + # Provide healthcheck script options for startup + options: >- + --health-cmd healthcheck.sh + --health-interval 10s + --health-timeout 5s + --health-retries 10 + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install ${{ env.DB_MODULE }} + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Run Robot Framework tests using oracledb + working-directory: ./test + run: | + robot -T --xunit result.xml -d results/ -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} ${{ github.workspace }}/test/Oracle_Custom_Params_Tests.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/results/ diff --git a/test/Oracle_Custom_Params_Tests.robot b/test/tests/_old/Oracle_Custom_Params_Tests.robot similarity index 93% rename from test/Oracle_Custom_Params_Tests.robot rename to test/tests/_old/Oracle_Custom_Params_Tests.robot index 0af1b27..4de9c88 100644 --- a/test/Oracle_Custom_Params_Tests.robot +++ b/test/tests/_old/Oracle_Custom_Params_Tests.robot @@ -10,7 +10,7 @@ Test Teardown Drop Tables *** Variables *** ${DBHost} 127.0.0.1 -${DBName} FREEPDB1 +${DBName} db ${DBPass} pass ${DBPort} 1521 ${DBUser} db_user @@ -116,9 +116,9 @@ Verify Query - Row Count foobar table [Setup] Create 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 + ${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 @@ -153,9 +153,9 @@ Verify Query - Row Count foobar table 1 row [Setup] Create Foobar Table And Insert Data ${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 + ${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 @@ -196,12 +196,15 @@ Connect To DB Connect To Database Using Custom Params oracledb "${con_str}" Create Person Table + # ${sql} = Catenate + # ... CREATE TABLE person + # ... (id INT GENERATED BY DEFAULT AS IDENTITY, + # ... first_name varchar2(20) NOT NULL, + # ... last_name varchar2(20) NOT NULL, + # ... PRIMARY KEY(id)) ${sql} = Catenate ... CREATE TABLE person - ... (id INT GENERATED BY DEFAULT AS IDENTITY, - ... first_name varchar2(20) NOT NULL, - ... last_name varchar2(20) NOT NULL, - ... PRIMARY KEY(id)) + ... (id integer not null unique,first_name varchar(20),last_name varchar(20)) ${output} = Execute Sql String ${sql} RETURN ${output} diff --git a/test/tests/_old/PostgreSQL-tests.yml b/test/tests/_old/PostgreSQL-tests.yml new file mode 100644 index 0000000..fe68cf4 --- /dev/null +++ b/test/tests/_old/PostgreSQL-tests.yml @@ -0,0 +1,71 @@ +name: PostgreSQL (psycopg2) Tests + +on: [push, pull_request] + +env: + DB_DATABASE: db + DB_USER: postgres + DB_PASSWORD: postgres + DB_HOST: postgres + DB_PORT: 5432 + DB_MODULE: psycopg2 + +jobs: + run-robotframework-tests-psycopg2: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + services: + postgres: + image: postgres:11 + env: + POSTGRES_DB: db + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - '5432' + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install ${{ env.DB_MODULE }} + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Set DB options if in ACT + if: ${{ env.ACT }} + run: | + echo "DB_HOST=${{ env.ACT_DB_HOST }}" >> $GITHUB_ENV + echo "DB_PORT=${{ env.ACT_POSTGRES_DB_PORT }}" >> $GITHUB_ENV + + - name: Set DB options if not in ACT + if: ${{ !env.ACT }} + run: | + echo "DB_HOST=localhost" >> $GITHUB_ENV + echo "DB_PORT=${{ job.services.postgres.ports[5432] }}" >> $GITHUB_ENV + + - name: Run Robot Framework tests using psycopg2 + working-directory: ./test + run: | + robot -T --xunit result.xml -d results/ -v DBModule:${{ env.DB_MODULE }} -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/PostgreSQL_DB_Tests.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/results/ diff --git a/test/PostgreSQL_DB_Tests.robot b/test/tests/_old/PostgreSQL_DB_Tests.robot similarity index 97% rename from test/PostgreSQL_DB_Tests.robot rename to test/tests/_old/PostgreSQL_DB_Tests.robot index 9d9acdd..d77c106 100644 --- a/test/PostgreSQL_DB_Tests.robot +++ b/test/tests/_old/PostgreSQL_DB_Tests.robot @@ -8,13 +8,13 @@ Library Collections *** Variables *** ${DBHost} localhost ${DBName} db -${DBPass} postgres +${DBPass} pass ${DBPort} 5432 -${DBUser} postgres +${DBUser} db_user *** Test Cases *** Create person table - ${output} = Execute SQL String CREATE TABLE person (id integer unique,first_name varchar,last_name varchar); + ${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 diff --git a/test/PyODBC_DB_Tests.robot b/test/tests/_old/PyODBC_DB_Tests.robot similarity index 97% rename from test/PyODBC_DB_Tests.robot rename to test/tests/_old/PyODBC_DB_Tests.robot index 3f7feac..874b121 100644 --- a/test/PyODBC_DB_Tests.robot +++ b/test/tests/_old/PyODBC_DB_Tests.robot @@ -1,180 +1,180 @@ -*** Settings *** -Suite Setup Connect To Database ${DBModule} ${DBName} ${DBUser} ${DBPass} dbHost=${DBHost} dbPort=${DBPort} dbCharset=${DBCharset} dbDriver=${dbDriver} -Suite Teardown Disconnect From Database -Library DatabaseLibrary -Library Collections -Library OperatingSystem - -*** Variables *** -${DBModule} pyodbc -${DBHost} ${EMPTY} -${DBName} ${EMPTY} -${DBPass} ${EMPTY} -${DBPort} ${EMPTY} -${DBUser} ${EMPTY} -${DBCharset} ${None} -#${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 * FROM person LIMIT 1; - Log Many @{queryResults} - ${output} = Set Variable ${queryResults[0]} - Should Be Equal As Strings ${output} ('id', , None, 10, 10, 0, True) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('first_name', , None, 20, 20, 0, True) - ${output} = Set Variable ${queryResults[2]} - Should Be Equal As Strings ${output} ('last_name', , None, 20, 20, 0, True) - ${NumColumns} = Get Length ${queryResults} - Should Be Equal As Integers ${NumColumns} 3 - -Verify foobar Description - 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, 10, 10, 0, False) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('firstname', , None, 20, 20, 0, True) - ${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 SAVEPOINT 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 SAVEPOINT 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 TO 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 TO 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 +*** Settings *** +Suite Setup Connect To Database ${DBModule} ${DBName} ${DBUser} ${DBPass} dbHost=${DBHost} dbPort=${DBPort} dbCharset=${DBCharset} dbDriver=${dbDriver} +Suite Teardown Disconnect From Database +Library DatabaseLibrary +Library Collections +Library OperatingSystem + +*** Variables *** +${DBModule} pyodbc +${DBHost} ${EMPTY} +${DBName} ${EMPTY} +${DBPass} ${EMPTY} +${DBPort} ${EMPTY} +${DBUser} ${EMPTY} +${DBCharset} ${None} +#${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 * FROM person LIMIT 1; + Log Many @{queryResults} + ${output} = Set Variable ${queryResults[0]} + Should Be Equal As Strings ${output} ('id', , None, 10, 10, 0, True) + ${output} = Set Variable ${queryResults[1]} + Should Be Equal As Strings ${output} ('first_name', , None, 20, 20, 0, True) + ${output} = Set Variable ${queryResults[2]} + Should Be Equal As Strings ${output} ('last_name', , None, 20, 20, 0, True) + ${NumColumns} = Get Length ${queryResults} + Should Be Equal As Integers ${NumColumns} 3 + +Verify foobar Description + 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, 10, 10, 0, False) + ${output} = Set Variable ${queryResults[1]} + Should Be Equal As Strings ${output} ('firstname', , None, 20, 20, 0, True) + ${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 SAVEPOINT 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 SAVEPOINT 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 TO 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 TO 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/tests/_old/SQLite-tests.yml b/test/tests/_old/SQLite-tests.yml new file mode 100644 index 0000000..1d0fa27 --- /dev/null +++ b/test/tests/_old/SQLite-tests.yml @@ -0,0 +1,38 @@ +name: SQLite (sqlite3) Tests + +on: [push, pull_request] + +jobs: + run-robotframework-tests-sqlite: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Run Robot Framework tests using SQLite3 + working-directory: ./test + run: | + robot -T --xunit result.xml -d results/ ${{ github.workspace }}/test/SQLite3_DB_Tests.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/results/ diff --git a/test/SQLite3_DB_Tests.robot b/test/tests/_old/SQLite3_DB_Tests.robot similarity index 100% rename from test/SQLite3_DB_Tests.robot rename to test/tests/_old/SQLite3_DB_Tests.robot diff --git a/test/Teradata_DB_Tests.robot b/test/tests/_old/Teradata_DB_Tests.robot similarity index 99% rename from test/Teradata_DB_Tests.robot rename to test/tests/_old/Teradata_DB_Tests.robot index 6dc779e..dab7795 100644 --- a/test/Teradata_DB_Tests.robot +++ b/test/tests/_old/Teradata_DB_Tests.robot @@ -19,7 +19,7 @@ ${DBUser} dbc 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)); + ... 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 diff --git a/test/Excel_DB_Test.robot b/test/tests/_old/excel_old.robot similarity index 98% rename from test/Excel_DB_Test.robot rename to test/tests/_old/excel_old.robot index 0a4d287..85e41cc 100644 --- a/test/Excel_DB_Test.robot +++ b/test/tests/_old/excel_old.robot @@ -15,7 +15,7 @@ ${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 @@ -286,7 +286,7 @@ Drop person and foobar tables *** Keywords *** Setup testing excel - Create Excel Workbook Test_Excel + Create Excel Document Test_Excel Connect To Database excelrw ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} Cleanup testing excel diff --git a/test/tests/_old/my_db_test_insertData.sql b/test/tests/_old/my_db_test_insertData.sql new file mode 100644 index 0000000..566fd1b --- /dev/null +++ b/test/tests/_old/my_db_test_insertData.sql @@ -0,0 +1,2 @@ +INSERT INTO person VALUES(1,'Franz Allan','See'); +INSERT INTO person VALUES(2,'Jerry','Schneider'); diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot new file mode 100644 index 0000000..d20aac9 --- /dev/null +++ b/test/tests/common_tests/basic_tests.robot @@ -0,0 +1,124 @@ +*** 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 With Semicolon Works + Query SELECT * FROM person + +SQL Statement Ending Without Semicolon Works + Query SELECT * FROM person; + # ToDo: fix for oracle + +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 + +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 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 + +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/custom_connection.robot b/test/tests/common_tests/custom_connection.robot new file mode 100644 index 0000000..d235c20 --- /dev/null +++ b/test/tests/common_tests/custom_connection.robot @@ -0,0 +1,22 @@ +*** 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 currend DB module + + +*** Test Cases *** +# ToDo: custom tests for params and string for oracle and psycopg2 and sqlite (this one supports params only) +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 + Fail implement me! \ No newline at end of file diff --git a/test/tests/common_tests/description.robot b/test/tests/common_tests/description.robot new file mode 100644 index 0000000..5ad69d1 --- /dev/null +++ b/test/tests/common_tests/description.robot @@ -0,0 +1,95 @@ +*** 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}" == "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}" in ["pymysql", "pyodbc"] + 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}" == "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}" == "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}" in ["pymysql", "pyodbc"] + 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}" == "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 0000000..76ae94f --- /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/transaction.robot b/test/tests/common_tests/transaction.robot new file mode 100644 index 0000000..cac6723 --- /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/excel.robot b/test/tests/custom_db_tests/excel.robot new file mode 100644 index 0000000..f61b793 --- /dev/null +++ b/test/tests/custom_db_tests/excel.robot @@ -0,0 +1,245 @@ +*** Settings *** +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} ${CURDIR}/Test_Excel.xlsx +${DBPass} dummy +${DBPort} 80 +${DBUser} dummy + + +*** Test Cases *** +Create person table + ${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 + 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 + ${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'; + +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} ('id', , None, 255, 255, 0, True) + ${output} = Set Variable ${queryResults[1]} + 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) + ${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} ('id', , None, 255, 255, 0, True) + ${output} = Set Variable ${queryResults[1]} + 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 + ${output} = Query SELECT COUNT(*) FROM [person$]; + Log ${output} + Should Be Equal As Integers ${output}[0][0] 2 + +Verify Query - Row Count 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$]; \ 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 Integers ${output}[0][0] 1 + +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 + +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 + +Setup RO access to excel + Disconnect From Database + Connect To Database excel ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} + +Check If Exists In RODB - Franz Allan + Check If Exists In Database SELECT id FROM [person$] WHERE first_name = 'Franz Allan'; + +Check If Not Exists In RODB - Joe + Check If Not Exists In Database SELECT id FROM [person$] WHERE first_name = 'Joe'; + +Verify Row Count is 0 RODB + Row Count is 0 SELECT * FROM [person$] WHERE first_name = 'NotHere'; + +Verify Row Count is Equal to X RODB + Row Count is Equal to X SELECT id FROM [person$]; 4 + +Verify Row Count is Less Than X RODB + Row Count is Less Than X SELECT id FROM [person$]; 5 + +Verify Row Count is Greater Than X RODB + Row Count is Greater Than X SELECT * FROM [person$]; 1 + +Retrieve Row Count RODB + ${output} = Row Count SELECT id FROM [person$]; + Log ${output} + Should Be Equal As Strings ${output} 4 + +Retrieve records from person table RODB + ${output} = Execute SQL String SELECT * FROM [person$]; + Log ${output} + Should Be Equal As Strings ${output} None + +Verify person Description RODB + 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) + ${output} = Set Variable ${queryResults[1]} + 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) + ${NumColumns} = Get Length ${queryResults} + Should Be Equal As Integers ${NumColumns} 3 + +Verify foobar Description RODB + 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) + ${output} = Set Variable ${queryResults[1]} + 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 + ${output} = Query SELECT COUNT(*) FROM [person$]; + Log ${output} + Should Be Equal As Integers ${output}[0][0] 4 + +Verify Query - Row Count foobar table RODB + ${output} = Query SELECT COUNT(*) FROM [foobar$]; + Log ${output} + Should Be Equal As Integers ${output}[0][0] 1 + +Verify Query - Get results as a list of dictionaries RODB + ${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 RODB + ${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 + ${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 + ${output} = Query SELECT COUNT(*) FROM [foobar$]; + Log ${output} + 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} + +Drop person and foobar tables + ${output} = Execute SQL String DROP TABLE [person$],[foobar$] + Log ${output} + Should Be Equal As Strings ${output} None + + +*** Keywords *** +Setup testing excel + 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} From 220d604afe8d444c3baadcc5d8545407d5dd9241 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 15:47:16 +0200 Subject: [PATCH 029/266] [CI] Unified workflow for all tests --- .github/workflows/MySQL-tests.yml | 125 ----------------- .github/workflows/Oracle-tests.yml | 61 --------- .github/workflows/PostgreSQL-tests.yml | 71 ---------- .github/workflows/SQLite-tests.yml | 38 ------ .github/workflows/common_tests.yml | 179 +++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 295 deletions(-) delete mode 100644 .github/workflows/MySQL-tests.yml delete mode 100644 .github/workflows/Oracle-tests.yml delete mode 100644 .github/workflows/PostgreSQL-tests.yml delete mode 100644 .github/workflows/SQLite-tests.yml create mode 100644 .github/workflows/common_tests.yml diff --git a/.github/workflows/MySQL-tests.yml b/.github/workflows/MySQL-tests.yml deleted file mode 100644 index 7a3ae4e..0000000 --- a/.github/workflows/MySQL-tests.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: MySQL (pymysql, pyodbc) Tests - -on: [push, pull_request] - -env: - DB_DATABASE: my_db_test - DB_USER: root - DB_PASSWORD: root - DB_HOST: 127.0.0.1 - DB_PORT: 3306 - -jobs: - dbsetup: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - # py-db-module: ["pymysql", "pyodbc", "pymssql", "MySQLdb"] - include: - - db-name: PyMySQL - py-db-module: pymysql - pip-install: pymysql - - db-name: PyODBC - py-db-module: pyodbc - pip-install: pyodbc - - db-name: MySQLdb - py-db-module: MySQLdb - pip-install: mysqlclient - steps: - - uses: actions/checkout@v3 - - - name: Set up MySQL - run: | - sudo /etc/init.d/mysql start - mysql -e 'CREATE DATABASE ${{ matrix.py-db-module }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} - - - name: Verify MySQL Setup - run: | - mysql -e "SHOW DATABASES;" -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} - netstat -ano - - - name: Install ODBC driver for PostgreSQL - run: | - echo "*** apt-get install the driver" - sudo apt-get install --yes odbc-postgresql - echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' - ls -l /usr/lib/x86_64-linux-gnu/odbc || true - echo '*** add full paths to Postgres .so files in /etc/odbcinst.ini' - sudo sed -i 's|Driver=psqlodbca.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbca.so|g' /etc/odbcinst.ini - sudo sed -i 's|Driver=psqlodbcw.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so|g' /etc/odbcinst.ini - sudo sed -i 's|Setup=libodbcpsqlS.so|Setup=/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so|g' /etc/odbcinst.ini - - - name: Install ODBC driver for MySQL - run: | - cd "$RUNNER_TEMP" - echo "*** download driver zip file" - curl --silent --show-error --write-out "$CURL_OUTPUT_FORMAT" -O "https://www.mirrorservice.org/sites/ftp.mysql.com/Downloads/Connector-ODBC/8.0/${MYSQL_DRIVER}.tar.gz" - ls -l "${MYSQL_DRIVER}.tar.gz" - tar -xz -f "${MYSQL_DRIVER}.tar.gz" - echo "*** copy driver file to /usr/lib" - sudo cp -v "${MYSQL_DRIVER}/lib/libmyodbc8a.so" /usr/lib/x86_64-linux-gnu/odbc/ - sudo chmod a+r /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so - echo "*** create odbcinst.ini entry" - echo '[MySQL ODBC 8.0 ANSI Driver]' > mysql_odbcinst.ini - echo 'Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so' >> mysql_odbcinst.ini - echo 'UsageCount = 1' >> mysql_odbcinst.ini - echo 'Threading = 2' >> mysql_odbcinst.ini - sudo odbcinst -i -d -f mysql_odbcinst.ini - env: - CURL_OUTPUT_FORMAT: '%{http_code} %{filename_effective} %{size_download} %{time_total}\n' - MYSQL_DRIVER: mysql-connector-odbc-8.0.22-linux-glibc2.12-x86-64bit - - - name: Check ODBC setup - run: | - echo "*** odbcinst -j" - odbcinst -j - echo "*** cat /etc/odbcinst.ini" - cat /etc/odbcinst.ini - echo "*** cat /etc/odbc.ini" - cat /etc/odbc.ini - echo '*** ls -l /opt/microsoft/msodbcsql17/lib64' - ls -l /opt/microsoft/msodbcsql17/lib64 || true - echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' - ls -l /usr/lib/x86_64-linux-gnu/odbc || true - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.16' - - - name: Setup Python Packaging and Pip - run: | - python -m pip install --upgrade pip - pip install wheel - - - name: Setup Robot Framework - run: | - pip install robotframework - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install ${{ github.workspace }}/. - - - name: Setup Python DB module - run: | - pip install ${{ matrix.pip-install }} - - - name: Run Robot Framework tests using PyMySQL - if: matrix.py-db-module == 'pymysql' - working-directory: ./test - run: | - robot -d pymysql/ -v DBModule:${{ matrix.py-db-module }} -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/MySQL_DB_Tests.robot - - - name: Run Robot Framework tests using PyODBC - if: matrix.py-db-module == 'pyodbc' - working-directory: ./test - run: | - python3 -c "import pyodbc; print(pyodbc.drivers())" - robot --loglevel TRACE -d pyodbc/ -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:localhost -v DBPort:${{ env.DB_PORT }} -v DBCharset:utf8mb4 -v dbDriver:"{MySQL ODBC 8.0 ANSI Driver}" ${{ github.workspace }}/test/PyODBC_DB_Tests.robot - - - name: Upload Robot Logs - if: always() - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/ \ No newline at end of file diff --git a/.github/workflows/Oracle-tests.yml b/.github/workflows/Oracle-tests.yml deleted file mode 100644 index 29cc791..0000000 --- a/.github/workflows/Oracle-tests.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Oracle Tests - -on: [push, pull_request] - -env: - DB_DATABASE: FREEPDB1 - DB_USER: db_user - DB_PASSWORD: pass - DB_MODULE: oracledb - -jobs: - run-robotframework-tests-psycopg2: - runs-on: ubuntu-latest - strategy: - fail-fast: false - - services: - oracle: - image: gvenzl/oracle-free:latest - env: - ORACLE_PASSWORD: pass - APP_USER: db_user - APP_USER_PASSWORD: pass - ports: - - 1521:1521 - # Provide healthcheck script options for startup - options: >- - --health-cmd healthcheck.sh - --health-interval 10s - --health-timeout 5s - --health-retries 10 - - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install ${{ env.DB_MODULE }} - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Run Robot Framework tests using oracledb - working-directory: ./test - run: | - robot -T --xunit result.xml -d results/ -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} ${{ github.workspace }}/test/Oracle_Custom_Params_Tests.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ diff --git a/.github/workflows/PostgreSQL-tests.yml b/.github/workflows/PostgreSQL-tests.yml deleted file mode 100644 index 17a7413..0000000 --- a/.github/workflows/PostgreSQL-tests.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: PostgreSQL (psycopg2) Tests - -on: [push, pull_request] - -env: - DB_DATABASE: db - DB_USER: postgres - DB_PASSWORD: postgres - DB_HOST: postgres - DB_PORT: 5432 - DB_MODULE: psycopg2 - -jobs: - run-robotframework-tests-psycopg2: - runs-on: ubuntu-latest - strategy: - fail-fast: false - - services: - postgres: - image: postgres:11 - env: - POSTGRES_DB: db - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - '5432' - # needed because the postgres container does not provide a healthcheck - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install ${{ env.DB_MODULE }} - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Set DB options if in ACT - if: ${{ env.ACT }} - run: | - echo "DB_HOST=${{ env.ACT_DB_HOST }}" >> $GITHUB_ENV - echo "DB_PORT=${{ env.ACT_POSTGRES_DB_PORT }}" >> $GITHUB_ENV - - - name: Set DB options if not in ACT - if: ${{ !env.ACT }} - run: | - echo "DB_HOST=localhost" >> $GITHUB_ENV - echo "DB_PORT=${{ job.services.postgres.ports[5432] }}" >> $GITHUB_ENV - - - name: Run Robot Framework tests using psycopg2 - working-directory: ./test - run: | - robot -T --xunit result.xml -d results/ -v DBModule:${{ env.DB_MODULE }} -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/PostgreSQL_DB_Tests.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ diff --git a/.github/workflows/SQLite-tests.yml b/.github/workflows/SQLite-tests.yml deleted file mode 100644 index 61862ea..0000000 --- a/.github/workflows/SQLite-tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: SQLite (sqlite3) Tests - -on: [push, pull_request] - -jobs: - run-robotframework-tests-sqlite: - runs-on: ubuntu-latest - strategy: - fail-fast: false - - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Run Robot Framework tests using SQLite3 - working-directory: ./test - run: | - robot -T --xunit result.xml -d results/ ${{ github.workspace }}/test/SQLite3_DB_Tests.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml new file mode 100644 index 0000000..23ce87f --- /dev/null +++ b/.github/workflows/common_tests.yml @@ -0,0 +1,179 @@ +name: Common DB Tests + +on: [push, pull_request] + +env: + DB_NAME: db + DB_USER: db_user + DB_PASS: pass + DB_HOST: 127.0.0.1 + # port is set in the job + +jobs: + run-robotframework-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - job_name: PostgreSQL + module_mode: standard + py_db_module: psycopg2 + db_port: 5432 + - job_name: Oracle_Custom + module_mode: custom + py_db_module: oracledb + db_port: 1521 + - job_name: SQLite + module_mode: custom + py_db_module: sqlite3 + - job_name: IBM_DB2 + module_mode: standard + py_db_module: ibm_db_dbi + db_port: 50000 + - job_name: MySQL_pymysql + module_mode: standard + py_db_module: pymysql + db_port: 3306 + - job_name: MySQL_pyodbc + module_mode: standard + py_db_module: pyodbc + db_port: 3306 + + services: + postgres: + image: postgres:11 + env: + POSTGRES_DB: ${{ env.DB_NAME }} + POSTGRES_USER: ${{ env.DB_USER }} + POSTGRES_PASSWORD: ${{ env.DB_PASS }} + ports: + - '5432' + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + oracle: + image: gvenzl/oracle-free:latest + env: + ORACLE_PASSWORD: ${{ env.DB_PASS }} + ORACLE_DATABASE: ${{ env.DB_NAME }} + APP_USER: ${{ env.DB_USER }} + APP_USER_PASSWORD: ${{ env.DB_PASS }} + ports: + - 1521:1521 + # Provide healthcheck script options for startup + options: --health-cmd healthcheck.sh --health-interval 10s --health-timeout 5s --health-retries 10 + + mysql: + image: mysql + env: + MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }} + MYSQL_DATABASE: ${{ env.DB_NAME }} + MYSQL_USER: ${{ env.DB_USER }} + MYSQL_PASSWORD: ${{ env.DB_PASS }} + ports: + - 3306:3306 + + ibmdb2: + image: ibmcom/db2 + env: + LICENSE: accept + MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }} + DBNAME: ${{ env.DB_NAME }} + DB2INSTANCE: ${{ env.DB_USER }} + DB2INST1_PASSWORD: ${{ env.DB_PASS }} + ports: + - 50000:50000 + + steps: + - name: Install ODBC driver for PostgreSQL + if: matrix.py_db_module == 'pyodbc' + run: | + echo "*** apt-get install the driver" + sudo apt-get install --yes odbc-postgresql + echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' + ls -l /usr/lib/x86_64-linux-gnu/odbc || true + echo '*** add full paths to Postgres .so files in /etc/odbcinst.ini' + sudo sed -i 's|Driver=psqlodbca.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbca.so|g' /etc/odbcinst.ini + sudo sed -i 's|Driver=psqlodbcw.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so|g' /etc/odbcinst.ini + sudo sed -i 's|Setup=libodbcpsqlS.so|Setup=/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so|g' /etc/odbcinst.ini + + - name: Install ODBC driver for MySQL + if: matrix.py_db_module == 'pyodbc' + run: | + cd "$RUNNER_TEMP" + echo "*** download driver zip file" + curl --silent --show-error --write-out "$CURL_OUTPUT_FORMAT" -O "https://www.mirrorservice.org/sites/ftp.mysql.com/Downloads/Connector-ODBC/8.0/${MYSQL_DRIVER}.tar.gz" + ls -l "${MYSQL_DRIVER}.tar.gz" + tar -xz -f "${MYSQL_DRIVER}.tar.gz" + echo "*** copy driver file to /usr/lib" + sudo cp -v "${MYSQL_DRIVER}/lib/libmyodbc8a.so" /usr/lib/x86_64-linux-gnu/odbc/ + sudo chmod a+r /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so + echo "*** create odbcinst.ini entry" + echo '[MySQL ODBC 8.0 ANSI Driver]' > mysql_odbcinst.ini + echo 'Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so' >> mysql_odbcinst.ini + echo 'UsageCount = 1' >> mysql_odbcinst.ini + echo 'Threading = 2' >> mysql_odbcinst.ini + sudo odbcinst -i -d -f mysql_odbcinst.ini + env: + CURL_OUTPUT_FORMAT: '%{http_code} %{filename_effective} %{size_download} %{time_total}\n' + MYSQL_DRIVER: mysql-connector-odbc-8.0.22-linux-glibc2.12-x86-64bit + + - name: Check ODBC setup + run: | + echo "*** odbcinst -j" + odbcinst -j + echo "*** cat /etc/odbcinst.ini" + cat /etc/odbcinst.ini + echo "*** cat /etc/odbc.ini" + cat /etc/odbc.ini + echo '*** ls -l /opt/microsoft/msodbcsql17/lib64' + ls -l /opt/microsoft/msodbcsql17/lib64 || true + echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' + ls -l /usr/lib/x86_64-linux-gnu/odbc || true + + - name: Check out repository code + uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Setup Python DB module + run: | + pip install ${{ matrix.py_db_module }} + + - name: Run Robot Framework tests using ${{ matrix.job_name }} + working-directory: ./test + run: >- + robot + -d results + --xunit result.xml + --loglevel DEBUG:INFO + --output output_${{ matrix.job_name }}.xml + --log log_${{ matrix.job_name }}.html + --report report_${{ matrix.job_name }}.html + -v DB_MODULE_MODE:${{ matrix.module_mode }} + -v DB_MODULE:${{ matrix.py_db_module }} + -v DB_NAME:${{ env.DB_NAME }} + -v DB_USER:${{ env.DB_USER }} + -v DB_PASS:${{ env.DB_PASS }} + -v DB_HOST:${{ env.DB_HOST }} + -v DB_PORT:${{ matrix.db_port }} + tests/common_tests + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/results/ From 5e1ca4d82b0e5771aadac2099a2d92726e822835 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 15:47:35 +0200 Subject: [PATCH 030/266] Tasks and launch configs for local debugging with VS Code --- .vscode/launch.json | 33 +++++++++++++++++++++++---------- .vscode/tasks.json | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 03efdff..2f06040 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,23 +6,36 @@ "configurations": [ { "type": "robotframework-lsp", - "name": "Launch all tests in Dryrun mode", + "name": "Launch .robot file for DB", "request": "launch", "cwd": "${workspaceFolder}", - "target": "${workspaceFolder}/test", + "target": "${file}", "terminal": "integrated", "args": [ "--outputdir", "logs", "--loglevel", "DEBUG:INFO", - "--dryrun" + "-v GLOBAL_DB_SELECTOR:${input:DB}", ], }, { "type": "robotframework-lsp", - "name": "Launch .robot file in Dryrun mode", + "name": "Run all common tests for DB", "request": "launch", "cwd": "${workspaceFolder}", - "target": "${file}", + "target": "${workspaceFolder}/test/tests/common_tests", + "terminal": "integrated", + "args": [ + "--outputdir", "logs", + "--loglevel", "DEBUG:INFO", + "-v GLOBAL_DB_SELECTOR:${input:DB}", + ], + }, + { + "type": "robotframework-lsp", + "name": "dryrun all tests", + "request": "launch", + "cwd": "${workspaceFolder}", + "target": "${workspaceFolder}/test", "terminal": "integrated", "args": [ "--outputdir", "logs", @@ -32,7 +45,7 @@ }, { "type": "robotframework-lsp", - "name": "Launch .robot file for DB", + "name": "dryrun .robot file", "request": "launch", "cwd": "${workspaceFolder}", "target": "${file}", @@ -40,7 +53,7 @@ "args": [ "--outputdir", "logs", "--loglevel", "DEBUG:INFO", - "-v DB_MODULE:${input:DB_MODULE}", + "--dryrun" ], }, { @@ -59,10 +72,10 @@ "inputs": [ { "type": "pickString", - "id": "DB_MODULE", + "id": "DB", "description": "Database to run the tests for", - "options": ["oracledb"], - "default": "oracledb" + "options": ["PostgreSQL", "Oracle_Custom", "SQLite", "IBM_DB2", "Teradata", "MySQL_pymysql", "MySQL_pyodbc", "MSSQL", "Excel", "Excel_RW"], + "default": "PostgreSQL" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..476b056 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,35 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "shell", + "label": "Run Oracle DB in Docker", + "command": "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", + "problemMatcher": [] + }, + { + "type": "shell", + "label": "Run PostreSQL DB in Docker", + "command": "docker run --rm --name postgres -e POSTGRES_USER=db_user -e POSTGRES_PASSWORD=pass -e POSTGRES_DB=db -p 5432:5432 -d postgres", + "problemMatcher": [] + }, + { + "type": "shell", + "label": "Run IBM DB2 in Docker", + "command": "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", + "problemMatcher": [] + }, + { + "type": "shell", + "label": "Run MySQL in Docker", + "command": "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", + "problemMatcher": [] + }, + { + "type": "shell", + "label": "Run MS SQL in Docker (don't forget the DB init!)", + "command": "docker run --rm --name mssql -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD=MyPass1234! -p 1433:1433 -d mcr.microsoft.com/mssql/server", + "problemMatcher": [] + }, + ] + } \ No newline at end of file From c55e8f65da88c40dcc0d185a253118a7add128d9 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 16:01:19 +0200 Subject: [PATCH 031/266] [CI] fixed installing proper python modules --- .github/workflows/common_tests.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 23ce87f..aa8f8f8 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -19,25 +19,31 @@ jobs: - job_name: PostgreSQL module_mode: standard py_db_module: psycopg2 + pip_install: psycopg2 db_port: 5432 - job_name: Oracle_Custom module_mode: custom py_db_module: oracledb + pip_install: oracledb db_port: 1521 - job_name: SQLite module_mode: custom py_db_module: sqlite3 + pip_install: none - job_name: IBM_DB2 module_mode: standard py_db_module: ibm_db_dbi + pip_install: ibm_db_dbi db_port: 50000 - job_name: MySQL_pymysql module_mode: standard py_db_module: pymysql + pip_install: pymysql db_port: 3306 - job_name: MySQL_pyodbc module_mode: standard py_db_module: pyodbc + pip_install: pyodbc db_port: 3306 services: @@ -149,8 +155,10 @@ jobs: pip install -e ${{ github.workspace }} - name: Setup Python DB module + if: matrix.pip_install != 'none' + run: | - pip install ${{ matrix.py_db_module }} + pip install ${{ matrix.pip_install }} - name: Run Robot Framework tests using ${{ matrix.job_name }} working-directory: ./test From 96287563e5fad808a0f380cea3bcbd76fa2b8eea Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 16:05:55 +0200 Subject: [PATCH 032/266] [CI] explicitely set port for postgres container --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index aa8f8f8..7a51e77 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -54,7 +54,7 @@ jobs: POSTGRES_USER: ${{ env.DB_USER }} POSTGRES_PASSWORD: ${{ env.DB_PASS }} ports: - - '5432' + - 5432:5432 # needed because the postgres container does not provide a healthcheck options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 From 4b83ae75f44112348880563445f49ba109fac423 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 16:14:08 +0200 Subject: [PATCH 033/266] [CI] proper python module name for ibmdb --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 7a51e77..285d347 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -33,7 +33,7 @@ jobs: - job_name: IBM_DB2 module_mode: standard py_db_module: ibm_db_dbi - pip_install: ibm_db_dbi + pip_install: ibm_db_db db_port: 50000 - job_name: MySQL_pymysql module_mode: standard From 17eefd9642f3bf33591d6ead9dd4448ca6b0a13f Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Tue, 20 Jun 2023 17:18:34 +0200 Subject: [PATCH 034/266] [CI] Typo in ibm db module name --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 285d347..a17bf57 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -33,7 +33,7 @@ jobs: - job_name: IBM_DB2 module_mode: standard py_db_module: ibm_db_dbi - pip_install: ibm_db_db + pip_install: ibm_db db_port: 50000 - job_name: MySQL_pymysql module_mode: standard From 21f7f9621c3b52c7c0d9da93d21dc2de5462c4cf Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 20:31:42 +0200 Subject: [PATCH 035/266] [CI] shorter job name --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 285d347..af22acd 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -160,7 +160,7 @@ jobs: run: | pip install ${{ matrix.pip_install }} - - name: Run Robot Framework tests using ${{ matrix.job_name }} + - name: Tests for ${{ matrix.job_name }} working-directory: ./test run: >- robot From 8494eb7071f019e28cd1a96667f7bfa5315dff68 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 20:39:41 +0200 Subject: [PATCH 036/266] [CI] shorter job name - not only in steps --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index c33d958..78a23a1 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -10,7 +10,7 @@ env: # port is set in the job jobs: - run-robotframework-tests: + tests: runs-on: ubuntu-latest strategy: fail-fast: false From 07a8c305f04e374291435284c29bcaa6e9ad00c1 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 21:50:21 +0200 Subject: [PATCH 037/266] [CI] installing pymssql with RSA dependency shoulld solve the connection error --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 78a23a1..61eec71 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -38,7 +38,7 @@ jobs: - job_name: MySQL_pymysql module_mode: standard py_db_module: pymysql - pip_install: pymysql + pip_install: pymysql[rsa] db_port: 3306 - job_name: MySQL_pyodbc module_mode: standard From 49c16185adbc6ebef971953561d9f1f9886107a5 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 22:38:09 +0200 Subject: [PATCH 038/266] [CI] set driver and charset for pyodbc tests --- .github/workflows/common_tests.yml | 6 ++++++ test/resources/common.resource | 34 ++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 61eec71..c061f07 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -8,6 +8,10 @@ env: DB_PASS: pass DB_HOST: 127.0.0.1 # port is set in the job + + # options for pyodbc only + DB_CHARSET: utf8mb4 + DB_DRIVER: "{MySQL ODBC 8.0 ANSI Driver}" jobs: tests: @@ -177,6 +181,8 @@ jobs: -v DB_PASS:${{ env.DB_PASS }} -v DB_HOST:${{ env.DB_HOST }} -v DB_PORT:${{ matrix.db_port }} + -v DB_CHARSET: ${{env.DB_CHARSET}} + -v DB_DRIVER: ${{env.DB_DRIVER}} tests/common_tests - name: Upload Robot Logs diff --git a/test/resources/common.resource b/test/resources/common.resource index ef37741..e85a7e4 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -16,6 +16,10 @@ ${DB_PASS} pass ${DB_PORT} 5432 ${DB_USER} db_user +# used for MySQL via PyODBC only +${DB_CHARSET} ${None} +${DB_DRIVER} ODBC Driver 18 for SQL Server + *** Keywords *** Connect To DB @@ -29,14 +33,26 @@ Connect To DB ${Connection String}= Build Connection String Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} END - ELSE IF "${DB_MODULE_MODE}" == "standard" - Connect To Database - ... ${DB_MODULE} - ... ${DB_NAME} - ... ${DB_USER} - ... ${DB_PASS} - ... ${DB_HOST} - ... ${DB_PORT} + ELSE IF "${DB_MODULE_MODE}" == "standard" + IF "${DB_MODULE}" == "pyodbc" + Connect To Database + ... ${DB_MODULE} + ... ${DB_NAME} + ... ${DB_USER} + ... ${DB_PASS} + ... ${DB_HOST} + ... ${DB_PORT} + ... ${DB_CHARSET} + ... ${DB_DRIVER} + ELSE + Connect To Database + ... ${DB_MODULE} + ... ${DB_NAME} + ... ${DB_USER} + ... ${DB_PASS} + ... ${DB_HOST} + ... ${DB_PORT} + END ELSE Fail Unexpected mode - ${DB_MODULE_MODE} END @@ -51,7 +67,7 @@ Build Connection String ${Result}= Set Variable ... postgresql://${DB_USER}:${DB_PASS}@${DB_HOST}:${DB_PORT}/${DB_NAME} ELSE - Fail Don't know how to build a connection string for '${DB_MODULE}'! + Skip Don't know how to build a connection string for '${DB_MODULE}' END RETURN ${Result} From d273feb5b8e81081f84cb984a94689efa02471ff Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 22:38:39 +0200 Subject: [PATCH 039/266] [CI] start services depending on the job --- .github/workflows/common_tests.yml | 40 +++++++++++------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index c061f07..44372b4 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -25,11 +25,15 @@ jobs: py_db_module: psycopg2 pip_install: psycopg2 db_port: 5432 + container_image: postgres + container_options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - job_name: Oracle_Custom module_mode: custom py_db_module: oracledb pip_install: oracledb db_port: 1521 + container_image: gvenzl/oracle-free + container_options: --health-cmd healthcheck.sh --health-interval 10s --health-timeout 5s --health-retries 10 - job_name: SQLite module_mode: custom py_db_module: sqlite3 @@ -38,62 +42,46 @@ jobs: module_mode: standard py_db_module: ibm_db_dbi pip_install: ibm_db + container_image: ibmcom/db2 + # container_options: !!!!!!!!!!! db_port: 50000 - job_name: MySQL_pymysql module_mode: standard py_db_module: pymysql pip_install: pymysql[rsa] db_port: 3306 + container_image: mysql + # container_options: !!!!!!!!!!!!! - job_name: MySQL_pyodbc module_mode: standard py_db_module: pyodbc pip_install: pyodbc db_port: 3306 + container_image: mysql + # container_options: !!!!!!!!!!!!!!!!!!!!! services: - postgres: - image: postgres:11 + db_container: + image: ${{ matrix.container_image }} env: POSTGRES_DB: ${{ env.DB_NAME }} POSTGRES_USER: ${{ env.DB_USER }} POSTGRES_PASSWORD: ${{ env.DB_PASS }} - ports: - - 5432:5432 - # needed because the postgres container does not provide a healthcheck - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - - oracle: - image: gvenzl/oracle-free:latest - env: ORACLE_PASSWORD: ${{ env.DB_PASS }} ORACLE_DATABASE: ${{ env.DB_NAME }} APP_USER: ${{ env.DB_USER }} APP_USER_PASSWORD: ${{ env.DB_PASS }} - ports: - - 1521:1521 - # Provide healthcheck script options for startup - options: --health-cmd healthcheck.sh --health-interval 10s --health-timeout 5s --health-retries 10 - - mysql: - image: mysql - env: MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }} MYSQL_DATABASE: ${{ env.DB_NAME }} MYSQL_USER: ${{ env.DB_USER }} MYSQL_PASSWORD: ${{ env.DB_PASS }} - ports: - - 3306:3306 - - ibmdb2: - image: ibmcom/db2 - env: LICENSE: accept - MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }} DBNAME: ${{ env.DB_NAME }} DB2INSTANCE: ${{ env.DB_USER }} DB2INST1_PASSWORD: ${{ env.DB_PASS }} ports: - - 50000:50000 + - ${{ matrix.db_port }}:${{ matrix.db_port }} + options: ${{matrix.container_options}} steps: - name: Install ODBC driver for PostgreSQL From 18098a91308aa9eab142ff3f55d9106e3f229c82 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 22:42:21 +0200 Subject: [PATCH 040/266] [CI] no spaces in robot command and no empty container options --- .github/workflows/common_tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 44372b4..6dda3bb 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -43,7 +43,7 @@ jobs: py_db_module: ibm_db_dbi pip_install: ibm_db container_image: ibmcom/db2 - # container_options: !!!!!!!!!!! + container_options: none db_port: 50000 - job_name: MySQL_pymysql module_mode: standard @@ -51,14 +51,14 @@ jobs: pip_install: pymysql[rsa] db_port: 3306 container_image: mysql - # container_options: !!!!!!!!!!!!! + container_options: none - job_name: MySQL_pyodbc module_mode: standard py_db_module: pyodbc pip_install: pyodbc db_port: 3306 container_image: mysql - # container_options: !!!!!!!!!!!!!!!!!!!!! + container_options: none services: db_container: @@ -169,8 +169,8 @@ jobs: -v DB_PASS:${{ env.DB_PASS }} -v DB_HOST:${{ env.DB_HOST }} -v DB_PORT:${{ matrix.db_port }} - -v DB_CHARSET: ${{env.DB_CHARSET}} - -v DB_DRIVER: ${{env.DB_DRIVER}} + -v DB_CHARSET:${{env.DB_CHARSET}} + -v DB_DRIVER:${{env.DB_DRIVER}} tests/common_tests - name: Upload Robot Logs From 7adfde0fcfee63d906056b66c715ac84f727231b Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 22:51:44 +0200 Subject: [PATCH 041/266] [CI] quotes around driver rf variable --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 6dda3bb..9197fdb 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -170,7 +170,7 @@ jobs: -v DB_HOST:${{ env.DB_HOST }} -v DB_PORT:${{ matrix.db_port }} -v DB_CHARSET:${{env.DB_CHARSET}} - -v DB_DRIVER:${{env.DB_DRIVER}} + -v DB_DRIVER:"${{env.DB_DRIVER}}" tests/common_tests - name: Upload Robot Logs From 01fd0e706f4aea6b5eee6224f52050fea70acd8d Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 22:55:57 +0200 Subject: [PATCH 042/266] [CI] Revert to start ALL containers - didn't work to start them on demand --- .github/workflows/common_tests.yml | 40 +++++++++++++++++++----------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 9197fdb..ab794cf 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -25,15 +25,11 @@ jobs: py_db_module: psycopg2 pip_install: psycopg2 db_port: 5432 - container_image: postgres - container_options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - job_name: Oracle_Custom module_mode: custom py_db_module: oracledb pip_install: oracledb db_port: 1521 - container_image: gvenzl/oracle-free - container_options: --health-cmd healthcheck.sh --health-interval 10s --health-timeout 5s --health-retries 10 - job_name: SQLite module_mode: custom py_db_module: sqlite3 @@ -42,46 +38,62 @@ jobs: module_mode: standard py_db_module: ibm_db_dbi pip_install: ibm_db - container_image: ibmcom/db2 - container_options: none db_port: 50000 - job_name: MySQL_pymysql module_mode: standard py_db_module: pymysql pip_install: pymysql[rsa] db_port: 3306 - container_image: mysql - container_options: none - job_name: MySQL_pyodbc module_mode: standard py_db_module: pyodbc pip_install: pyodbc db_port: 3306 - container_image: mysql - container_options: none services: - db_container: - image: ${{ matrix.container_image }} + postgres: + image: postgres:11 env: POSTGRES_DB: ${{ env.DB_NAME }} POSTGRES_USER: ${{ env.DB_USER }} POSTGRES_PASSWORD: ${{ env.DB_PASS }} + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + oracle: + image: gvenzl/oracle-free:latest + env: ORACLE_PASSWORD: ${{ env.DB_PASS }} ORACLE_DATABASE: ${{ env.DB_NAME }} APP_USER: ${{ env.DB_USER }} APP_USER_PASSWORD: ${{ env.DB_PASS }} + ports: + - 1521:1521 + # Provide healthcheck script options for startup + options: --health-cmd healthcheck.sh --health-interval 10s --health-timeout 5s --health-retries 10 + + mysql: + image: mysql + env: MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }} MYSQL_DATABASE: ${{ env.DB_NAME }} MYSQL_USER: ${{ env.DB_USER }} MYSQL_PASSWORD: ${{ env.DB_PASS }} + ports: + - 3306:3306 + + ibmdb2: + image: ibmcom/db2 + env: LICENSE: accept + MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }} DBNAME: ${{ env.DB_NAME }} DB2INSTANCE: ${{ env.DB_USER }} DB2INST1_PASSWORD: ${{ env.DB_PASS }} ports: - - ${{ matrix.db_port }}:${{ matrix.db_port }} - options: ${{matrix.container_options}} + - 50000:50000 steps: - name: Install ODBC driver for PostgreSQL From c1075f4c03f43b100e04c37315b94cf6632de5c7 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 20 Jun 2023 23:07:04 +0200 Subject: [PATCH 043/266] [CI] Workflow for Excel tests --- .github/workflows/excel_tests.yml | 49 +++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 50 insertions(+) create mode 100644 .github/workflows/excel_tests.yml diff --git a/.github/workflows/excel_tests.yml b/.github/workflows/excel_tests.yml new file mode 100644 index 0000000..efb5185 --- /dev/null +++ b/.github/workflows/excel_tests.yml @@ -0,0 +1,49 @@ +name: Excel DB Tests + +on: [push, pull_request] + +jobs: + tests_excel: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Setup Python DB module + run: | + pip install pyodbc + + - name: Tests for ${{ matrix.job_name }} + working-directory: ./test + run: >- + robot + -d results + --xunit result.xml + --loglevel DEBUG:INFO + --output output_${{ matrix.job_name }}.xml + --log log_${{ matrix.job_name }}.html + --report report_${{ matrix.job_name }}.html + tests/custom_db_tests/excel.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/results/ diff --git a/requirements.txt b/requirements.txt index cb708a2..059a6ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ #PyMySQL==0.7.4 #psycopg2==2.6.1 robotframework>=3.0 +robotframework-excellib \ No newline at end of file From 3085d7de7cc2ab255e501f44e4aa874d2c89aef0 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 10:07:33 +0200 Subject: [PATCH 044/266] [CI] wait for IBM DB2 to be up and running --- .github/workflows/common_tests.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index ab794cf..f86e7f5 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -94,6 +94,7 @@ jobs: DB2INST1_PASSWORD: ${{ env.DB_PASS }} ports: - 50000:50000 + options: --name mydb2 steps: - name: Install ODBC driver for PostgreSQL @@ -164,6 +165,15 @@ jobs: run: | pip install ${{ matrix.pip_install }} + - name: Wait for IBM DB2 to be ready + if: matrix.job_name == 'IBM_DB2' + run: | + while ! docker logs mydb2 | grep -q "Setup has completed."; + do + sleep 1 + echo "still waiting for the 'completed' message in the container logs..." + done + - name: Tests for ${{ matrix.job_name }} working-directory: ./test run: >- From fbbca36e2de55dd4300599e6717e0342f3859fe8 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 10:21:27 +0200 Subject: [PATCH 045/266] [CI] trying to fix IBMDB2 container issue with mounting --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index f86e7f5..236842a 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -94,7 +94,7 @@ jobs: DB2INST1_PASSWORD: ${{ env.DB_PASS }} ports: - 50000:50000 - options: --name mydb2 + options: --name mydb2 -v ${{ github.workspace }}/test/myibmdb:/database steps: - name: Install ODBC driver for PostgreSQL From 3a6a8ae08249302b290d24cde67ac55de282038b Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:01:01 +0200 Subject: [PATCH 046/266] description command - different response from mysql via pyodbc --- test/tests/common_tests/description.robot | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/tests/common_tests/description.robot b/test/tests/common_tests/description.robot index 5ad69d1..c1767a4 100644 --- a/test/tests/common_tests/description.robot +++ b/test/tests/common_tests/description.robot @@ -44,10 +44,14 @@ Verify Person Description 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}" in ["pymysql", "pyodbc"] + 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', 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}" == "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) @@ -83,9 +87,12 @@ Verify Foobar Description 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}" in ["pymysql", "pyodbc"] + 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', 1, None, None, None, None, None) 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) From 0cc76b5ef072eff46e5900f28c483ac48a8a7cf4 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:15:39 +0200 Subject: [PATCH 047/266] [CI] trying to fix the path to ibm db --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 236842a..9130ea6 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -94,7 +94,7 @@ jobs: DB2INST1_PASSWORD: ${{ env.DB_PASS }} ports: - 50000:50000 - options: --name mydb2 -v ${{ github.workspace }}/test/myibmdb:/database + options: --name mydb2 -v ./myibmdb:/database steps: - name: Install ODBC driver for PostgreSQL From b874929cfd2a290839390053bd5ac3b811d63661 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:21:34 +0200 Subject: [PATCH 048/266] [CI] trying to fix the path to ibm db --- .github/workflows/common_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 9130ea6..ed52ff3 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -94,7 +94,7 @@ jobs: DB2INST1_PASSWORD: ${{ env.DB_PASS }} ports: - 50000:50000 - options: --name mydb2 -v ./myibmdb:/database + options: --name mydb2 -v myibmdb:/database steps: - name: Install ODBC driver for PostgreSQL From 10e814a9cda30defc32ccc4360d6054faebbf1cb Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:24:56 +0200 Subject: [PATCH 049/266] [CI] remove excel tests workflow as it requires odbc driver --- .github/workflows/excel_tests.yml | 49 ------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 .github/workflows/excel_tests.yml diff --git a/.github/workflows/excel_tests.yml b/.github/workflows/excel_tests.yml deleted file mode 100644 index efb5185..0000000 --- a/.github/workflows/excel_tests.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Excel DB Tests - -on: [push, pull_request] - -jobs: - tests_excel: - runs-on: ubuntu-latest - strategy: - fail-fast: false - - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Setup Python DB module - run: | - pip install pyodbc - - - name: Tests for ${{ matrix.job_name }} - working-directory: ./test - run: >- - robot - -d results - --xunit result.xml - --loglevel DEBUG:INFO - --output output_${{ matrix.job_name }}.xml - --log log_${{ matrix.job_name }}.html - --report report_${{ matrix.job_name }}.html - tests/custom_db_tests/excel.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ From 232663e678b6168d740f61924e866f55196ec2c9 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:30:01 +0200 Subject: [PATCH 050/266] Test "Connect Using Custom Params" --- test/tests/common_tests/custom_connection.robot | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index d235c20..f8efdd1 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -8,7 +8,7 @@ Test Teardown Disconnect From Database *** Variables *** -${CONNECTION_STRING} ${EMPTY} # the variable is set dynamically depending on the currend DB module +${CONNECTION_STRING} ${EMPTY} # the variable is set dynamically depending on the current DB module *** Test Cases *** @@ -19,4 +19,12 @@ Connect Using Custom Connection String Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} Connect Using Custom Params - Fail implement me! \ No newline at end of file + ${Params}= Catenate + ... database='${DB_NAME}', + ... user='${DB_USER}', + ... password='${DB_USER}', + ... host='${DB_HOST}', + ... port=${DB_PORT} + Connect To Database Using Custom Params + ... ${DB_MODULE} + ... ${Params} From 60b11d01008faaa8487351ea518887ba991d4c87 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:37:36 +0200 Subject: [PATCH 051/266] typo in param name --- test/tests/common_tests/custom_connection.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index f8efdd1..794e3d2 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -22,7 +22,7 @@ Connect Using Custom Params ${Params}= Catenate ... database='${DB_NAME}', ... user='${DB_USER}', - ... password='${DB_USER}', + ... password='${DB_PASS}', ... host='${DB_HOST}', ... port=${DB_PORT} Connect To Database Using Custom Params From f20d509f1443610c550dce93db6ec2f63c979411 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:39:31 +0200 Subject: [PATCH 052/266] [CI] Remove IBM DB2 tests - docker container is causing problems --- .github/workflows/common_tests.yml | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index ed52ff3..1a4fe3c 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -34,11 +34,6 @@ jobs: module_mode: custom py_db_module: sqlite3 pip_install: none - - job_name: IBM_DB2 - module_mode: standard - py_db_module: ibm_db_dbi - pip_install: ibm_db - db_port: 50000 - job_name: MySQL_pymysql module_mode: standard py_db_module: pymysql @@ -83,18 +78,6 @@ jobs: MYSQL_PASSWORD: ${{ env.DB_PASS }} ports: - 3306:3306 - - ibmdb2: - image: ibmcom/db2 - env: - LICENSE: accept - MYSQL_ROOT_PASSWORD: ${{ env.DB_PASS }} - DBNAME: ${{ env.DB_NAME }} - DB2INSTANCE: ${{ env.DB_USER }} - DB2INST1_PASSWORD: ${{ env.DB_PASS }} - ports: - - 50000:50000 - options: --name mydb2 -v myibmdb:/database steps: - name: Install ODBC driver for PostgreSQL @@ -164,16 +147,7 @@ jobs: run: | pip install ${{ matrix.pip_install }} - - - name: Wait for IBM DB2 to be ready - if: matrix.job_name == 'IBM_DB2' - run: | - while ! docker logs mydb2 | grep -q "Setup has completed."; - do - sleep 1 - echo "still waiting for the 'completed' message in the container logs..." - done - + - name: Tests for ${{ matrix.job_name }} working-directory: ./test run: >- From 074e546ae8d128465595b5cb006c6ff29c31ec9b Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:48:41 +0200 Subject: [PATCH 053/266] fix description tests for MySQL via pyodbc --- test/tests/common_tests/description.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tests/common_tests/description.robot b/test/tests/common_tests/description.robot index c1767a4..06f7527 100644 --- a/test/tests/common_tests/description.robot +++ b/test/tests/common_tests/description.robot @@ -50,8 +50,8 @@ Verify Person Description 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', 253, None, 80, 80, 0, True) - Should Be Equal As Strings ${queryResults}[2] ('LAST_NAME', 253, None, 80, 80, 0, True) + 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) @@ -92,7 +92,7 @@ Verify Foobar Description 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', 1, None, None, None, None, None) + 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) From 7cdb4d258b5d9f2c7eb329cc63a9249e71d86344 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:53:51 +0200 Subject: [PATCH 054/266] Fix test 'Using Custom Params' for oracledb --- .../common_tests/custom_connection.robot | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index 794e3d2..f94cfba 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -19,12 +19,19 @@ Connect Using Custom Connection String Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} Connect Using Custom Params - ${Params}= Catenate - ... database='${DB_NAME}', - ... user='${DB_USER}', - ... password='${DB_PASS}', - ... host='${DB_HOST}', - ... port=${DB_PORT} + IF "${DB_MODULE}" == "oracledb" + ${Params}= Catenate + ... user='${DB_USER}', + ... password='${DB_PASS}', + ... dsn='${DB_HOST}:${DB_PORT}/${DB_NAME}' + 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} From 74a36b0634a3c8e474812895a418ef5173a2523f Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 13:57:13 +0200 Subject: [PATCH 055/266] [CI] Fake port to avoid sqlite3 tests failing --- .github/workflows/common_tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index 1a4fe3c..e9715c6 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -34,6 +34,7 @@ jobs: module_mode: custom py_db_module: sqlite3 pip_install: none + db_port: 0000 - job_name: MySQL_pymysql module_mode: standard py_db_module: pymysql From 4ae8e4381059e1d845cfa9c17d46772779a845ab Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 14:01:11 +0200 Subject: [PATCH 056/266] Try fixing 'custom params' test for pyodbc setting the driver --- test/tests/common_tests/custom_connection.robot | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index f94cfba..72fd581 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -24,6 +24,15 @@ Connect Using Custom Params ... 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 ${Params}= Catenate ... database='${DB_NAME}', From 78ef687fa3f266600dd0de9847f7f0fc2bc83d31 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 14:01:33 +0200 Subject: [PATCH 057/266] Update status of tests/workflows --- .github/workflows/README.rst | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/README.rst b/.github/workflows/README.rst index 0d38f53..8db0654 100644 --- a/.github/workflows/README.rst +++ b/.github/workflows/README.rst @@ -30,18 +30,28 @@ chart is intended to keep track of those implemented and resources around them. ================================== =========== ========================== ======================================= Database Systems module Status Workflow ================================== =========== ========================== ======================================= -MySQL pymysql Completed MySQL-tests.yml -\ pyodbc Completed MySQL-tests.yml -PostgreSQL psycopg2 Completed PostgreSQL-tests.yml +MySQL pymysql Completed common_tests.yml +\ pyodbc Completed common_tests.yml +PostgreSQL psycopg2 Completed common_tests.yml \ psycopg3 Not Yet Implemented \ pyodbc Not Yet Implemented -SQLite sqlite3 Completed SQLite-tests.yml -Oracle - "custom params" oracledb Workflow is done, Oracle-tests.yml +SQLite sqlite3 Completed common_tests.yml +Oracle - "custom params" oracledb Workflow is done, common_tests.yml but some tests are failing bugs have to be fixed in the library, tests are to be checked and probably extended +Teradata Teradata Can be tested locally only, local only + as it requires a VM +Excel pyodbc Currentyl local tests only, local only + as I wasn't able to install + the ODBC driver for Excel + in the container +IBM DB2 ibmdb Currently local tests only, local only + as I wasn't able to get + the container working in + the workflow ================================== =========== ========================== ======================================= From 9a8a4477b5afd9d61118d04a21b008d660fc3f2a Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 14:07:11 +0200 Subject: [PATCH 058/266] Forgot commas --- test/tests/common_tests/custom_connection.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index 72fd581..c0e5bdb 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -26,8 +26,8 @@ Connect Using Custom Params ... dsn='${DB_HOST}:${DB_PORT}/${DB_NAME}' ELSE IF "${DB_MODULE}" == "pyodbc" ${Params}= Catenate - ... driver='${DB_DRIVER}' - ... charset='${DB_CHARSET}' + ... driver='${DB_DRIVER}', + ... charset='${DB_CHARSET}', ... database='${DB_NAME}', ... user='${DB_USER}', ... password='${DB_PASS}', From ac12d6ce5728ddb16be16d12deacd407eb8de2f0 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 14:10:18 +0200 Subject: [PATCH 059/266] Fix 'custom params' test for sqlite3 --- test/tests/common_tests/custom_connection.robot | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index c0e5bdb..ab974e5 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -33,6 +33,10 @@ Connect Using Custom Params ... password='${DB_PASS}', ... host='${DB_HOST}', ... port=${DB_PORT} + ELSE IF "${DB_MODULE}" == "sqlite3" + ${Params}= Catenate + ... database="./${DBName}.db", + ... isolation_level=None ELSE ${Params}= Catenate ... database='${DB_NAME}', From 583fe9751cc55e3510f679b23900ecbcee9c555e Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 14:15:11 +0200 Subject: [PATCH 060/266] fix #174 - remove trailing semicolons in the sql statement --- src/DatabaseLibrary/query.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index c99e6c2..6300a60 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -368,4 +368,5 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): self._dbconnection.rollback() def __execute_sql(self, cur, sqlStatement): + sqlStatement = sqlStatement.rstrip(";") return cur.execute(sqlStatement) From 5f8f9ba6092460bef898dde83428c790dea5d948 Mon Sep 17 00:00:00 2001 From: amochin Date: Wed, 21 Jun 2023 21:08:10 +0200 Subject: [PATCH 061/266] #162 Tests for custom assertion messages in new structure --- .../assertion_custom_messages.robot | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 test/tests/common_tests/assertion_custom_messages.robot diff --git a/test/tests/common_tests/assertion_custom_messages.robot b/test/tests/common_tests/assertion_custom_messages.robot new file mode 100644 index 0000000..78bd0c1 --- /dev/null +++ b/test/tests/common_tests/assertion_custom_messages.robot @@ -0,0 +1,109 @@ +*** 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 + + +*** 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 from + ... '${Non Existing Select}' + ... but got 0 rows. + 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 zero rows to be returned from + ... '${Existing Select}' + ... but got rows back. Number of rows returned was 1 + 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 same number of rows to be returned from + ... '${Existing Select}' + ... than the returned rows of 1 + 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 rows to be returned from + ... '${Existing Select}' + ... than the returned rows of 1 + 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 rows to be returned from + ... '${Existing Select}' + ... than the returned rows of 1 + 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} From 4b3fc2d7093296acf602e79f0e49ee1801f61fb5 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 23 Jun 2023 08:50:24 +0200 Subject: [PATCH 062/266] fix some tests docs --- test/tests/common_tests/assertion_custom_messages.robot | 3 ++- test/tests/common_tests/basic_tests.robot | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tests/common_tests/assertion_custom_messages.robot b/test/tests/common_tests/assertion_custom_messages.robot index 78bd0c1..72ab4bb 100644 --- a/test/tests/common_tests/assertion_custom_messages.robot +++ b/test/tests/common_tests/assertion_custom_messages.robot @@ -1,5 +1,6 @@ *** Settings *** -Documentation Tests which work with the same input params across all databases. +Documentation Simulate keyword fails and check that +... using custom and starndard error messages work as expected Resource ../../resources/common.resource diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index d20aac9..21d8dac 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -15,7 +15,6 @@ SQL Statement Ending With Semicolon Works SQL Statement Ending Without Semicolon Works Query SELECT * FROM person; - # ToDo: fix for oracle Create Person Table [Setup] Log No setup for this test From e9b44e0759753e396c287c52cd8cf972b920a6c6 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 23 Jun 2023 08:50:44 +0200 Subject: [PATCH 063/266] some tests for encoding --- .../insert_data_in_person_table_utf8.sql | 1 + test/tests/common_tests/encoding.robot | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 test/resources/insert_data_in_person_table_utf8.sql create mode 100644 test/tests/common_tests/encoding.robot 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 0000000..7a15eeb --- /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/tests/common_tests/encoding.robot b/test/tests/common_tests/encoding.robot new file mode 100644 index 0000000..ce8c03a --- /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 *** +Character 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 From ef52808b17366d08c4f01ea4490ed7d8020c8d76 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 23 Jun 2023 08:51:35 +0200 Subject: [PATCH 064/266] launch config for cx_oracle --- .vscode/launch.json | 46 +++++++++++++++++++++++++++++---------- test/tests/__init__.robot | 8 +++++++ 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2f06040..76a52f4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,10 @@ "target": "${file}", "terminal": "integrated", "args": [ - "--outputdir", "logs", - "--loglevel", "DEBUG:INFO", + "--outputdir", + "logs", + "--loglevel", + "DEBUG:INFO", "-v GLOBAL_DB_SELECTOR:${input:DB}", ], }, @@ -25,8 +27,10 @@ "target": "${workspaceFolder}/test/tests/common_tests", "terminal": "integrated", "args": [ - "--outputdir", "logs", - "--loglevel", "DEBUG:INFO", + "--outputdir", + "logs", + "--loglevel", + "DEBUG:INFO", "-v GLOBAL_DB_SELECTOR:${input:DB}", ], }, @@ -38,8 +42,10 @@ "target": "${workspaceFolder}/test", "terminal": "integrated", "args": [ - "--outputdir", "logs", - "--loglevel", "DEBUG:INFO", + "--outputdir", + "logs", + "--loglevel", + "DEBUG:INFO", "--dryrun" ], }, @@ -51,8 +57,10 @@ "target": "${file}", "terminal": "integrated", "args": [ - "--outputdir", "logs", - "--loglevel", "DEBUG:INFO", + "--outputdir", + "logs", + "--loglevel", + "DEBUG:INFO", "--dryrun" ], }, @@ -64,8 +72,10 @@ "target": "^\"\\${file}\"", "terminal": "integrated", "args": [ - "--outputdir", "logs", - "--loglevel", "DEBUG:INFO", + "--outputdir", + "logs", + "--loglevel", + "DEBUG:INFO", ], }, ], @@ -74,8 +84,20 @@ "type": "pickString", "id": "DB", "description": "Database to run the tests for", - "options": ["PostgreSQL", "Oracle_Custom", "SQLite", "IBM_DB2", "Teradata", "MySQL_pymysql", "MySQL_pyodbc", "MSSQL", "Excel", "Excel_RW"], + "options": [ + "PostgreSQL", + "Oracle_Custom", + "cx_Oracle", + "SQLite", + "IBM_DB2", + "Teradata", + "MySQL_pymysql", + "MySQL_pyodbc", + "MSSQL", + "Excel", + "Excel_RW", + ], "default": "PostgreSQL" } ] -} +} \ No newline at end of file diff --git a/test/tests/__init__.robot b/test/tests/__init__.robot index 308621e..e613171 100644 --- a/test/tests/__init__.robot +++ b/test/tests/__init__.robot @@ -30,6 +30,14 @@ Set DB Variables 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 From d7b016b933e5f5d05d6a62f8268da9bbdf7a2720 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 23 Jun 2023 09:35:12 +0200 Subject: [PATCH 065/266] fix #179 - comprehensive error messages in assertion keywords --- src/DatabaseLibrary/assertion.py | 35 +++++++++---------- ...s.robot => assertion_error_messages.robot} | 19 ++++------ 2 files changed, 24 insertions(+), 30 deletions(-) rename test/tests/common_tests/{assertion_custom_messages.robot => assertion_error_messages.robot} (86%) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 8a9c7f4..3c6acf1 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -44,10 +44,10 @@ def check_if_exists_in_database(self, selectStatement, sansTran=False, msg=None) Using optional `msg` to override the default error message: | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | msg=my error message | """ - logger.info ('Executing : Check If Exists In Database | %s ' % selectStatement) + logger.info (f"Executing : Check If Exists In Database | {selectStatement}") if not self.query(selectStatement, sansTran): - raise AssertionError(msg or "Expected to have have at least one row from '%s' " - "but got 0 rows." % selectStatement) + raise AssertionError(msg or f"Expected to have have at least one row, " + f"but got 0 rows from: '{selectStatement}'") def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=None): """ @@ -75,11 +75,11 @@ def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=N Using optional `msg` to override the default error message: | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ - logger.info('Executing : Check If Not Exists In Database | %s ' % selectStatement) + logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") queryResults = self.query(selectStatement, sansTran) if queryResults: - raise AssertionError(msg or "Expected to have have no rows from '%s' " - "but got some rows : %s." % (selectStatement, queryResults)) + raise AssertionError(msg or f"Expected to have have no rows from '{selectStatement}', " + f"but got some rows: {queryResults}") def row_count_is_0(self, selectStatement, sansTran=False, msg=None): """ @@ -105,11 +105,10 @@ def row_count_is_0(self, selectStatement, sansTran=False, msg=None): Using optional `msg` to override the default error message: | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ - logger.info('Executing : Row Count Is 0 | %s ' % selectStatement) + logger.info(f"Executing : Row Count Is 0 | selectStatement") num_rows = self.row_count(selectStatement, sansTran) if num_rows > 0: - raise AssertionError(msg or "Expected zero rows to be returned from '%s' " - "but got rows back. Number of rows returned was %s" % (selectStatement, num_rows)) + raise AssertionError(msg or f"Expected 0 rows, but {num_rows} were returned from: '{selectStatement}'") def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg=None): """ @@ -136,11 +135,11 @@ def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg= Using optional `msg` to override the default error message: | Row Count Is Equal To X | SELECT id FROM person | 1 | msg=my error message | """ - logger.info('Executing : Row Count Is Equal To X | %s | %s ' % (selectStatement, numRows)) + logger.info(f"Executing : Row Count Is Equal To X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran) if num_rows != int(numRows.encode('ascii')): - raise AssertionError(msg or "Expected same number of rows to be returned from '%s' " - "than the returned rows of %s" % (selectStatement, num_rows)) + raise AssertionError(msg or f"Expected {numRows} rows, " + f"but {num_rows} were returned from: '{selectStatement}'") def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, msg=None): """ @@ -167,11 +166,11 @@ def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, Using optional `msg` to override the default error message: | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | msg=my error message | """ - logger.info('Executing : Row Count Is Greater Than X | %s | %s ' % (selectStatement, numRows)) + logger.info(f"Executing : Row Count Is Greater Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran) if num_rows <= int(numRows.encode('ascii')): - raise AssertionError(msg or "Expected more rows to be returned from '%s' " - "than the returned rows of %s" % (selectStatement, num_rows)) + raise AssertionError(msg or f"Expected more than {numRows} rows, " + f"but {num_rows} were returned from '{selectStatement}'") def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg=None): """ @@ -198,11 +197,11 @@ def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg Using optional `msg` to override the default error message: | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 | msg=my error message | """ - logger.info('Executing : Row Count Is Less Than X | %s | %s ' % (selectStatement, numRows)) + logger.info(f"Executing : Row Count Is Less Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran) if num_rows >= int(numRows.encode('ascii')): - raise AssertionError(msg or "Expected less rows to be returned from '%s' " - "than the returned rows of %s" % (selectStatement, num_rows)) + raise AssertionError(msg or f"Expected less than {numRows} rows, " + f"but {num_rows} were returned from '{selectStatement}'") def table_must_exist(self, tableName, sansTran=False, msg=None): """ diff --git a/test/tests/common_tests/assertion_custom_messages.robot b/test/tests/common_tests/assertion_error_messages.robot similarity index 86% rename from test/tests/common_tests/assertion_custom_messages.robot rename to test/tests/common_tests/assertion_error_messages.robot index 72ab4bb..35d81e4 100644 --- a/test/tests/common_tests/assertion_custom_messages.robot +++ b/test/tests/common_tests/assertion_error_messages.robot @@ -19,9 +19,8 @@ ${Existing Select} SELECT id FROM person WHERE first_name = 'Franz Alla *** Test Cases *** Check If Exists In DB Fails ${expected error}= Catenate - ... Expected to have have at least one row from + ... Expected to have have at least one row, but got 0 rows from: ... '${Non Existing Select}' - ... but got 0 rows. Run Keyword And Expect Error ${expected error} ... Check If Exists In Database ${Non Existing Select} @@ -33,8 +32,8 @@ Check If Exists In DB Fails With Message Check If Not Exists In DB Fails ${expected error}= Catenate ... Expected to have have no rows from - ... '${Existing Select}' - ... but got some rows : * + ... '${Existing Select}', + ... but got some rows: * Run Keyword And Expect Error ${expected error} ... Check If Not Exists In Database ${Existing Select} @@ -56,9 +55,8 @@ Table Must Exist Fails With Message Verify Row Count Is 0 Fails ${expected error}= Catenate - ... Expected zero rows to be returned from + ... Expected 0 rows, but 1 were returned from: ... '${Existing Select}' - ... but got rows back. Number of rows returned was 1 Run Keyword And Expect Error ${expected error} ... Row Count Is 0 ${Existing Select} @@ -69,9 +67,8 @@ Verify Row Count Is 0 Fails With Message Verify Row Count Is Equal To X Fails ${expected error}= Catenate - ... Expected same number of rows to be returned from + ... Expected 9 rows, but 1 were returned from: ... '${Existing Select}' - ... than the returned rows of 1 Run Keyword And Expect Error ${expected error} ... Row Count Is Equal To X ${Existing Select} 9 @@ -82,9 +79,8 @@ Verify Row Count Is Equal To X Fails With Message Verify Row Count Is Less Than X Fails ${expected error}= Catenate - ... Expected less rows to be returned from + ... Expected less than 1 rows, but 1 were returned from ... '${Existing Select}' - ... than the returned rows of 1 Run Keyword And Expect Error ${expected error} ... Row Count Is Less Than X ... ${Existing Select} 1 @@ -96,9 +92,8 @@ Verify Row Count Is Less Than X Fails With Message Verify Row Count Is Greater Than X Fails ${expected error}= Catenate - ... Expected more rows to be returned from + ... Expected more than 1 rows, but 1 were returned from ... '${Existing Select}' - ... than the returned rows of 1 Run Keyword And Expect Error ${expected error} ... Row Count Is Greater Than X ... ${Existing Select} 1 From d980ad6bbc7a78524553444afe6aad5fee25a63c Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 23 Jun 2023 10:06:02 +0200 Subject: [PATCH 066/266] fix #166 - support new oracledb driver --- .github/workflows/common_tests.yml | 4 ++-- .vscode/launch.json | 2 +- src/DatabaseLibrary/assertion.py | 2 +- src/DatabaseLibrary/connection_manager.py | 5 +++++ src/DatabaseLibrary/query.py | 2 +- test/tests/__init__.robot | 4 ++-- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/common_tests.yml b/.github/workflows/common_tests.yml index e9715c6..9e81a67 100644 --- a/.github/workflows/common_tests.yml +++ b/.github/workflows/common_tests.yml @@ -25,8 +25,8 @@ jobs: py_db_module: psycopg2 pip_install: psycopg2 db_port: 5432 - - job_name: Oracle_Custom - module_mode: custom + - job_name: oracledb + module_mode: standard py_db_module: oracledb pip_install: oracledb db_port: 1521 diff --git a/.vscode/launch.json b/.vscode/launch.json index 76a52f4..868cdbf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -86,7 +86,7 @@ "description": "Database to run the tests for", "options": [ "PostgreSQL", - "Oracle_Custom", + "oracledb", "cx_Oracle", "SQLite", "IBM_DB2", diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 3c6acf1..5ecbe55 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -224,7 +224,7 @@ def table_must_exist(self, tableName, sansTran=False, msg=None): | Table Must Exist | first_name | msg=my error message | """ logger.info('Executing : Table Must Exist | %s ' % tableName) - if self.db_api_module_name in ["cx_Oracle"]: + if self.db_api_module_name in ["cx_Oracle", "oracledb"]: selectStatement = ("SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('%s')" % tableName) table_exists = self.row_count(selectStatement, sansTran) > 0 elif self.db_api_module_name in ["sqlite3"]: diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 52d02a3..de76d53 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -130,6 +130,11 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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 ["oracledb"]: + dbPort = dbPort or 1521 + oracle_connection_params = db_api_2.ConnectParams(host=dbHost, port=dbPort, service_name=dbName) + logger.info('Connecting using: %s.connect(user=%s, password=%s, params=%s) ' % (dbapiModuleName, dbUsername, dbPassword, oracle_connection_params)) + self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) elif dbapiModuleName in ["teradata"]: dbPort = dbPort or 1025 teradata_udaExec = db_api_2.UdaExec(appName="RobotFramework", version="1.0", logConsole=False) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 6300a60..5551f6f 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -345,7 +345,7 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): spParams = [] cur = None try: - if self.db_api_module_name in ["cx_Oracle"]: + if self.db_api_module_name in ["cx_Oracle", "oracledb"]: cur = self._dbconnection.cursor() else: cur = self._dbconnection.cursor(as_dict=False) diff --git a/test/tests/__init__.robot b/test/tests/__init__.robot index e613171..05b728e 100644 --- a/test/tests/__init__.robot +++ b/test/tests/__init__.robot @@ -22,8 +22,8 @@ Set DB Variables 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_Custom" - Set Global Variable ${DB_MODULE_MODE} custom + 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 From a943dcc5c010ac754a4229a347d6df7cb58019a3 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 23 Jun 2023 11:06:33 +0200 Subject: [PATCH 067/266] Set charset when connecting via pyodbc --- src/DatabaseLibrary/connection_manager.py | 4 ++-- test/resources/common.resource | 4 +--- test/tests/common_tests/encoding.robot | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index de76d53..ae547fe 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -97,7 +97,7 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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) + self._dbconnection = db_api_2.connect(db=dbName, user=dbUsername, passwd=dbPassword, host=dbHost, port=dbPort, charset='utf8mb4' or 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)) @@ -106,7 +106,7 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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)) + self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s:%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, 'utf8mb4' or dbCharset)) 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";)' % ( diff --git a/test/resources/common.resource b/test/resources/common.resource index e85a7e4..6b78a1b 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -17,7 +17,6 @@ ${DB_PORT} 5432 ${DB_USER} db_user # used for MySQL via PyODBC only -${DB_CHARSET} ${None} ${DB_DRIVER} ODBC Driver 18 for SQL Server @@ -42,8 +41,7 @@ Connect To DB ... ${DB_PASS} ... ${DB_HOST} ... ${DB_PORT} - ... ${DB_CHARSET} - ... ${DB_DRIVER} + ... dbDriver=${DB_DRIVER} ELSE Connect To Database ... ${DB_MODULE} diff --git a/test/tests/common_tests/encoding.robot b/test/tests/common_tests/encoding.robot index ce8c03a..b7dd2d9 100644 --- a/test/tests/common_tests/encoding.robot +++ b/test/tests/common_tests/encoding.robot @@ -10,7 +10,7 @@ Test Teardown Drop Tables Person And Foobar *** Test Cases *** -Character In Values +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' From 5f3cefd5b28292770f82569d64e7635549742d98 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 10 Jul 2023 14:58:20 +0200 Subject: [PATCH 068/266] Hide DB connection password in output logs --- src/DatabaseLibrary/connection_manager.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index ae547fe..6f60b36 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -96,16 +96,16 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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)) + logger.info('Connecting using : %s.connect(db=%s, user=%s, passwd=***, host=%s, port=%s, charset=%s) ' % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort, dbCharset)) self._dbconnection = db_api_2.connect(db=dbName, user=dbUsername, passwd=dbPassword, host=dbHost, port=dbPort, charset='utf8mb4' or 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)) + logger.info('Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, 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)) + logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s:%s;DATABASE=%s;UID=%s;PWD=***)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername)) self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s:%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, 'utf8mb4' or dbCharset)) elif dbapiModuleName in ["excel"]: logger.info( @@ -123,22 +123,24 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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)) + logger.info('Connecting using : %s.connect(DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=***;) ' % (dbapiModuleName, dbName, dbHost, dbPort, dbUsername)) 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) + self.omit_trailing_semicolon = True elif dbapiModuleName in ["oracledb"]: dbPort = dbPort or 1521 oracle_connection_params = db_api_2.ConnectParams(host=dbHost, port=dbPort, service_name=dbName) - logger.info('Connecting using: %s.connect(user=%s, password=%s, params=%s) ' % (dbapiModuleName, dbUsername, dbPassword, oracle_connection_params)) + logger.info('Connecting using: %s.connect(user=%s, password=***, params=%s) ' % (dbapiModuleName, dbUsername, oracle_connection_params)) self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) + self.omit_trailing_semicolon = True elif dbapiModuleName in ["teradata"]: dbPort = dbPort 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)) + logger.info('Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort)) self._dbconnection = teradata_udaExec.connect( method="odbc", system=dbHost, From 841260a965697006a335cbbae9364a7a5ce0eccd Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 11 Jul 2023 14:19:02 +0200 Subject: [PATCH 069/266] #174 - omitting semicolon at the end of an SQL statement is now defined based on the DB module (True for Oracle only) and can be changed from outside - necessary for running some SQL scripts even in Oracle --- src/DatabaseLibrary/connection_manager.py | 1 + src/DatabaseLibrary/query.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 6f60b36..195313c 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -33,6 +33,7 @@ def __init__(self): """ self._dbconnection = None self.db_api_module_name = None + self.omit_trailing_semicolon = False def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None, dbPassword=None, dbHost=None, dbPort=None, dbCharset=None, dbDriver=None, dbConfigFile=None): """ diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 5e4370a..0797d69 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -367,6 +367,17 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): if not sansTran: self._dbconnection.rollback() - def __execute_sql(self, cur, sqlStatement): - sqlStatement = sqlStatement.rstrip(";") - return cur.execute(sqlStatement) + def __execute_sql(self, cur, sql_statement, omit_trailing_semicolon=None): + """ + Runs the `sql_statement` using `cur` as Cursor object. + Use `omit_trailing_semicolon` parameter (bool) for explicite 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 is None: + omit_trailing_semicolon = self.omit_trailing_semicolon + if omit_trailing_semicolon: + sql_statement = sql_statement.rstrip(";") + logger.debug(f"Executing sql: {sql_statement}") + return cur.execute(sql_statement) From 46d6120dd6ccd64fa0d704063ea4c0f8ebbc0650 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 11 Jul 2023 14:28:20 +0200 Subject: [PATCH 070/266] Improve running SQL script files - separate statements more precisely and handle begin/end blocks properly --- src/DatabaseLibrary/query.py | 98 ++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 0797d69..2e817a1 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -200,6 +200,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): 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 | @@ -207,7 +208,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): | 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 (';'). + SQL commands are expected to be delimited by a semi-colon (';') - they will be executed separately. For example: DELETE FROM person_employee_table; @@ -232,8 +233,9 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): 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. + However, lines that starts with a number sign (`#`) or a double dash ("--") + are treated as a commented line. Thus, none of the contents of that line will be executed. + For example: # Delete the bridging table first... @@ -245,50 +247,68 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): DELETE FROM employee_table + The slash signs ("/") are always ignored and have no impact on execution order. + 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') - - 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: + with open(sqlScriptFileName, encoding='UTF-8') as sql_file: + cur = None + try: + statements_to_execute = [] + cur = self._dbconnection.cursor() + logger.info('Executing : Execute SQL Script | %s ' % sqlScriptFileName) + current_statement = '' + inside_statements_group = False + + for line in sql_file: + line = line.strip() + if line.startswith('#') or line.startswith('--') or line == "/": + continue + if 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 + + # "select * from person;" -> ["select..", ""] for sqlFragment in sqlFragments: sqlFragment = sqlFragment.strip() if len(sqlFragment) == 0: continue - - sqlStatement += sqlFragment + ' ' - - self.__execute_sql(cur, sqlStatement) - sqlStatement = '' - - sqlStatement = sqlStatement.strip() - if len(sqlStatement) != 0: - self.__execute_sql(cur, sqlStatement) - - if not sansTran: - self._dbconnection.commit() - finally: - if cur: + if inside_statements_group: + # if statements inside a begin/end block have semicolns, + # they must persist - even with oracle + sqlFragment += "; " + if sqlFragment.lower() == "end; ": + inside_statements_group = False + elif sqlFragment.lower().startswith("begin"): + inside_statements_group = True + current_statement += sqlFragment + if not inside_statements_group: + statements_to_execute.append(current_statement.strip()) + current_statement = '' + + current_statement = current_statement.strip() + if len(current_statement) != 0: + statements_to_execute.append(current_statement) + + for statement in statements_to_execute: + logger.info(f"Executing statement from script file: {statement}") + omit_semicolon = not statement.lower().endswith("end;") + self.__execute_sql(cur, statement, omit_semicolon) if not sansTran: - self._dbconnection.rollback() + self._dbconnection.commit() + finally: + if cur: + if not sansTran: + self._dbconnection.rollback() def execute_sql_string(self, sqlString, sansTran=False): """ From 9f4d18f2b62578391f0e03e8e1b2d568cd45e5d2 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 11 Jul 2023 14:34:55 +0200 Subject: [PATCH 071/266] Improve the 'Call stored procedure' keyword: - Support pymysql, oracledb/cx_Oracle, psycopg2/3, pymssql - Return both param values and result sets (even multiple sets for supported DB) - Special handling for OUT params of cursor type - For not supported DB's there is a warning message and more cautious attempt to get results + Tests --- src/DatabaseLibrary/query.py | 141 ++++++++++++++---- .../create_stored_procedure_mssql.sql | 36 +++++ .../create_stored_procedure_mysql.sql | 30 ++++ .../create_stored_procedure_postgres.sql | 48 ++++++ .../create_stored_procedures_oracle.sql | 32 ++++ .../common_tests/stored_procedures.robot | 105 +++++++++++++ 6 files changed, 366 insertions(+), 26 deletions(-) create mode 100644 test/resources/create_stored_procedure_mssql.sql create mode 100644 test/resources/create_stored_procedure_mysql.sql create mode 100644 test/resources/create_stored_procedure_postgres.sql create mode 100644 test/resources/create_stored_procedures_oracle.sql create mode 100644 test/tests/common_tests/stored_procedures.robot diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 2e817a1..dfdda02 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -13,6 +13,7 @@ # limitations under the License. import sys +import inspect from robot.api import logger @@ -340,48 +341,136 @@ def execute_sql_string(self, sqlString, sansTran=False): 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. + Calls a stored procedure `spName` with the `spParams` - a *list* of parameters the procedure requires. + Use the special *CURSOR* value for OUT params, which should receive result sets - + they will be converted to appropriate DB variables before calling the procedure. + This is necessary only for some databases (e.g. Oracle or PostgreSQL). - The return from this keyword will always be a list. + The keywords always *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 or put them in the OUT params of type *CURSOR* (like in Oracle or PostgreSQL). - Example: - | @{ParamList} = | Create List | FirstParam | SecondParam | ThirdParam | - | @{QueryResults} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | List of Parameters | + It also depends on the database, how the procedure returns the values - as params or as result sets. + E.g. calling a procedure in *PostgreSQL* returns even a single value of an OUT param as a result set. - Example: - | @{ParamList} = | Create List | Testing | LastName | - | Set Test Variable | ${SPName} = | DBTest.DBSchema.MyStoredProc | - | @{QueryResults} = | Call Stored Procedure | ${SPName} | ${ParamList} | - | Log List | @{QueryResults} | + Set optional input `sansTran` to True to run command without an explicit transaction commit or rollback. + + Simple example: + | @{Params} = | Create List | Jerry | out_second_name | + | @{Param values} @{Result sets} = | Call Stored Procedure | Get_second_name | ${Params} | + | # ${Param values} = ['Jerry', 'Schneider'] | + | # ${result sets} = [] | + + Example with a single CURSOR parameter (Oracle DB): + | @{Params} = | Create List | CURSOR | + | @{Param values} @{Result sets} = | Call Stored Procedure | Get_all_second_names | ${Params} | + | # ${Param values} = [>] | + | # ${result sets} = [[('See',), ('Schneider',)]] | + + Example with multiple CURSOR parameters (Oracle DB): + | @{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',)]] | Using optional `sansTran` to run command without an explicit transaction commit or rollback: - | @{QueryResults} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | List of Parameters | True | + | @{Param values} @{Result sets} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | ${Params} | True | """ if spParams is None: spParams = [] cur = None try: + logger.info('Executing : Call Stored Procedure | %s | %s ' % (spName, spParams)) if self.db_api_module_name == "pymssql": cur = self._dbconnection.cursor(as_dict=False) else: cur = self._dbconnection.cursor() - 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) + + param_values = [] + result_sets = [] + + if self.db_api_module_name == "pymysql": + cur.callproc(spName, spParams) + + # 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(spParams)): + cur.execute(f"select @_{spName}_{i}") + param_values.append(cur.fetchall()[0][0]) + + elif self.db_api_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= spParams.copy() + cursor_params = [] + for i in range(0, len(spParams)): + if spParams[i] == "CURSOR": + cursor_param = self._dbconnection.cursor() + params_substituted[i] = cursor_param + cursor_params.append(cursor_param) + param_values = cur.callproc(spName, params_substituted) + for result_set in cursor_params: + result_sets.append(list(result_set)) + + elif self.db_api_module_name in ["psycopg2", "psycopg3"]: + cur = self._dbconnection.cursor() + # check if "CURSOR" params were passed - they will be replaced + # with cursor variables for storing the result sets + params_substituted= spParams.copy() + cursor_params = [] + for i in range(0, len(spParams)): + if spParams[i] == "CURSOR": + cursor_param = f"CURSOR_{i}" + params_substituted[i] = cursor_param + cursor_params.append(cursor_param) + param_values = cur.callproc(spName, 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 self.db_api_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: + logger.info(f"CAUTION! Calling a stored procedure for '{self.db_api_module_name}' is not tested, " + "results might be invalid!") + cur = self._dbconnection.cursor() + param_values = cur.callproc(spName, spParams) + logger.info("Reading the procedure results..") + 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 + if not sansTran: self._dbconnection.commit() - return retVal + + return param_values, result_sets finally: if cur: if not sansTran: diff --git a/test/resources/create_stored_procedure_mssql.sql b/test/resources/create_stored_procedure_mssql.sql new file mode 100644 index 0000000..f7da94f --- /dev/null +++ b/test/resources/create_stored_procedure_mssql.sql @@ -0,0 +1,36 @@ +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; \ No newline at end of file diff --git a/test/resources/create_stored_procedure_mysql.sql b/test/resources/create_stored_procedure_mysql.sql new file mode 100644 index 0000000..2f69130 --- /dev/null +++ b/test/resources/create_stored_procedure_mysql.sql @@ -0,0 +1,30 @@ +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; \ No newline at end of file diff --git a/test/resources/create_stored_procedure_postgres.sql b/test/resources/create_stored_procedure_postgres.sql new file mode 100644 index 0000000..540e3ad --- /dev/null +++ b/test/resources/create_stored_procedure_postgres.sql @@ -0,0 +1,48 @@ +DROP ROUTINE IF EXISTS no_params; +CREATE FUNCTION no_params() +RETURNS VOID +BEGIN ATOMIC +-- 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 +'; \ 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 0000000..f56cd3a --- /dev/null +++ b/test/resources/create_stored_procedures_oracle.sql @@ -0,0 +1,32 @@ +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; \ No newline at end of file diff --git a/test/tests/common_tests/stored_procedures.robot b/test/tests/common_tests/stored_procedures.robot new file mode 100644 index 0000000..bdda277 --- /dev/null +++ b/test/tests/common_tests/stored_procedures.robot @@ -0,0 +1,105 @@ +*** 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] None + 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 + + +*** 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_procedure_mysql.sql + ELSE IF "${DB_MODULE}" in ["psycopg2", "psycopg3"] + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_postgres.sql + ELSE IF "${DB_MODULE}" in ["pymssql"] + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_mssql.sql + ELSE + Skip Don't know how to create stored procedures for '${DB_MODULE}' + END From a4f6f2b287577ca355cfafa1605a0e96b35855d9 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 11 Jul 2023 14:35:27 +0200 Subject: [PATCH 072/266] Add a test 'Simple Select With Multiple Rows' --- test/tests/common_tests/basic_tests.robot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 21d8dac..2fe17bc 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -31,6 +31,12 @@ Execute SQL String - Create Foobar Table ${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' From 7ff91a6a237e4dc69a997084dc741758aebc63f8 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 11 Jul 2023 14:44:55 +0200 Subject: [PATCH 073/266] Tests for stored procedures - change SQL script for PostgreSQL to plpgsql language to fix failiing pipeline --- test/resources/create_stored_procedure_postgres.sql | 8 ++++++-- test/tests/common_tests/stored_procedures.robot | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/resources/create_stored_procedure_postgres.sql b/test/resources/create_stored_procedure_postgres.sql index 540e3ad..d35e9d7 100644 --- a/test/resources/create_stored_procedure_postgres.sql +++ b/test/resources/create_stored_procedure_postgres.sql @@ -1,9 +1,13 @@ DROP ROUTINE IF EXISTS no_params; CREATE FUNCTION no_params() RETURNS VOID -BEGIN ATOMIC +LANGUAGE plpgsql +AS +' +BEGIN -- Do nothing -END; +END +'; DROP ROUTINE IF EXISTS get_second_name; CREATE FUNCTION diff --git a/test/tests/common_tests/stored_procedures.robot b/test/tests/common_tests/stored_procedures.robot index bdda277..32aa6e2 100644 --- a/test/tests/common_tests/stored_procedures.robot +++ b/test/tests/common_tests/stored_procedures.robot @@ -13,7 +13,7 @@ Procedure Takes 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] None + Should Be Equal As Strings ${result sets}[0][0][0] ${EMPTY} ELSE Length Should Be ${result sets} 0 END From a49f7e190eaed1fca400f6f47ce4d41a985b771a Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 13 Jul 2023 17:16:23 +0200 Subject: [PATCH 074/266] Fix #111 - support semicolons in values in SQL script files --- src/DatabaseLibrary/query.py | 18 +++++--- .../semicolons_and_quotes_in_values.sql | 1 + .../semicolons_in_values.sql | 2 + .../statements_in_one_line.sql | 1 + test/tests/common_tests/script_files.robot | 42 +++++++++++++++++++ 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 test/resources/script_file_tests/semicolons_and_quotes_in_values.sql create mode 100644 test/resources/script_file_tests/semicolons_in_values.sql create mode 100644 test/resources/script_file_tests/statements_in_one_line.sql create mode 100644 test/tests/common_tests/script_files.robot diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index dfdda02..9c979b7 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -272,16 +272,14 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): # 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: - sqlFragment = sqlFragment.strip() - if len(sqlFragment) == 0: + if len(sqlFragment.strip()) == 0: continue if inside_statements_group: # if statements inside a begin/end block have semicolns, @@ -291,10 +289,20 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): inside_statements_group = False 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: + 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: 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 0000000..d5a846a --- /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 0000000..47f6f3f --- /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 0000000..444900c --- /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/tests/common_tests/script_files.robot b/test/tests/common_tests/script_files.robot new file mode 100644 index 0000000..2d067ec --- /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 From 8436c621156cbd83bf9b31d65241de88cdfdc518 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 13 Jul 2023 18:07:16 +0200 Subject: [PATCH 075/266] Changelog --- dblib_changelog.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100755 dblib_changelog.md diff --git a/dblib_changelog.md b/dblib_changelog.md new file mode 100755 index 0000000..b377238 --- /dev/null +++ b/dblib_changelog.md @@ -0,0 +1,29 @@ +# Most important changes +- [added support for Teradata 15 & 16](https://github.com/MarketSquare/Robotframework-Database-Library/commit/ec8a62acad8ff63fce3edc07d6110851438bf9c7) + ---> !TESTS ARE OK!!! +- [Solve encoding problem - explicitely use UTF8 when running SQL script files](https://github.com/MarketSquare/Robotframework-Database-Library/commit/3d0709168481e70caf18e61a3ca18f0680473e22) + + https://github.com/MarketSquare/Robotframework-Database-Library/commit/01427be542142e71e85cf8929d988fa4971f980e +- [Support kingbase database](https://github.com/MarketSquare/Robotframework-Database-Library/commit/afddd4bf56def722c5b8e95b96424fd421df80e0) + + https://github.com/MarketSquare/Robotframework-Database-Library/commit/287c3455bd863106fa0466d5e1ee9829d0fff627 + ---> THERE ARE NO TESTS AT ALL + +- pyodbc - allow overriding dbDriver istead of always using "SQL Server" + - https://github.com/MarketSquare/Robotframework-Database-Library/commit/27333ce101222846b0e3dc9ab65b9695245ec340 + - https://github.com/MarketSquare/Robotframework-Database-Library/commit/1a8295e2ac757b7d857dbd13e1e72fb7f05b1eb2 + - https://github.com/MarketSquare/Robotframework-Database-Library/commit/7d719d7b8c79515bacbe2cbff03919766faa5f7b + - https://github.com/MarketSquare/Robotframework-Database-Library/commit/353253022da048259701dd14488245bab730e261 + +- PyODBC Tests: + - https://github.com/MarketSquare/Robotframework-Database-Library/commit/1c7727eade8a1db3a2375463ab3a9752e8664990 + + +- The keyword "Disconnect From Database" wouldn't fail if there was no open connection: + - https://github.com/MarketSquare/Robotframework-Database-Library/commit/d3d0962c3c6eb9a6808371b559a9657bfe125796 + - My last commit + +# Other minor improvements + +# What I have done +- See issues +- Tests and CI +- Set charset when connecting via pyodbc \ No newline at end of file From d4aa0090423e864deba2fdaad8071ebb5f4348d2 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 13 Jul 2023 18:11:44 +0200 Subject: [PATCH 076/266] Don't output passwords into logs - forgot some --- src/DatabaseLibrary/connection_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 195313c..30b8a9c 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -129,7 +129,7 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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)) + logger.info('Connecting using: %s.connect(user=%s, password=***, dsn=%s) ' % (dbapiModuleName, dbUsername, oracle_dsn)) self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, dsn=oracle_dsn) self.omit_trailing_semicolon = True elif dbapiModuleName in ["oracledb"]: @@ -153,12 +153,12 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None ) 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)) + logger.info('Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) ' % ( + dbapiModuleName, dbName, dbUsername, dbHost, dbPort)) self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort) else: - logger.info('Connecting using : %s.connect(database=%s, user=%s, password=%s, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbPassword, dbHost, dbPort)) + logger.info('Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort)) self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort) def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_connect_string=''): From 66f45e455c4469440f4ff32c2c0341c7bfb9ecc8 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 13 Jul 2023 19:39:41 +0200 Subject: [PATCH 077/266] Hide passwords in logs even in case of using the 'connect using custom params' keyword - check most common password parameter names --- src/DatabaseLibrary/connection_manager.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 30b8a9c..fb37ca3 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -181,7 +181,16 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne db_connect_string = f'db_api_2.connect({db_connect_string})' - logger.info('Executing : Connect To Database Using Custom Params : %s.connect(%s) ' % (dbapiModuleName, db_connect_string)) + connection_string_with_hidden_pass = db_connect_string + for pass_param_name in ['pass', 'passwd', 'password', 'pwd', 'PWD']: + splitted = connection_string_with_hidden_pass.split(pass_param_name + '=') + if len(splitted) < 2: + continue + splitted = splitted[1].split(',') + value_to_hide = splitted[0] + connection_string_with_hidden_pass = connection_string_with_hidden_pass.replace(value_to_hide, "***") + logger.info('Executing : Connect To Database Using Custom Params : %s.connect(%s) ' % (dbapiModuleName, connection_string_with_hidden_pass)) + self._dbconnection = eval(db_connect_string) def connect_to_database_using_custom_connection_string(self, dbapiModuleName=None, db_connect_string=''): From a74ebdb7e22a41982f46189a37a64cf02d5f080c Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 09:42:41 +0200 Subject: [PATCH 078/266] Remove dist --- .../robotframework-databaselibrary-1.2.4.tar.gz | Bin 8618 -> 0 bytes ...ework_databaselibrary-1.2.4-py3-none-any.whl | Bin 11778 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 dist/robotframework-databaselibrary-1.2.4.tar.gz delete mode 100644 dist/robotframework_databaselibrary-1.2.4-py3-none-any.whl diff --git a/dist/robotframework-databaselibrary-1.2.4.tar.gz b/dist/robotframework-databaselibrary-1.2.4.tar.gz deleted file mode 100644 index 555dced24b90d2fab83b0ff7acf31e054eede4dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8618 zcmV;bAywWViwFn^urOT$|72-%bX;<8VsCV2a$#*{cW-iQEo5PIVPau(Wo&6;a$$0L zEio=KE;KH5VR8WNJ!^B?$g=y{RsIiioXrJxu?&9R>l{w4A7sN>zZOVxwp=bOV8A*N z644_zyUBmQ-8~~|BqR(NJKhBCR*a;Xp6;3H*Gx~#vD>!SbMyhfwVe;^UER~$y30+Y z?dZ;Ey{v6&+Z#VT>?*;vx3@=s;Y$9c-{q~H@^*QvR4T*s@@{E!_XoD~!xLP->*)?- z4~CN@?r(BA(~SScZ@;eB&R(BCplfz_cQeO-XL~O({(I%E@(--E9REkZPPwP!ps&B< zj%(P~J}YbG)s-`t5c_QMcBKvU1|7(0le5}$Fd~zJ;OA-5tCI|&vsZHp|aNj z*wvLc_JFSs^**$C<9S1Oe`BL>csG7q>)3-0Xy5vC-Q|3v9!Do=Wc?(-=fL-FY-b-~ zTYs_x)6^~YPB*#LF?818JSefw2fAVIGXkvv=wJHy0GnG~IWaoiasijOXTP1Dzdu`D zIp%K1F@_$I8a}-+bq~lHD39v=qi;CeP4?XC%HNy+>(ztf)2cS;J`VqH?*#l`+SvlZ z5B$Hixwqv1r?~FrsmkPQbp`k%;C5Mwb!^Mi4a=46Z*Z3lxNdoA?4N^DF7Up_nm2~a zI>55JY1+4JWc$ps*+-uPM7VKSFgcmt^SHyXWCxj&-RLfBb8fML-sM>76L*Z>hPwW=N48R#h0_)(S#u`K3F?vQvC*Ye}i4E+oZ*uJ!^YNqd zzso&;sC9b%$Kn6d&Sp~o?{04|`Tr@dOWyCd46A2ft*&@_zt#2)v&$+>+`)9sALZnch4|6<98iC^q66h5;qOq>TsdMy{H9^fcznW*ln~#m<~0DaMx2y zGTjUC#tVH9a6N8sZ#A88)@-N0VT!Wu#!2m{de*3}1KvTcx0cCW7fcK?`@5_?V)_v9 z>gZryFjK!}VDIRChr>ItN&t^r2h1U>Uu3S`^KNyA6P&K$dXCZdy;#QuSOWEG0q8pD z{I!Dyt2Nfx;X$L;C=!(SwdR}iw@vo`pk6;XYu2g_c3x*k=V!;YX6^h8?q0Klvp?8x zwX@?QgR8Szua*J%JFRBG&IY_JG;0 z24>w2QeitD)7viI7NLS;fpjaf|JnvkY!4jN)fF6YszYMl-Je@0NF#>;KPc{1b9y<1xp7v%I^# z-2Zvrt9y6J!cI2;@rMZvU5eyn^&%$k)RkVF7jnt;5LRegQQXiX<0jKyp_?Q{af7 zUEX6b{+$mkqu1hh(D`o5uv&o(%!3&0!4Ps2&}aa4e84Ra9_W_a1O}j)t;qeuG-j(y8+ZQq2+gIf}w?^t3Y^^H&5BJ2%o=zHus(cHDhYBZa{w}g2F+!E|) z6!EDg*yh$S&8TJX2AhM~6LuB3uNsWD2ks(1(9N(tNzFA*-KOKy{)yug?+u`XiScYG z3GiSR!K^nt=0OElY`#RzVg+~yGsv+`vki`j7W6b;uWg)$I?O(h4ub4k4rBNU2xA_x z(ltyi*9KHVX7Aa!7lgA8M@8xC&&=q;KRv^7y_RrGKf`1VZa%9eSsDIOMcQYY_wu!h z<9qB;6ikFx906r^8J5_PqUo%*B*q*DtV*+q)vRb5LZj7_>QNIBe_cO6WrA4t{!O)B zRSAi^uDLiZi)!KD_Kg4?0dEGx<7{Gmm@Bak*nfON*$Z}Y&}cj=#n8s!K#ULOSe&&X63?~Bc4&{NkFRM2%yf`%Fel>iX5 z7>z>eX-uU=t&%i&bd{oLuZisR{i>8Rn8=F~`i}!XskI)cS{6s}WWBO*%AbddSzz}~ zt7bB^Oa>-XX&TZlbs?SrrKvPdOi!r*9-xW>kvpdba_s>Z0QP}q(nR9~sHhfVnh9#Q zI<{|lEyHb<7VMa~qy|D2LKKn@=(k~_VNcwnLXw_Hs4b#4pTZM~I3sHJyHy)?7=R<3 zF=%p>7UX<90O$k#jfIh;$mT`TrD&L^7x@c7e?AExHC)4nrxNIvKWLHAk-|I_ZPN<$ z5Wv4!X*PlWCwHV2hyHITMc(W}oj{RvVQ2oJ%^fu7>0t=@R(GLG(#3s1$OKW(Kl-}a z^6b`KmdpnrFbeMM!k^rb46La?#)Ll3EiRK;Rw zS%BV9u#gHw(IfdRfme7GmGX}YFou_xZD`!W_@{hMcRPlWE6li*->U^kTjN z&2oa{eXpR|njn3rfiOw*mKUT`rl#b6g|YyWo{u(}zF!ugO4P=_x*qx0BK3#}b9+8| zgv3^B;Jb1=Ug{5ed`5?Q=1~|z7zz<7w%k#U;G1CE{lTIc1s@` zEs=;obPWvsmAZqe!QZoJ|1fy=6EfHktN>MNFTHa{LS?!GF zbIpUplWMM*dsnNz&lT9g*)g+kEzs5#*7&2*IyygVR{zt?=iWA;`q?`?q}Hf5@NicP z9|cteSgAtZUR+e`d6XuFwG67cc^}>GN7L~5)+|(0Wg=WuZVkYm0a66%NOL7e$m9rV zEM6g*cXWPoa?q@@v-6{aMs+qvNbT_f1RL=uua?82m8$x$f2PKET!I&KmbWU^#< z?gUAU#31`096#k=?vjU2Me6yJR?Dyquhk;gCeweZK^a3gjX$APuo~ixoY__Ye5@jA z)p(5xRY(*`FA8bZN{{kpv{B zLle9RG6$3;atz}L zS`u@X2ieW{0|X_}SwJH=Ds(0=iC}g?#CAM$BuGM`;}~5bq$CxXPR{PBdwyK(wSM3u z6u$9ZD#!)hWaojnl|qhpi%eZ~m7PY5AlKuQD?VW08K*#o+0ye(G*E>HZslNzFaaKb z?1;4p3x3ktW#i~H0vFG7*8yz<#xHQb8*8*G76+I>h?J)4wO&dJuGEJ~79EZTBk+S@ zCdj3Rcws7o*mT=CPVtu^Cu0F$-o%T9yp2VM@+Mw{xE){Lz>h?2O&oQgVnJ44?C$E0SwyB|VSLd(Te z6OalzT(yrh^(J3PmKI5YF4cazDyTpc9RbjjPe7PUSbgs0gQ3G!p+w1CK?NH^yBElK z-Jrjzet0tpMo=gYCm1Gz6W)LXSMS7f0U9B+Y?9qZP$k|5N<4y3KPKUb1#$%}k~8g& zZlZ|H#n45wR;>NYn@QlXK&}uQOrk6?o)xx+Hn+Kkz%t)Dw`=3;MHwdK+Hhop^_kc; zkVQ9-{1oktxpQvA$*m1PFcRFhPej=9lq;4Hs+Y5&+8nUge z(r&y^Z0R`gKESjRRwW%Dk%lKAS$XLeQT#acgY;~de&U1?&rp@(kB&5nUA58!13j?K6|-F3+RF4gf=*Yl4_YJ&)Mh0mYU@N%fC5n!;1N(3;geXEO&za5 zq)szRsL|&iB`L;J#tIFjw9#732+cVx0lJe12T`A9c%~Z*!R*93^7j;@Qr_O$ntN1^ z>$P{)dgU)*TGrqW_!(bbHL7)d1_SZZeRX`$JUBdPRN?m3+uAYyxp5*W zHQ(6e=EqthJui;gV<$95a=6ry7~e9Ua%@hGj$`8z8rc2N2s?JbMR!>KC+*GzW}!cK z^6xciBbgLsr%V(Ers+uihNjD}PiQYp=xE zjw77GiSvzON`E{*iX+RcP{c%uw%97I;I`-V^8##zXFW4Y-`&h8ef^m+)jnvN7RHU0 z`31fr8Q+{Ynr8>6^!wtx-lV_v^XB={`AMaDbWyvA3(r^gE99hLU8K-|2<8&+na~8E z0sV&DRHK<4fsm%McQ(uMLN?WHbzP)AB7UHM;P5P;ZS?`-pNx*&WR~-BhRG3|8|hXV zv_H`nOSi!yg613U>C7#}O=)SOm$bB1r?*un?sl}O$66iV_3S}ws5|<=y=Moc6@bB= zJ*!^%HZb2a%@fxZ7#Veo0G$wzA}s0|63m&^Fw|xH0H%$g}`!xH?{x54A#1& zsx=`ch2BQ-8lx@ej$Drq=$D`+Gq!B5)r(m5_?*P3-Z9RSZq8U?71A85FWJdLrtIgK zw3;M*5_qffB~`ZYPr8{Ouck_bXnVJXOXu-T&`h>I4r{qlZVhEqI5qAw#$OQx_8I{w z>%uP-q$K!`?>HE=mAw-^d(>hU2Dyp7s&z~odMDP0F85k!Gq!}$D5WPpIZ~|1%p6+2 zBZ9Odvl9a)V#FzQpP!-4asK+X#v&N;y}MwCw9RkyPX_M3Vc-R`_^mm@{Wl$BG16cg zv_FV=U>IBnw`>`P5_wQN^#Dha-QF0T8~PF!9Y67u4_b9Z=mb5z72Acj6GSXK6dWQy zclC**7%j-e$OS4hz*3_9;-Na{2wJN zn(muGljHxkcHw0r{%>o$y!`&xGX8HF|M$(s|ACo1eURSol;|xZFppl2<$xx!LRrk&yCzBbN%#lgcGBU7 zwd*1RQo{0rf=0K9ebOq6Kby&Vg}-k$y@`k6e)&ty!1x882pJQeDE%pZpj54U0+aC+ zM?M9Nipd|-9HP5~o{#0N!(&yPPE z@lt8tv=5EM;%tUBg`G(w8vUU1cAC2*Ct$T+x z9F)0vFl_UZg0Z90;c^*%MeR%aJ3_!VvtV9|_O3Ev6a34NuuB2!>I!|jIEdg?I-IEQ zH2h_C^WDODNvV6I!pUxu1!U9FyPiQ2>HA|t3jLl!S&$rF@?-|>DHA+k!F+l$kq4%| zo!$(KEnc7$5gp^Er5g!X^n^c*){wGr@@N@uzpES|6P5Olf5A9VZiX+htPs;G?F59G zb@k>0b@EKui^H`B%B0PiNV!MXmcNE_opt1%lX8u)ePjhIzvG$It_gWN)lvB{-Xals zQ4FJWuTo@nFh+m`sy4(Y(SFK~bD|<*54R-6Q&mn-_POXB#h7?9trJM-@EE$>F9;R@ zd{-eD#ZT`I9?|+lUSv}+y5J3BqK6h{`l*yl+gtdN zYjw}g`A@dBpXoG4-d(9E zw4ee$({YT-pcjWuT|W5?S`Jfo`Cf1Re{+Rmf&DyI_p-cym8v+Eg3m{HnxLUtm|O@w zBoQh>@_+91UB?76T;mttzB16R2Gt_JcZEQ|pS)eNM?l>%S~8)i43XLMUg}pKoEuo15f9a#ypnE(~&lP5~A;^9=5yL#7S zV6Sl(4o^c#)^s&=xiI+x>TZ{NUa-p-uIORd7W-h?w~Dx@Z9Whu#+UG*QpqG!!VdG+FdOHif$38m-7sk!Ln5COc`7Qu z%5SC`^a)hmlR=unLavAdW$q9&B*?5S>NtZj=W_5U)YuE4JQ2q*iC$F<&p>)swJA$y zcgP$276~c}GDM$HbgK+v(1U#t*XYC4y?$|BpxN%ph*^y6h<_Ozq&Q|625=f{-uTeb z7Dnhu!7k;z4Bx=-{^9~xu=FLhOm~Xzd=VHN2(|4P-9E{GvNy>7(=_eDosZ1`9E#Yu zkssTowvf#!4W{;FBlT1^>jU-0WNS92*1^uRhu~z7>H65N^3$$pgw*4?YeBtgy{2Fq zGpFFaVwLA|@z-dn(0oTZqJm1X&MyAg`VA>cEMZ_C@cdShl?s_AAJ)t;_g=U|nSw?9 zNTd-jvK(+ZSVp-*dVNB^BA6Q^7>D_S*5w3Zjg~dm($SI?P^{qW%;#R@vQ|@2Kp2+K z$KTS)#4$+g>r;SAF}XjQuN}Ql0be0Sz$_kV=X4^(R1K>YFjGo7%dl2w!q1`s_A^YP zTpD4i`zpbVQQDLfCq!XFxPe-$qTU4qmtA$7)vgRz zj>3QWI*WLV@5Se|g%dM)1a%36CpaHDwKPj9|4j%oJo^U{4D6$BYv|Y=-u0beuS3{V z_*a}?4K^I-2_7Kl=vqJ&*#&a}*c>Gm#U*qQ34{p?G5N{@O`4W2z%aWg!AM3Ya1@_5 zFg)xuwW>Vydl{s~y;9)l>?Ak@fw^I^Bt}NK@J=i`kX@vSW1e)#K<;^v+v%`@Z+Zp_ zDUhZEcPy!^T)CC;eTIwKWhn_DIiWFTkLm*^yXNI1yhJ zd5yA-N>K0u%(-rG*Ss;D;3f|7_t-Wu0mUiM5S@Dwb}opeLo(|dbo4;{q(WHcOzejQ zop}*;5ylqS4f>GXH1DLD-^F$7+(#MF&F9T`-MU!%Olzncc@iJ--Cm;g2;F*^e9G1Y`YIzd@lA$+#VBl z$+(ZHv?8g%;gdD<33v2|<@LRP|3}KWX)*vzKL3xO0NRr0|2Ow`$~$=eA3q7SJpca$ z*Sl&Rzt)BeUo_UaC37$Ne;WP|K3p?}0Jw+$cS_~$Wd4u6y{#qxFY|va^M8D6`9Ewo z{7P3sqG;n^6jVM77XMgR1_W`CMphzkIiq0s3GtwC_?5_v3ewsMic77Dq_hHO4^*h~ zSR^n>fXR;$QWR(wBLv3soFtQ%(1sdJcIar&Fb(<~5{9dIG`xUNF zHId73Axy`xVq(JZ1XBFtLn5S2W*r04Z**Mm;&_0#ON)d+IDVE&eCL@KCE~5X|JTUC zFfD*je!32J14a=^nB}q|ry9*3CFi9kGQkLdb>8C+h#`)9o6=MH`93)7Dz6Vm-VK2eGnQXh?0ae0%UwJR7eBcutLpP!_c*!4mc{{E~s#X zDLCJK7@Pqz(sW>z8X=x%wkLZcc%t?+07il2F_UAg77P&h{Xvd8!&BB!qe>b(5_tom z2*xdv1fs8S!I%XpH$O_BHZQV>1{pgG+I4_NkRmY zzxuj^F+>5A=EJE5L{ZQsZS@0=h81m)Q4D86Q(6hW`W=4bl|r?mG>9xrcp}z$<#rQ& zS1s|`do>#et-VKQ(JCT2he`k?`DAZvbY4%K0uIhvcaNWsn+Ke7`i}I*6i|*pNq%65 zzUPUPOL!DwDr#QP@~adp(c|oV9Pu1C@iTe%oyQBBd?#iZOc<ZoGcx`rT;g{|BDxF zB?@Z1-(SZC*^La>Xm4*Xd;O;reE)f?1l|XJ|9NK@{J)*0|M%#ZwxMnOa$;Nk6T|xO zNNr?{dpQ2(?cLa1D3!~b<=r3H$#VSve*E9%xXl8l`lw~XlyWui&RdGv?|S_2UH>nY zBl%wfJ+Za9y|Y~Zdy;Eu^ey>+q5OaG+pp`jv)AV{?Emd-@5T9lb8mNVng9Dqu2b&m zIF{>jy?&pSweso;#&zto$&+JsMX3YRWOb$C4+ijJKXO9F66Gj#@Kcs3S6ANH1HL}Q z1tVGvarZYi`i6Jow=v3MqlXUiy36@SJ&sP$$offu&w=mV*v|fh?F&(2oey-w+-C$@ z1JJ+p@c}lsx*`{?_u1RC-_Fk8pRKMa=}eTT7pCsv8jbR(E_MjqWY0Zg*Lvan-#C9; zKdLrVv+q9s-`!5?|I+r(?vnqX;;L5kUUM_w4qjz7N-GvkHuF?qrn= zJ}j{^IqGC(9;LK0=S*ICs0TC?0M8z_O#X?RPvAQ(oc~jhzd!z`l;Hm|{I}%)r??Uf wwsZ%U*Ya9k%WHWpujRG8me=xHUdwBFEwAOZyq4GU`i8Fm2f1P*vHHq)$ diff --git a/dist/robotframework_databaselibrary-1.2.4-py3-none-any.whl b/dist/robotframework_databaselibrary-1.2.4-py3-none-any.whl deleted file mode 100644 index 89cf0d613025da28d113226531a9d4e04f48d31a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11778 zcmb7qWmFtp+HK?R1P|`gKyV2Xym3izcXyZI+7R3!xCVj-C%8B6?gV$YoB8H`>z&EW zy=%QybyltFAA47w(`P@bBntzJ3jhER0c{=^3dM_TaAvRo04g#7K>YhxaYGkFBSU9X zIZGoaLnlvGeSJ$iOBa28po3@IM_olG9!y`UO<`~si~})tiTmB61rgNY&A zr9;Xv|F_9diU6C^{d$P0BgY8V3h1)w`>8x(E)4!R6s4$Gy)d zCag%7mo@mYd;BsxYc{uHr;Ph{m(M*>&sD4)@9k zhM7u_T^m!o6IqF99z>O;x#NMKt_@VTWwWwGDVo@#UeeIS&+~LU;cY5HVQ>Dt~3KL>D?K63r5IhuKasck_zNP+4V0{^AgH4D=%1k4(=KG8-@=ovP$6I&#v54=XXTvAQ218RGjBMeN;_D*cL2P0r_&z}a>vYe zp3Zofw6{Btq2Pe8?=FuH^4iKmVg1OXzI)qkDcji$)sX>|VBXy?}YLJ7{8(>#xkTY3%+Q+B0o2qul zQG7{P0IJ&KnLb|3QmKOwp@Mbp;1VmYwfA~Jm89*w0v#9y$B1+jM2Kq}+}bnosD$%u zZL}`azWMvk6uZzg$5$ZmyWd^)QU}QdDp4_cP?(NmwVl= z?L2$G;u902!U6ogaaA<2K(?`Ix!kk>L=LA4hZ+Sst-4YKlwUZ4C^}`>`RyZ6x)1Xv zg@1gE%XB^-$uI?3{HHJEohkERh9_QOZSArL1siev(#(wKUUd<>k0~`PqI>0jnZjxp z(moi%F+WQ@L(&LbG&O5!c!}r8>hO~ubE}XP<#RJ&o{FKeWRW2jov>f5^;os^&@_I6 z_1XVQ;By^Ph|+?-7AU%5aR&P|olryQq#kP&kLzT6q%%oAVe*w*uSd1miy9oQ8b2{* z27rO+(<9}guqj_5$7*s(3Y4=Vn?<&llVV05)E0@m%i`&#@7C4+C?zKj^V}lf?~31J zK(b?i7id&6YYHK3*V6K*x@p4dyjhodwRd<7Z+Ka`1AxAg4$}SFnQb1RFk-!{woouA z_*DPxsNwTVH`7er@@s-ytIrA3{eDhti#PgFxoY-j|M`|y))p{P-^yN*6&szyU{hXd{H|J!SB~_T?dADfjZ+lye_0e`+iL zCqU`nx8z+S~osr(-Qc`$cKYXHte&!LXYBC-b666PySv^x&U)fKXM7!s<8&ePwEqWN zX&)civ7>IjH}U6)dg^6LvoN6j(#h4+zP{9c%y@Qj9Z_ZmB13F1<{S}wAAYqC$}>-1 zRDuW}WU353R*d#@!B|N`s{4ofRpHBctlf|#4EXU z-%!w$5`*Y?c`6d@3Ly-kk&e*5K^+t!6u!tq;3i+M>oQd*Zq&mg zxu;@ucut=}iVTeg-6xKS4DpCxmoRLusZT19fKb&ycdp`%BVs0ev|Hc9k&q~@7G3LR zw55Pg#0M3-j@`C(>393ERuI-R^?c7(4BV;Em|qYj3seI7DTcxo>d%cu9HLhq*oh=6 zU$0ZY{L1r_d%%5oN{>Vo?Tn3#m1%3l7CRymfI>;593p9fLDM}xxq`(IJwsPdbt}qW z{~+4&=*xI@zw%a?oWM{X#8{X6?(|94x8A+6B|oJ?jgjWn1jfLB)zQ=+Ho#Ge*1Vw7 z-#AY?O!}>a6efG+_7U}~no+~a!nv*$UveGc9-J&j>1#w+4 z)bT*>fe9oe>Z~3foP59OdQ*eYIG4)K zy=z8(h#Zrds>hva(L7G}bqyTANh5u3jIVO55RPds9epR6MbeN`->q68#z> zUxL-|;*0CFRd4tAeZctq`RS>08nsZ{Wl?FOlni^V#U>EPP8(JE@$e$kaD`*lmXm`H z#l?y*vHl@Zw{An9)wTLAH`k9q3 z1IhhmY%26rL*)pwavDwCw?a-y-9@DLhSJsyqhN&{G(nL_+QgPZXc^MUo(>8lZjcMU zh|qK!p=noI_SlxRr<~VJAP?>2fCw!GeC`5F-&tCeuC~=i`giSAznP!3MTE%HJ7HN zxdboH(p-&nXqVVY{#q-M)+D)HODk@L$gHHkJA#Jr9x3Ji9L)$y+rf%Ujqi04hN=lX z*#9U8t>}1aM!1)cr32T*cb{Q95MLrMbQ0TM3B-w2_?qn}AIM9GBZ=#9c;$?*oJg`Q z+1{6ep%(7wHgJxBW%^J&D}{}IWVyuLFuZZ8@~LTtoYctR4a>(UB`})9az1(pz>8Kz z88sQ&g$iGSk(}?X=0N&!A}5_+WJ02TOclEt-?egX+*nJU4c0z~*|F`NYV!1bdj5PE z(ifuG?Ubw$fA+{evK)3_QOCx!x^we<4ojt23cXUUds(te@7S+e31FWatxNOf?Ml51 z+iNpiZ?#|jXgfc0>O~Fq&+pk&BYG$Kr2}}?&2}bz=INu)+d8b((dYeWkyBdm` zekVpR2t!U~bB}s?p0$C#bJbOmUixne7_6z9^p%kPuY9IWo?CIrX*|spE2-?GrsKA` z8}{<4LVvWfCGYRtsR@s4(9UkT1;)i1eux{SafeVnjBM9D29^38ctR#u(}y7PUlJPe z-&v%5H^86Mcnl|0s2tzOm1(h&)nJkXniRrqIfF0jsB4jj-Q=A5jbiv~nzN5xyD?W{ zq(VnfJbC((*-ASLUt6}_x!jMUn%u22MIJSC~?=OB`sd3$1zuIl8$4E97P{^OT zy2>3)?6F~_F#jsFa9m@S&^C!Z&Dn_2(2AUzXU{EaOG~^yXu{fa%7I7|P{~fFNlP#c zTM7jgG&gnnAH!~Izr>6ELQ4K> zxSmJ%xUM>7a_b5)s#Jjo*>rr1%{S>>)EI{oDbB>6xgWUoj%C6t1)v)?`km8zeE;Km>%!sH zf#guX!mIABFPRYc=}9`zvqB5Pia_J9)jIxXg zFDJ>t86YeP6x1eeB<-ukqJV*cn0U1x0+urJ8CL6>*$Bu&kcjM1l0$>5D(G?aU{i5| z2B8;9x=8fB89bjC5*9?HOeSbgMJPDqikcAmnAELbou7=ZUaDq8MHifiO*(FsiPhIb z{`m>NYpkiiR0Nk|%4PtJpB!k*u}wvQD$aampd|0XIRwv2FY*(85)m(A&2d_kml@rO zYD?}8nM&q5iUh4&i4rLc#%0ccvRktUfj#4lMz_+M(IjVwoMWg&H_Rb?c`o!G-jldT zrY$7n7E$@gZJ83&xwOl)Um)$*69aTZYm*0gaF#dBBaK;mzrYT03<4Naw&~j*8dCU`yet$t zr|NX-SXDyk?Tm!x!xne}1caDkgx-i)Q4qDxH*|NAUPd@%jK6es_BQ3loG9TM7tc~D zg&9y6{ftt39Qz1EM)J~xpWwf$qO><=?OqBq-o-~4 zB(LJiBv;E*PhAyB8|7t@3LE72$XDC*R#%T+Xa=U;O`Cd2Sd&x^2JR|^G6R)(Bf2&~ z03T<~DO?O+q>7R2HG_2Ks#F3pW9OOeNZMxfs?zggQk;AhREmiT-760R?)GK%@9Mwv zqLhEt74|J{`XC!4vcE$rw_Ey+eQpMo|9cEQ9z$C?lN-aaCPjH`+E29C3$qL`xP+cA zXmCJt)d1f86#MeW2MNS8`i+eK0gXswr9O@edE<;2@%5STo+y(JN>H4hs4k;5aUED~ zD#La&yrcY02WN89nT<$@)k(>RE^eWZmyrpc`wdOO-{kPI%f~=*LNxDbVQOxn)_$nG zSoQt7`y$wW*+FxxFX$KX^T&)M!TeXIxu{iE^^GbOx_ro@*1eM!DNhJX*wOa(uTu%i z$I8tp59)Hgo11{bqzXpG4cE=$5IV*MkF05iXZk6&LmP{9PD2|qmcnVoN$dw#6&MUX zv4x&*dhW8W4WKdPt8d)yLl;`MXv{keF(dnxHw)o?(C+io*zPXI%0+?Ij8jy#%ya!& zoEc|BDhELNiO6~XhvN^ilMo|D%`wTOMO`b?`eKAa*a^)e=^QH5R>$wuQ#+7Wuf!U1Vj>gZe8&mjQc9 z@fE+!_wGe9^NLITfir<`=FN_E7On5Cg)K?L1cR4$1WfXcLSDLs`Mu|wt$nO+8O`yQ zK8Em!Z~DWZ^&@Dyeg)Bk?z{CQ*38 zdyvw3_0Q_i9^Bl|`i}6Axte9Z88pYu^857$87a)z5V|5$Tg`=);i&`C7JRsy%}jB= zRgg*HcRr?`%7XW9`pBgv&pknDKlm)vh4E`Ac*(pC;mT1 zqr!5CyPNB`LGH`4VO@MXg#|%gIHn6O_=t6RLqxTJ0` zCc|_j&yRot6mKsOZs>nqMGLk1r;g>rW&LD-z%$OWF&ssZ`GPb>nM0*5SByGLvWuGg zCJM)l_kis)W+#jsuUqbr+@2GQ#G*h$e~5~2VVmTGPqzV#DJ1cXvwUVZ=U1=bG(rX2i>K6r6? zM59h2vuJ$2T7iQeM(qlkz2$AR+A=}na}_LZb1Nm;J^5M zj;^Lop8s(3h@b738n17mpUHTO5tanh?p?EyBv7I>AfHTcIo`@^h$)Rx*TT$lymoaPl})eTn6YpY!KI`gOh0_l)FD!Q2qKpE^OeIGhPe68(wuoMA4HP@e5s2jEV3pUvHsK z;1xHZ1U*rI!MgTSOV{d2z4kG@;r;Z%lg~wNx6pA`_Q_j!Ed*OPr=c6gn2ZXA!)=0W z^o7-P`=gXu$J$QV5;Vk!VVU4}fPCzajDq>X8dD@VLE~(Nv|ULs`++WE#dxWSyN9M@NaAFo&ol8vaG1EmALIT zPLM|(3bE&KDUSXRrtMxv21T=pX9!hxT?py6m@;6AoDDb(HH229+t^2o)wpp-+@sPW zmwL~Xcd5qM!ekxa!qEujj3A>ZjbjsL;Ws*u`i*2$B$b^fqBZT(h=GO||O8 zBK!UvTNzW3?4iw4sjE*Y2Emm2^C=6;^mtH<=D6sdO~ob5m0}o;r(TFq0juz}+<30G z&PmKSA0wcG+VdRBhzPmSytso*Xc05W5$_0ggKnYY=&J!mlcp+!$OhgdEmTheNS#=0 z$N=GWLwx{d9WM+SVlWD-a{-=If^;615~?buQ=ee>ZXgl{TobktmW~V=ZGf?Y<8wq> zE%0n7LUx;AGnkv!?h5JQf%2UT*FGXbP7>a-m~Eyyw(`r!z-eD?c3;OwK6~;!n_Utm zFSKbH`~D|S%}n(%mNCQdjSw+4s0IR%5>y8A*)S`n2O-2@5A&TG;tdbRCQMbn1IQ1# z-O_XPjZ&CC3?aix_nS_2Ow{-=CgS+x4pxDWy2^sGp|+&L(XcE`;vs_fBs-sdXm|wQ zFy7QRhw{+&0TzSugPzeX&d#p3-*QZDd-6dW`-Cy+2lnm4;@~x1VJ@2yGMVSxm(ggv zAr-B$n(M3EE3Xp$&_vFUSkzVRPmRofye69Bwb~Qy*L82Pvq&mnvzmu(P3cM0L_oCH|kGZK+$R4Pj z+2p8s#lR;bOr3$?5KYX_iWKe8-GC$1;82U*#{pwf1#~CETJm>(eM%}wtfFqSy09He zCLcJ#Ed~mSKwU;#4VdK>Eq*lRB&#k+I=<+;NP zb|dnulnp_St7ch8oR``r19N!w1{7)(`pH};!L$>SCp-_X@9l_b+pDOJB}K606j{+| z)Vyi>b*dUF8@xu{~~tSp4=kp#V| z3!;K2VxGj<#zO^to*ObD8j>{{cq{T0H$P(|Sq4(h4)rO!<`Y}3TJlAtZHU?_EQKGD z539c11Z)tZrc87%%Cz_!Zs|N%)keD$Tqh5VKncr@?II~6Oe^`bNK`XQi)Pef-fIsRZNsYcP;w z1rVOJpm!16hnCu5v#gQ%B7DEBNj5)H_XJp37dlp)RytcLyUHP+u8r`|8^CE1So-Wa z(-GYrF=qar<}-c^0Ss}~fdOsR)9%XfS3j$D_z)(?H6WBS@K*JV{N`EjE-m|D#k8k( z$3uj6_mcywpkQc`^V>^rxrK?{+sey)39FPFZ0iuaDr)V~#LHe1#j9A)BbwJfy(H@B z0?U!+mgCUDK)i>-Xk+*wT@r5B`b~kHqj11+!>8!$^?UvDYsswVMz&jPZqjCZY%+yc){vvPUzANba`iu+KqzzRNcWfTPp`({`05v)|6 zbA+)PiWwsiRkb`UX?O4sIrM6@W6TI9CMr`^t)SM2D&c^<54}J;RSfc9kocraB1BUU zY3Hfkx&=J%pL2FgP^G4ByU~=bN+mgEqWX2uWRRx(K>Ws_>UIqi2C)V{`%mlvPj64^ zV=h3=-w}Ui!}Fm33XzV0DxM5Bnc!n9-9)%9f;RLx`F#C22q$Z*E5Ie5h`e`XlTKxE z8OJ6qk&8Za^ByKw^CS<1x>xj*;sQlyMZ}f`gh6~%k@PD~F7ZYd-!CH@V<)t8l|XTl z3krrvo{w)vKwrc=rr6H_`T`1%jED!jc~) zS_o~qL`g9SFx-KAOH)%-Zluij?(6MM;SiNZ(@3zCN17=#sfm_*!uVO4xKk7?stfbi zmRt-b&GPnA;Cs#bf(gKW*4c6Q$8e%EB>^ zm8}E|cPEQoGg0nSdulcBi%Th_RJcYeqN)M-8TQ-FaUEwlIriJxj?c^dV(i7RF1R-< zD6y>$UksC4-3V4iu1u$9gY_z7zjiYqPqP^jWQl;mE=~~v#e3u+^iG$thS5Wl<8)3_ zV5vqgGCgu0Z>y9G`XSgz$;F??CB7rO=YIPE9#gF2epWr+w3TbRDG84xyq-+PUi$p& z0b1EBNq1nX0U{MIa!t3KBu%VqC@6?e*P?kEb~xC!g_!*c1x z^NGbsAwq*@9ob(&N*k8#eNO2B0_^D?#rPnC{#t)0B3GjA0!Bav;b z)rJ;8v<4cYhu=OnlnuM7vWX0k<3;7iD#^0F|J)8=79aoEB@Dic1^Pf}pv@-|vxY<1 ziuk{NGH+?IG1oM-)$aNd3V6f^C9%mHdEM(1voD8}_=wc+XZoCjyr*N~3Dx zOO}{QHq4WnQ%ZYx-%X504i2Y(K-ovG24y-I-Y$OG0S)6OO_+aXoSRM`d7aRfijGd2 z;h(&0s0Ux}%L<6KmPgD*d2)iIi@S-P_bPUGe`RExJ9=_5M8_Yc=UX=CFn40q37{M%STR%w<)k+TAkkG*G{uJ zuk89OX23{qK6+uU(1lk5y-Ntb)V^j|Y4O>qH|cpb2!rJ3&IN!=Y7c&kZ$-Ae$fDe0 zX#gPmv*{xt8pc38rOQTc(~Dp56I-Mp zb_-od(^8x*%-whgbKa%fJ1$$cb7czrE}X2bRYrs1{TjqI8oT2VRm5M&?{`UOy|uj%Ddh7#LI`Kl^K=QO(|^&;Vtd-ZACu}SG6a#SAs*zvs0eC)%{*Vv+fTn zpNNSs3in%=mEys6G&fr8hBGL*XHIamM5rrK9Z|o(G`OVQfg66}n(Eelzg^OBpxIeD zyS;&#K@)93!6Qsl!i_^~AL0SqoIpuUVH)!oyJ=jicyXOtDvReW7X_k(1M0u4{bV8a zit<;0#TI>`;7*9>@aA`5h4Xt5{g(jC&D6>HpCxh>XEfxSt6SBok4ik#9FrW%dppqo zQgP4u4aSF-Dd+(O07ygsj@W*KIoTW8yO=o{+M2rCJ6Y?S{NMf{n}7BPv9JT*0Xcyt zmd-9LmUd?Lta3795(=skrg5T*TWpvir*Ri#*0M1C`Xqp8UA={ z;w6ei{jmaEq`G#E8^JN3Q_uY;c83PV#_^O!JslPY;zVC5LvW3UTIe^kkaYU7i=%9= z1!**|B_)wX9OUnHHhV+3UvMb}q7E0J@$(IwfbziC?S+YJ-V&;BvcITg5MnGAFn`G> zC>o#~qpw!gq_a6Jel5!M9=SNWU+z4P$F9b1N>P2(g-?=P6b$<23wpWr5kl>j%hE6h zx;S5!f-fJPn#vAlSU2A43u=h5Q;y4yiGojJl=K|x7Lf{+VRZZtpk-K^u-p+d=iOAr z=ZZw(kjgevp-&&Ks7hIR9iMc`HDx9f@+37C&l*aGz0%DUcW%}m45NyTY8lFGo64$J z4Vo(LhHRTEw_T$0y!v!CSL8(F6XbGv$Rxc)x7o$4sM+qD$+om_BXHhZ+G$2HEM1pT zxtt^gzF|GXMJMV142GWDPTPQ?OH#mp<%_5Im&p4cYbm@hL^l6>4OM=xC(qwsOL+-3 zQE^cr@A#OT4I3BS%ldw;!Z9np09>_45Rc()wnc$0MCMF2NC@ z0h@0dOe0Xdp@_PsU0cv^6g%Stw=@oDcbzBYIf$~IzK5=-tDM-s!{S<5W04J89yR7p zE5>xCc_g;^CV_!aw~Y`~MVF2iPeN3_LHrH*&hOg?794GkSVhwT?Z@SlRqb=DxM8!~ zau=6DT9t=aVhX%4O#Q!h(LW|jXzGFA@b@JBV{rcc$x@e=kdQ+ela_}}pvkf@j*g7g zs<2IStnWA}LS$J$%!8~oD$<}qW?%#>0)xsp+c*dJB**Ob4)V|#>%=wlEE+uv2r?*F zqe9QXD8I9VEF;&TLSMo;3mG1hosl0Y-`?5$kG;w`v-FDn`&BW2kKf6azq?mm>>c!N zOx;XvfG!>`&N9q0av)}f>p?k25Odk~pSsY98yEolTNAQj006GP&!r+Erl=yGG(H@k z#|8-5@D)axrw&~*!lTR?r?)B(CuY={M#QG3h*T8j;+j%@zcqw^_rl-bM^c>n21-VI z`Q}vSgH>08$@mqvfMJQ1FbI(#e322>G~SAsUrenUF|)E|G2A9UGtvFDRRBfuJyIFJ zPwguQyRpPDQpL|)_O|3Olq(K1Vx+Whl0!0cEI^GrB^I|gyj|+`Q~9|O)POe-4;2or zIf$`?hwXtULoUb~(o=xMx4hBxZ5+hzM3UU&GLMFL}6$nSREI_XU zl6gHqS|O#+2Z`(n3SQ|_42-CbcBvM2`>6Z26piZI&a*qxu0?(oiV-cWue{0PfVXqa zvRu*gVt@3}14~2b#P@=GQMIz0Lhbjm$8dUUR6*)y)ima>#{zUq-O8pm-J?eiV{nwiH zpOAkhLH~j@#`{mm|4oVhE6RVf^naoBQ2i&$Uv~drasHc;{|g6;`ftm~|26r4a{Ot0 z{^D5qUHSi;ZpHtaqCZLg6z#uAkm>$5$^S_BpA3I$>|YFRf1lys6!%Z2KNa?Wnd+bz d=>PV&{8Mk0WZ~fd0fYSewD~RE{*3>4`adh~SD^p^ From b7789507935d25a3b58d3f6a063d872612818401 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 09:43:03 +0200 Subject: [PATCH 079/266] Remove old travis pipeline setup --- .travis.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7c84158..0000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: python -python: - - 2.7 - - 3.5 -sudo: false -env: - - DB=SQLite - - DB=Postgres - - DB=MySQL -matrix: - allow_failures: - - env: DB=MySQL -services: - - postgresql - - mysql -install: - - pip install -r requirements.txt - - python setup.py develop - - pip install -q flake8 -before_script: - - psql -c 'create database travis_ci_test;' -U postgres - - mysql -e 'create database my_db_test;' - - mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1';" -uroot -script: -# - flake8 src/ - - if [ $DB == 'SQLite' ]; then (cd test/ && pybot SQLite3_DB_Tests.robot); fi - - if [ $DB == 'Postgres' ]; then (cd test/ && pybot PostgreSQL_DB_Tests.robot); fi - - if [ $DB == 'MySQL' ]; then (cd test/ && pybot MySQL_DB_Tests.robot); fi From 175cffe4406f322fa70c868b0c0b25065ea21d06 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 09:43:53 +0200 Subject: [PATCH 080/266] Clean dependencies file - remove commented modules and RF minimum version --- requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 059a6ec..82fd6ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,2 @@ -#PyMySQL==0.7.4 -#psycopg2==2.6.1 -robotframework>=3.0 +robotframework robotframework-excellib \ No newline at end of file From 3d662f5b955d4b43ca089450f5f8750bacf6626f Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 09:44:05 +0200 Subject: [PATCH 081/266] Bump version to 1.3.0 --- src/DatabaseLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 6937870..9e23359 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = '1.2.4' +VERSION = '1.3.0' From 5f4d5c9f7ff4e7b9352182e9ae331555f1b5b830 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 09:45:20 +0200 Subject: [PATCH 082/266] Remove todo comment from test code --- test/tests/common_tests/custom_connection.robot | 1 - 1 file changed, 1 deletion(-) diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index ab974e5..61cb8ff 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -12,7 +12,6 @@ ${CONNECTION_STRING} ${EMPTY} # the variable is set dynamically depending *** Test Cases *** -# ToDo: custom tests for params and string for oracle and psycopg2 and sqlite (this one supports params only) Connect Using Custom Connection String [Documentation] Connection string provided without additional quotes should work properly. ${Connection String}= Build Connection String From b4004b54392a088e1d77cf8820bdc06d7aaeb039 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 09:50:32 +0200 Subject: [PATCH 083/266] Update link to repository in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fa5c028..51ec257 100755 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ 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', + url = 'https://github.com/MarketSquare/Robotframework-Database-Library', package_dir = { '' : 'src'}, packages = ['DatabaseLibrary'], package_data = {'DatabaseLibrary': []}, From 3720a6be7ce90cf80fac21fb6f053bb58a0c6a7c Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 14:08:03 +0200 Subject: [PATCH 084/266] Update the readme --- README.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3889120..f6a1fab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,78 @@ -Robotframework-Database-Library -=============================== +# Robot Framework Database Library -Database Library contains utilities meant for Robot Framework's usage. This can allow you to query your database after an action has been made to verify the results. This is compatible* with any Database API Specification 2.0 module. +The Database Library for [Robot Framework](https://robotframework.org) 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`. + +The library consists of some keywords designed to perform different checks on your database. +Here you can find the [keyword docs](LINK). + +# Requirements +- Python +- Robot Framework +- Python database module you're going to use - e.g. `oracledb` +# Installation +``` +pip install robotframework-databaselibrary +``` +# Usage example +``` +*** Settings *** +Library DatabaseLibrary +Test Setup Connect To My Oracle DB + +*** Keywords *** +Connect To My Oracle DB + Connect To Database + ... oracledb + ... dbName=db + ... dbUsername=my_user + ... dbPassword=my_pass + ... dbHost=127.0.0.1 + ... dbPort=1521 + +*** Test Cases *** +Person Table Contains Expected Records + ${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 + +Person Table Contains No Joe + ${sql}= Catenate SELECT id FROM person + ... WHERE FIRST_NAME= 'Joe' + Check If Not Exists In Database ${sql} +``` +See more examples in the folder `tests`. +# Database modules compatibility +The library is basically compatible with any [Python Database API Specification 2.0](https://peps.python.org/pep-0249/) 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 +- [oracledb](https://oracle.github.io/python-oracledb/) +- [cx_Oracle](https://oracle.github.io/python-cx_Oracle/) +### MySQL +- [pymysql](https://github.com/PyMySQL/PyMySQL) +- [MySQLdb](https://mysqlclient.readthedocs.io/index.html) +### PostgreSQL +- [psycopg2](https://www.psycopg.org/docs/) +### MS SQL Server +- [pymssql](https://github.com/pymssql/pymssql) +### SQLite +- [sqlite3](https://docs.python.org/3/library/sqlite3.html) +### Teradata +- [teradata](https://github.com/teradata/PyTd) +### IBM DB2 +- [ibm_db](https://github.com/ibmdb/python-ibmdb) +- [ibm_db_dbi](https://github.com/ibmdb/python-ibmdb) +### ODBC +- [pyodbc](https://github.com/mkleehammer/pyodbc) +- [pypyodbc](https://github.com/pypyodbc/pypyodbc) +### Kingbase +- ksycopg2 + +# Further references (partly outdated) +- [List of Python DB interfaces](https://wiki.python.org/moin/DatabaseInterfaces) +- [Python DB programming](https://wiki.python.org/moin/DatabaseProgramming) \ No newline at end of file From 9d26328e8d00b062c1ebe75679c7e72bc410f3e2 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 14 Jul 2023 14:14:55 +0200 Subject: [PATCH 085/266] Improve some docs about local testing --- test/readme.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/readme.md b/test/readme.md index df468b9..c9f9fde 100644 --- a/test/readme.md +++ b/test/readme.md @@ -1,16 +1,19 @@ -# Oracle: +# Some tests are run automatically in the pipeline after pushing in the repository +See the folder `.github/workflows` +# 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 +## 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 +## Teradata +- use VM image, e.g. in VirtualBox - https://downloads.teradata.com/download/database/teradata-express/vmware - use network bridge mode - create new DB @@ -24,18 +27,18 @@ -> new: https://github.com/Teradata/python-driver - docs: https://quickstarts.teradata.com/getting.started.vbox.html -# IBM Db2 +## 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 +## 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 +## 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: From 7ccd4e93681cb590aaeadfae7937f2f29c19aefc Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:51:11 +0200 Subject: [PATCH 086/266] Workflow for GH Pages - publish keyword docs --- .github/workflows/static.yml | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/static.yml diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..6c3001b --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + # Upload entire repository + path: 'doc' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From ad3fcc44ab305744c1643b23f4261c2e3dd7afec Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Mon, 17 Jul 2023 15:54:24 +0200 Subject: [PATCH 087/266] Pages - try uploading the html file --- .github/workflows/static.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 6c3001b..faeb0ff 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -37,7 +37,7 @@ jobs: uses: actions/upload-pages-artifact@v2 with: # Upload entire repository - path: 'doc' + path: 'doc/DatabaseLibrary.html' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 From c3b7fc5fec9ce6fc740595b5204b5ff9f516aff3 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Jul 2023 15:57:26 +0200 Subject: [PATCH 088/266] Pages - try using the index.html --- .github/workflows/static.yml | 2 +- doc/{DatabaseLibrary.html => index.html} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename doc/{DatabaseLibrary.html => index.html} (100%) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index faeb0ff..6c3001b 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -37,7 +37,7 @@ jobs: uses: actions/upload-pages-artifact@v2 with: # Upload entire repository - path: 'doc/DatabaseLibrary.html' + path: 'doc' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 diff --git a/doc/DatabaseLibrary.html b/doc/index.html similarity index 100% rename from doc/DatabaseLibrary.html rename to doc/index.html From 3ff15339a6b5f8ae16becc6b54d7ee93be95cbe6 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Jul 2023 15:59:25 +0200 Subject: [PATCH 089/266] Readme - link to keyword docs on GitHub Pages --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6a1fab..c31e2ae 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The Database Library for [Robot Framework](https://robotframework.org) allows yo It requires an appropriate **Python module to be installed separately** - depending on your database, like e.g. `oracledb` or `pymysql`. The library consists of some keywords designed to perform different checks on your database. -Here you can find the [keyword docs](LINK). +Here you can find the [keyword docs](http://marketsquare.github.io/Robotframework-Database-Library/). # Requirements - Python From 2cef42c0b7331d8d3535cc4cc6096385e7627a8d Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Jul 2023 16:38:40 +0200 Subject: [PATCH 090/266] Update docs --- README.md | 2 +- doc/DatabaseLibrary.html | 912 --------------- doc/index.html | 1855 +++++++++++++++++++++++++++++++ src/DatabaseLibrary/__init__.py | 88 +- 4 files changed, 1904 insertions(+), 953 deletions(-) delete mode 100644 doc/DatabaseLibrary.html create mode 100644 doc/index.html diff --git a/README.md b/README.md index c31e2ae..309a05b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Here you can find the [keyword docs](http://marketsquare.github.io/Robotframewor pip install robotframework-databaselibrary ``` # Usage example -``` +```RobotFramework *** Settings *** Library DatabaseLibrary Test Setup Connect To My Oracle DB diff --git a/doc/DatabaseLibrary.html b/doc/DatabaseLibrary.html deleted file mode 100644 index 058765e..0000000 --- a/doc/DatabaseLibrary.html +++ /dev/null @@ -1,912 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -
-

Opening library documentation failed

-
    -
  • Verify that you have JavaScript enabled in your browser.
  • -
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 8 or newer 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/doc/index.html b/doc/index.html new file mode 100644 index 0000000..6c6c8a6 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,1855 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Opening library documentation failed

+
    +
  • 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/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index b9daabe..1f5f9b2 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -23,46 +23,54 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): """ - Database Library contains utilities meant for Robot Framework's usage. - - This can allow you to query your database after an action has been made to verify the results. - - This is `compatible*` with any Database API Specification 2.0 module. - - - - References: - - + Database API Specification 2.0 - http://www.python.org/dev/peps/pep-0249/ - - + Lists of DB API 2.0 - http://wiki.python.org/moin/DatabaseInterfaces - - + Python Database Programming - http://wiki.python.org/moin/DatabaseProgramming/ - - Notes: - - - - `compatible* - or at least theoretically it should be compatible. Currently tested only with postgresql - (using psycopg2).` - - 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 | + 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`. + + == 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! + + == Usage example == + + | *** Settings *** + | Library DatabaseLibrary + | Test Setup Connect To My Oracle DB + | + | *** Keywords *** + | Connect To My Oracle DB + | Connect To Database + | ... oracledb + | ... dbName=db + | ... dbUsername=my_user + | ... dbPassword=my_pass + | ... dbHost=127.0.0.1 + | ... dbPort=1521 + | + | *** Test Cases *** + | Person Table Contains Expected Records + | ${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 + | + | Person Table Contains No Joe + | ${sql}= Catenate SELECT id FROM person + | ... WHERE FIRST_NAME= 'Joe' + | Check If Not Exists In Database ${sql} + | + + == 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. + + See more on the [https://github.com/MarketSquare/Robotframework-Database-Library|project page on GitHub]. """ ROBOT_LIBRARY_SCOPE = 'GLOBAL' From 38c160d2f03b370b7aab721dbdbcfa4a029c8337 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Jul 2023 16:47:45 +0200 Subject: [PATCH 091/266] Changelog file not needed anymore --- dblib_changelog.md | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100755 dblib_changelog.md diff --git a/dblib_changelog.md b/dblib_changelog.md deleted file mode 100755 index b377238..0000000 --- a/dblib_changelog.md +++ /dev/null @@ -1,29 +0,0 @@ -# Most important changes -- [added support for Teradata 15 & 16](https://github.com/MarketSquare/Robotframework-Database-Library/commit/ec8a62acad8ff63fce3edc07d6110851438bf9c7) - ---> !TESTS ARE OK!!! -- [Solve encoding problem - explicitely use UTF8 when running SQL script files](https://github.com/MarketSquare/Robotframework-Database-Library/commit/3d0709168481e70caf18e61a3ca18f0680473e22) - + https://github.com/MarketSquare/Robotframework-Database-Library/commit/01427be542142e71e85cf8929d988fa4971f980e -- [Support kingbase database](https://github.com/MarketSquare/Robotframework-Database-Library/commit/afddd4bf56def722c5b8e95b96424fd421df80e0) - + https://github.com/MarketSquare/Robotframework-Database-Library/commit/287c3455bd863106fa0466d5e1ee9829d0fff627 - ---> THERE ARE NO TESTS AT ALL - -- pyodbc - allow overriding dbDriver istead of always using "SQL Server" - - https://github.com/MarketSquare/Robotframework-Database-Library/commit/27333ce101222846b0e3dc9ab65b9695245ec340 - - https://github.com/MarketSquare/Robotframework-Database-Library/commit/1a8295e2ac757b7d857dbd13e1e72fb7f05b1eb2 - - https://github.com/MarketSquare/Robotframework-Database-Library/commit/7d719d7b8c79515bacbe2cbff03919766faa5f7b - - https://github.com/MarketSquare/Robotframework-Database-Library/commit/353253022da048259701dd14488245bab730e261 - -- PyODBC Tests: - - https://github.com/MarketSquare/Robotframework-Database-Library/commit/1c7727eade8a1db3a2375463ab3a9752e8664990 - - -- The keyword "Disconnect From Database" wouldn't fail if there was no open connection: - - https://github.com/MarketSquare/Robotframework-Database-Library/commit/d3d0962c3c6eb9a6808371b559a9657bfe125796 - - My last commit - -# Other minor improvements - -# What I have done -- See issues -- Tests and CI -- Set charset when connecting via pyodbc \ No newline at end of file From 011b6f7947e5af18e306777d7009c65461c5c949 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Jul 2023 17:15:37 +0200 Subject: [PATCH 092/266] Update description for PyPi --- setup.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/setup.py b/setup.py index 51ec257..075265b 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,61 @@ except ImportError as error: from distutils.core import setup +long_description = """ +The Database Library for [Robot Framework](https://robotframework.org) 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`. + +The library consists of some keywords designed to perform different checks on your database. +Here you can find the [keyword docs](http://marketsquare.github.io/Robotframework-Database-Library/). + +# 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! + +# Usage example +```RobotFramework +*** Settings *** +Library DatabaseLibrary +Test Setup Connect To My Oracle DB + +*** Keywords *** +Connect To My Oracle DB + Connect To Database + ... oracledb + ... dbName=db + ... dbUsername=my_user + ... dbPassword=my_pass + ... dbHost=127.0.0.1 + ... dbPort=1521 + +*** Test Cases *** +Person Table Contains Expected Records + ${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 + +Person Table Contains No Joe + ${sql}= Catenate SELECT id FROM person + ... WHERE FIRST_NAME= 'Joe' + Check If Not Exists In Database ${sql} +``` + +# Database modules compatibility +The library is basically compatible with any [Python Database API Specification 2.0](https://peps.python.org/pep-0249/) 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. + +See more on the [project page on GitHub](https://github.com/MarketSquare/Robotframework-Database-Library). +""" version_file = join(dirname(abspath(__file__)), 'src', 'DatabaseLibrary', 'version.py') @@ -34,6 +89,8 @@ setup(name = 'robotframework-databaselibrary', version = VERSION, description = 'Database utility library for Robot Framework', + long_description=long_description, + long_description_content_type="text/markdown", author = 'Franz Allan Valencia See', author_email = 'franz.see@gmail.com', url = 'https://github.com/MarketSquare/Robotframework-Database-Library', From 2421f3f88a7d6d6595bd818974c378b04564992e Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Jul 2023 19:18:41 +0200 Subject: [PATCH 093/266] Add dist to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 2b5d413..a9f49bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ +dist/ build/ +*.egg-info/ *.pyc .idea .py*/ -*.egg-info/ **/my_db_test.db logs **/output.xml From f605c72a1c63133255e0c66f5467a1997f140ae7 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Jul 2023 19:55:09 +0200 Subject: [PATCH 094/266] Update package setup info using pyproject.toml --- pyproject.toml | 33 +++++++++++ setup.py | 101 -------------------------------- src/DatabaseLibrary/__init__.py | 2 +- 3 files changed, 34 insertions(+), 102 deletions(-) create mode 100644 pyproject.toml delete mode 100755 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bfbd859 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = [ + "setuptools>=61.0", + "robotframework", + "robotframework-excellib" + ] +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.7" +dependencies = [ + "robotframework", + "robotframework-excellib" +] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", +] +license = {file = "LICENSE"} +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__"} \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100755 index 075265b..0000000 --- a/setup.py +++ /dev/null @@ -1,101 +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 - -long_description = """ -The Database Library for [Robot Framework](https://robotframework.org) 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`. - -The library consists of some keywords designed to perform different checks on your database. -Here you can find the [keyword docs](http://marketsquare.github.io/Robotframework-Database-Library/). - -# 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! - -# Usage example -```RobotFramework -*** Settings *** -Library DatabaseLibrary -Test Setup Connect To My Oracle DB - -*** Keywords *** -Connect To My Oracle DB - Connect To Database - ... oracledb - ... dbName=db - ... dbUsername=my_user - ... dbPassword=my_pass - ... dbHost=127.0.0.1 - ... dbPort=1521 - -*** Test Cases *** -Person Table Contains Expected Records - ${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 - -Person Table Contains No Joe - ${sql}= Catenate SELECT id FROM person - ... WHERE FIRST_NAME= 'Joe' - Check If Not Exists In Database ${sql} -``` - -# Database modules compatibility -The library is basically compatible with any [Python Database API Specification 2.0](https://peps.python.org/pep-0249/) 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. - -See more on the [project page on GitHub](https://github.com/MarketSquare/Robotframework-Database-Library). -""" - -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', - long_description=long_description, - long_description_content_type="text/markdown", - author = 'Franz Allan Valencia See', - author_email = 'franz.see@gmail.com', - url = 'https://github.com/MarketSquare/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 1f5f9b2..59e0991 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -19,7 +19,7 @@ from DatabaseLibrary.assertion import Assertion from DatabaseLibrary.version import VERSION -_version_ = VERSION +__version__ = VERSION class DatabaseLibrary(ConnectionManager, Query, Assertion): """ From 0f3069b55eec8cf2522f719c87f5dcc651e1e996 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 18 Jul 2023 11:06:18 +0200 Subject: [PATCH 095/266] Package building requires no excel-lib --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bfbd859..f20b8b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,7 @@ [build-system] requires = [ "setuptools>=61.0", - "robotframework", - "robotframework-excellib" + "robotframework" ] build-backend = "setuptools.build_meta" From 42ade3e82e1a4ed8f5037a2313ef1798189587ac Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 18 Jul 2023 21:26:38 +0200 Subject: [PATCH 096/266] Package metadata - short license text --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f20b8b1..a726fbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ] -license = {file = "LICENSE"} +license = {text = "Apache License 2.0"} dynamic = ["version"] [project.urls] From 309ef64289425490155ee48db7bcfa0c66c33f2b Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 16:12:55 +0200 Subject: [PATCH 097/266] Bump version to 1.3.1 --- src/DatabaseLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 9e23359..d0e714d 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = '1.3.0' +VERSION = '1.3.1' From a9c4fb498d4234b9ffb9364aaa7d736554bef0fa Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 16:14:14 +0200 Subject: [PATCH 098/266] pyodbc - set port as separate parameter --- src/DatabaseLibrary/connection_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index fb37ca3..2b2834e 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -107,7 +107,7 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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=***)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername)) - self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s:%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, 'utf8mb4' or dbCharset)) + self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, 'utf8mb4' or dbCharset)) 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";)' % ( From 6f090443ab2f6211abd1bc2728bc4e85ad888f1b Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 16:15:17 +0200 Subject: [PATCH 099/266] connect pyodbc - update log message --- src/DatabaseLibrary/connection_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 2b2834e..f2ef385 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -106,7 +106,7 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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=***)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername)) + logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername)) self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, 'utf8mb4' or dbCharset)) elif dbapiModuleName in ["excel"]: logger.info( From f4dcd042315b0ce1acff68bac3c8b568f18c32b2 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 16:26:49 +0200 Subject: [PATCH 100/266] Forgot the charset param in the log string --- src/DatabaseLibrary/connection_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index f2ef385..10f1cc8 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -106,8 +106,9 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None elif dbapiModuleName in ["pyodbc", "pypyodbc"]: dbPort = dbPort or 1433 dbDriver = dbDriver or "{SQL Server}" - logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername)) - self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, 'utf8mb4' or dbCharset)) + dbCharset = dbCharset or 'utf8mb4' + logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername, dbCharset)) + self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, dbCharset)) 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";)' % ( From 54dcba1066e31444c1a357cf201c070328572e76 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 20:34:14 +0200 Subject: [PATCH 101/266] Fix #181 - return to the old 'server,port' syntax in ODBC connection string to support legacy SQL Server driver --- src/DatabaseLibrary/connection_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 10f1cc8..4670d7a 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -107,8 +107,8 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None dbPort = dbPort or 1433 dbDriver = dbDriver or "{SQL Server}" dbCharset = dbCharset or 'utf8mb4' - logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername, dbCharset)) - self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s;PORT=%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, dbCharset)) + logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername, dbCharset)) + self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, dbCharset)) 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";)' % ( From 27866b64f1ded752ac5ac1eccf0a77e6d36d1090 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 20:43:19 +0200 Subject: [PATCH 102/266] Debugging... --- src/DatabaseLibrary/connection_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 4670d7a..7d3fa7e 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -107,6 +107,7 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None dbPort = dbPort or 1433 dbDriver = dbDriver or "{SQL Server}" dbCharset = dbCharset or 'utf8mb4' + # Hope it works logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername, dbCharset)) self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, dbCharset)) elif dbapiModuleName in ["excel"]: From 190d8861d9bc27f3f72af68d1ab74399345326db Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 20:47:07 +0200 Subject: [PATCH 103/266] remove debugging comment --- src/DatabaseLibrary/connection_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 7d3fa7e..4670d7a 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -107,7 +107,6 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None dbPort = dbPort or 1433 dbDriver = dbDriver or "{SQL Server}" dbCharset = dbCharset or 'utf8mb4' - # Hope it works logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername, dbCharset)) self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, dbCharset)) elif dbapiModuleName in ["excel"]: From 7c4cebad2fc0f6e71ff8260fe4d7edd50406144d Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Jul 2023 21:28:27 +0200 Subject: [PATCH 104/266] #181 different syntax for pyodbc connection string based on SQL driver - SQL Server drivers use a comma between server and port, others - a colon --- src/DatabaseLibrary/connection_manager.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 4670d7a..873a4cc 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -105,10 +105,15 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None 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}" dbCharset = dbCharset or 'utf8mb4' - logger.info('Connecting using : %s.connect(DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=***;charset=%s)' % (dbapiModuleName, dbDriver, dbHost, dbPort, dbName, dbUsername, dbCharset)) - self._dbconnection = db_api_2.connect('DRIVER=%s;SERVER=%s,%s;DATABASE=%s;UID=%s;PWD=%s;charset=%s' % (dbDriver, dbHost, dbPort, dbName, dbUsername, dbPassword, dbCharset)) + dbDriver = dbDriver or "{SQL Server}" + con_str = f"DRIVER={dbDriver};DATABASE={dbName};UID={dbUsername};PWD={dbPassword};charset={dbCharset};" + if "mysql" in dbDriver.lower(): + con_str += f"SERVER={dbHost}:{dbPort}" + else: + con_str += f"SERVER={dbHost},{dbPort}" + logger.info(f'Connecting using : {dbapiModuleName}.connect({con_str.replace(dbPassword, "***")})') + self._dbconnection = db_api_2.connect(con_str) 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";)' % ( From 107e70a09a1922847486761ec8b06c5156e5d457 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Tue, 8 Aug 2023 08:57:00 +0200 Subject: [PATCH 105/266] Add precommit configuration and refactor files --- .pre-commit-config.yaml | 11 ++ pyproject.toml | 9 +- src/DatabaseLibrary/__init__.py | 19 +-- src/DatabaseLibrary/assertion.py | 55 ++++--- src/DatabaseLibrary/connection_manager.py | 188 +++++++++++++++------- src/DatabaseLibrary/query.py | 67 ++++---- src/DatabaseLibrary/version.py | 2 +- 7 files changed, 228 insertions(+), 123 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..be00c51 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + name: isort (python) + +- repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black diff --git a/pyproject.toml b/pyproject.toml index a726fbc..2d05e7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,4 +29,11 @@ dynamic = ["version"] "Keyword docs" = "http://marketsquare.github.io/Robotframework-Database-Library/" [tool.setuptools.dynamic] -version = {attr = "DatabaseLibrary.__version__"} \ No newline at end of file +version = {attr = "DatabaseLibrary.__version__"} + +[tool.black] +line-length = 120 + +[tool.isort] +profile = "black" +line_length = 120 diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index 59e0991..efb757f 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -14,17 +14,18 @@ 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 + class DatabaseLibrary(ConnectionManager, Query, Assertion): """ 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`. + It requires an appropriate *Python module to be installed separately* - depending on your database, like e.g. `oracledb` or `pymysql`. == Requirements == - Python @@ -36,11 +37,11 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): Don't forget to install the required Python database module! == Usage example == - + | *** Settings *** | Library DatabaseLibrary | Test Setup Connect To My Oracle DB - | + | | *** Keywords *** | Connect To My Oracle DB | Connect To Database @@ -50,19 +51,19 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): | ... dbPassword=my_pass | ... dbHost=127.0.0.1 | ... dbPort=1521 - | + | | *** Test Cases *** | Person Table Contains Expected Records | ${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 - | + | | Person Table Contains No Joe | ${sql}= Catenate SELECT id FROM person - | ... WHERE FIRST_NAME= 'Joe' + | ... WHERE FIRST_NAME= 'Joe' | Check If Not Exists In Database ${sql} - | + | == Database modules compatibility == The library is basically compatible with any [https://peps.python.org/pep-0249|Python Database API Specification 2.0] module. @@ -73,4 +74,4 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): See more on the [https://github.com/MarketSquare/Robotframework-Database-Library|project page on GitHub]. """ - ROBOT_LIBRARY_SCOPE = 'GLOBAL' + ROBOT_LIBRARY_SCOPE = "GLOBAL" diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 5ecbe55..5d41e52 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -15,7 +15,7 @@ from robot.api import logger -class Assertion(object): +class Assertion: """ Assertion handles all the assertions of Database Library. """ @@ -44,10 +44,11 @@ def check_if_exists_in_database(self, selectStatement, sansTran=False, msg=None) Using optional `msg` to override the default error message: | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | msg=my error message | """ - logger.info (f"Executing : Check If Exists In Database | {selectStatement}") + logger.info(f"Executing : Check If Exists In Database | {selectStatement}") if not self.query(selectStatement, sansTran): - raise AssertionError(msg or f"Expected to have have at least one row, " - f"but got 0 rows from: '{selectStatement}'") + raise AssertionError( + msg or f"Expected to have have at least one row, " f"but got 0 rows from: '{selectStatement}'" + ) def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=None): """ @@ -78,8 +79,9 @@ def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=N logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") queryResults = self.query(selectStatement, sansTran) if queryResults: - raise AssertionError(msg or f"Expected to have have no rows from '{selectStatement}', " - f"but got some rows: {queryResults}") + raise AssertionError( + msg or f"Expected to have have no rows from '{selectStatement}', but got some rows: {queryResults}" + ) def row_count_is_0(self, selectStatement, sansTran=False, msg=None): """ @@ -137,9 +139,10 @@ def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg= """ logger.info(f"Executing : Row Count Is Equal To X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran) - if num_rows != int(numRows.encode('ascii')): - raise AssertionError(msg or f"Expected {numRows} rows, " - f"but {num_rows} were returned from: '{selectStatement}'") + 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, numRows, sansTran=False, msg=None): """ @@ -168,9 +171,10 @@ def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, """ logger.info(f"Executing : Row Count Is Greater Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran) - if num_rows <= int(numRows.encode('ascii')): - raise AssertionError(msg or f"Expected more than {numRows} rows, " - f"but {num_rows} were returned from '{selectStatement}'") + 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, numRows, sansTran=False, msg=None): """ @@ -199,9 +203,10 @@ def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg """ logger.info(f"Executing : Row Count Is Less Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran) - if num_rows >= int(numRows.encode('ascii')): - raise AssertionError(msg or f"Expected less than {numRows} rows, " - f"but {num_rows} were returned from '{selectStatement}'") + if num_rows >= int(numRows.encode("ascii")): + raise AssertionError( + msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" + ) def table_must_exist(self, tableName, sansTran=False, msg=None): """ @@ -223,30 +228,34 @@ def table_must_exist(self, tableName, sansTran=False, msg=None): Using optional `msg` to override the default error message: | Table Must Exist | first_name | msg=my error message | """ - logger.info('Executing : Table Must Exist | %s ' % tableName) + logger.info("Executing : Table Must Exist | %s " % tableName) if self.db_api_module_name in ["cx_Oracle", "oracledb"]: - selectStatement = ("SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('%s')" % tableName) + selectStatement = ( + "SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND " + "owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('%s')" % tableName + ) table_exists = self.row_count(selectStatement, sansTran) > 0 elif self.db_api_module_name in ["sqlite3"]: - selectStatement = ("SELECT name FROM sqlite_master WHERE type='table' AND name='%s' COLLATE NOCASE" % tableName) + selectStatement = ( + "SELECT name FROM sqlite_master WHERE type='table' AND name='%s' COLLATE NOCASE" % tableName + ) table_exists = self.row_count(selectStatement, sansTran) > 0 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) + selectStatement = "SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('%s')" % tableName table_exists = self.row_count(selectStatement, sansTran) > 0 elif self.db_api_module_name in ["teradata"]: - selectStatement = ("SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='%s'" % tableName) + selectStatement = "SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='%s'" % tableName table_exists = self.row_count(selectStatement, sansTran) > 0 else: try: - selectStatement = (f"SELECT * FROM information_schema.tables WHERE table_name='{tableName}'") + selectStatement = f"SELECT * FROM information_schema.tables WHERE table_name='{tableName}'" table_exists = self.row_count(selectStatement, sansTran) > 0 except: logger.info("Database doesn't support information schema, try using a simple SQL request") try: - selectStatement = (f"SELECT 1 from {tableName} where 1=0") + selectStatement = f"SELECT 1 from {tableName} where 1=0" num_rows = self.row_count(selectStatement, sansTran) table_exists = True except: table_exists = False assert table_exists, msg or f"Table '{tableName}' does not exist in the db" - diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 873a4cc..09d9e71 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -22,7 +22,7 @@ from robot.api import logger -class ConnectionManager(object): +class ConnectionManager: """ Connection Manager handles the connection & disconnection to the database. """ @@ -35,7 +35,18 @@ def __init__(self): self.db_api_module_name = None self.omit_trailing_semicolon = False - def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None, dbPassword=None, dbHost=None, dbPort=None, dbCharset=None, dbDriver=None, dbConfigFile=None): + def connect_to_database( + self, + dbapiModuleName=None, + dbName=None, + dbUsername=None, + dbPassword=None, + dbHost=None, + dbPort=None, + dbCharset=None, + dbDriver=None, + dbConfigFile=None, + ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using `dbName`, `dbUsername`, and `dbPassword`. @@ -81,12 +92,12 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None config = ConfigParser.ConfigParser() config.read([dbConfigFile]) - 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')) + 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")) if dbapiModuleName == "excel" or dbapiModuleName == "excelrw": self.db_api_module_name = "pyodbc" @@ -97,15 +108,34 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None if dbapiModuleName in ["MySQLdb", "pymysql"]: dbPort = dbPort or 3306 - logger.info('Connecting using : %s.connect(db=%s, user=%s, passwd=***, host=%s, port=%s, charset=%s) ' % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort, dbCharset)) - self._dbconnection = db_api_2.connect(db=dbName, user=dbUsername, passwd=dbPassword, host=dbHost, port=dbPort, charset='utf8mb4' or dbCharset) + logger.info( + "Connecting using : %s.connect(db=%s, user=%s, passwd=***, host=%s, port=%s, charset=%s) " + % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort, dbCharset) + ) + self._dbconnection = db_api_2.connect( + db=dbName, + user=dbUsername, + passwd=dbPassword, + host=dbHost, + port=dbPort, + charset="utf8mb4" or dbCharset, + ) elif dbapiModuleName in ["psycopg2"]: dbPort = dbPort or 5432 - logger.info('Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort)) - self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort) + logger.info( + "Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) " + % (dbapiModuleName, dbName, dbUsername, 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 - dbCharset = dbCharset or 'utf8mb4' + dbCharset = dbCharset or "utf8mb4" dbDriver = dbDriver or "{SQL Server}" con_str = f"DRIVER={dbDriver};DATABASE={dbName};UID={dbUsername};PWD={dbPassword};charset={dbCharset};" if "mysql" in dbDriver.lower(): @@ -116,38 +146,61 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None self._dbconnection = db_api_2.connect(con_str) 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)) + '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) + '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)) + '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) + '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=***;) ' % (dbapiModuleName, dbName, dbHost, dbPort, dbUsername)) - self._dbconnection = db_api_2.connect('DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=%s;' % (dbName, dbHost, dbPort, dbUsername, dbPassword), '', '') + logger.info( + "Connecting using : %s.connect(DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=***;) " + % (dbapiModuleName, dbName, dbHost, dbPort, dbUsername) + ) + 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=***, dsn=%s) ' % (dbapiModuleName, dbUsername, oracle_dsn)) + oracle_dsn = db_api_2.makedsn(host=dbHost, port=dbPort, service_name=dbName) + logger.info( + "Connecting using: %s.connect(user=%s, password=***, dsn=%s) " + % (dbapiModuleName, dbUsername, oracle_dsn) + ) self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, dsn=oracle_dsn) self.omit_trailing_semicolon = True elif dbapiModuleName in ["oracledb"]: dbPort = dbPort or 1521 - oracle_connection_params = db_api_2.ConnectParams(host=dbHost, port=dbPort, service_name=dbName) - logger.info('Connecting using: %s.connect(user=%s, password=***, params=%s) ' % (dbapiModuleName, dbUsername, oracle_connection_params)) + oracle_connection_params = db_api_2.ConnectParams(host=dbHost, port=dbPort, service_name=dbName) + logger.info( + "Connecting using: %s.connect(user=%s, password=***, params=%s) " + % (dbapiModuleName, dbUsername, oracle_connection_params) + ) self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) self.omit_trailing_semicolon = True elif dbapiModuleName in ["teradata"]: dbPort = dbPort 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=***, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort)) + logger.info( + "Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) " + % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort) + ) self._dbconnection = teradata_udaExec.connect( method="odbc", system=dbHost, @@ -155,28 +208,44 @@ def connect_to_database(self, dbapiModuleName=None, dbName=None, dbUsername=None username=dbUsername, password=dbPassword, host=dbHost, - port=dbPort + port=dbPort, ) elif dbapiModuleName in ["ksycopg2"]: dbPort = dbPort or 54321 - logger.info('Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) ' % ( - dbapiModuleName, dbName, dbUsername, dbHost, dbPort)) - self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost, - port=dbPort) + logger.info( + "Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) " + % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort) + ) + self._dbconnection = db_api_2.connect( + database=dbName, + user=dbUsername, + password=dbPassword, + host=dbHost, + port=dbPort, + ) else: - logger.info('Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) ' % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort)) - self._dbconnection = db_api_2.connect(database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort) + logger.info( + "Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) " + % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort) + ) + self._dbconnection = db_api_2.connect( + database=dbName, + user=dbUsername, + password=dbPassword, + host=dbHost, + port=dbPort, + ) - def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_connect_string=''): + def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_connect_string=""): """ Loads the DB API 2.0 module given `dbapiModuleName` 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. - Example usage: + Example usage: | 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" | @@ -185,36 +254,41 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne db_api_2 = importlib.import_module(dbapiModuleName) self.db_api_module_name = dbapiModuleName - db_connect_string = f'db_api_2.connect({db_connect_string})' + db_connect_string = f"db_api_2.connect({db_connect_string})" connection_string_with_hidden_pass = db_connect_string - for pass_param_name in ['pass', 'passwd', 'password', 'pwd', 'PWD']: - splitted = connection_string_with_hidden_pass.split(pass_param_name + '=') + for pass_param_name in ["pass", "passwd", "password", "pwd", "PWD"]: + splitted = connection_string_with_hidden_pass.split(pass_param_name + "=") if len(splitted) < 2: continue - splitted = splitted[1].split(',') + splitted = splitted[1].split(",") value_to_hide = splitted[0] connection_string_with_hidden_pass = connection_string_with_hidden_pass.replace(value_to_hide, "***") - logger.info('Executing : Connect To Database Using Custom Params : %s.connect(%s) ' % (dbapiModuleName, connection_string_with_hidden_pass)) + logger.info( + "Executing : Connect To Database Using Custom Params : %s.connect(%s) " + % (dbapiModuleName, connection_string_with_hidden_pass) + ) self._dbconnection = eval(db_connect_string) - def connect_to_database_using_custom_connection_string(self, dbapiModuleName=None, db_connect_string=''): + def connect_to_database_using_custom_connection_string(self, dbapiModuleName=None, db_connect_string=""): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using the `db_connect_string` (parsed as single connection connection string or URI). - + Use `connect_to_database_using_custom_params` for passing connection params as named arguments. - Example usage: + 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" | + | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb" | """ db_api_2 = importlib.import_module(dbapiModuleName) self.db_api_module_name = dbapiModuleName - logger.info(f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect('{db_connect_string}')") + logger.info( + f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect('{db_connect_string}')" + ) self._dbconnection = db_api_2.connect(db_connect_string) def disconnect_from_database(self, error_if_no_connection=False): @@ -227,7 +301,7 @@ def disconnect_from_database(self, error_if_no_connection=False): For example: | Disconnect From Database | # disconnects from current connection to the database | """ - logger.info('Executing : Disconnect From Database') + logger.info("Executing : Disconnect From Database") if self._dbconnection is None: log_msg = "No open database connection to close" if error_if_no_connection: @@ -239,19 +313,19 @@ def disconnect_from_database(self, error_if_no_connection=False): 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 + 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 | Set Auto Commit | # Explicitly set the desired state | Set Auto Commit | False """ - logger.info('Executing : Set Auto Commit') + logger.info("Executing : Set Auto Commit") self._dbconnection.autocommit = autoCommit diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 9c979b7..927031d 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys import inspect +import sys + from robot.api import logger -class Query(object): +class Query: """ Query handles all the querying done by the Database Library. """ @@ -59,7 +60,7 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): cur = None try: cur = self._dbconnection.cursor() - logger.info('Executing : Query | %s ' % selectStatement) + logger.info("Executing : Query | %s " % selectStatement) self.__execute_sql(cur, selectStatement) allRows = cur.fetchall() @@ -110,7 +111,7 @@ def row_count(self, selectStatement, sansTran=False): cur = None try: cur = self._dbconnection.cursor() - logger.info('Executing : Row Count | %s ' % selectStatement) + logger.info("Executing : Row Count | %s " % selectStatement) self.__execute_sql(cur, selectStatement) data = cur.fetchall() if self.db_api_module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: @@ -147,12 +148,12 @@ def description(self, selectStatement, sansTran=False): cur = None try: cur = self._dbconnection.cursor() - logger.info('Executing : Description | %s ' % selectStatement) + logger.info("Executing : Description | %s " % selectStatement) self.__execute_sql(cur, selectStatement) 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: @@ -179,10 +180,10 @@ def delete_all_rows_from_table(self, tableName, sansTran=False): | Delete All Rows From Table | person | True | """ cur = None - selectStatement = ("DELETE FROM %s" % tableName) + selectStatement = "DELETE FROM %s" % tableName try: cur = self._dbconnection.cursor() - logger.info('Executing : Delete All Rows From Table | %s ' % selectStatement) + logger.info("Executing : Delete All Rows From Table | %s " % selectStatement) result = self.__execute_sql(cur, selectStatement) if result is not None: if not sansTran: @@ -201,7 +202,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): 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 | @@ -253,28 +254,28 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | """ - with open(sqlScriptFileName, encoding='UTF-8') as sql_file: + with open(sqlScriptFileName, encoding="UTF-8") as sql_file: cur = None try: statements_to_execute = [] cur = self._dbconnection.cursor() - logger.info('Executing : Execute SQL Script | %s ' % sqlScriptFileName) - current_statement = '' + logger.info("Executing : Execute SQL Script | %s " % sqlScriptFileName) + current_statement = "" inside_statements_group = False for line in sql_file: line = line.strip() - if line.startswith('#') or line.startswith('--') or line == "/": + if line.startswith("#") or line.startswith("--") or line == "/": continue if 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(';') + sqlFragments = line.split(";") # no semicolons if len(sqlFragments) == 1: - current_statement += line + ' ' + current_statement += line + " " continue quotes = 0 # "select * from person;" -> ["select..", ""] @@ -289,7 +290,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): inside_statements_group = False 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("\\'") @@ -297,17 +298,17 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): 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 = '' + current_statement = "" quotes = 0 current_statement = current_statement.strip() if len(current_statement) != 0: statements_to_execute.append(current_statement) - + for statement in statements_to_execute: logger.info(f"Executing statement from script file: {statement}") omit_semicolon = not statement.lower().endswith("end;") @@ -338,7 +339,7 @@ def execute_sql_string(self, sqlString, sansTran=False): cur = None try: cur = self._dbconnection.cursor() - logger.info('Executing : Execute SQL String | %s ' % sqlString) + logger.info("Executing : Execute SQL String | %s " % sqlString) self.__execute_sql(cur, sqlString) if not sansTran: self._dbconnection.commit() @@ -364,7 +365,7 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): E.g. calling a procedure in *PostgreSQL* returns even a single value of an OUT param as a result set. Set optional input `sansTran` to True to run command without an explicit transaction commit or rollback. - + Simple example: | @{Params} = | Create List | Jerry | out_second_name | | @{Param values} @{Result sets} = | Call Stored Procedure | Get_second_name | ${Params} | @@ -390,15 +391,15 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): spParams = [] cur = None try: - logger.info('Executing : Call Stored Procedure | %s | %s ' % (spName, spParams)) + logger.info("Executing : Call Stored Procedure | %s | %s " % (spName, spParams)) if self.db_api_module_name == "pymssql": cur = self._dbconnection.cursor(as_dict=False) else: cur = self._dbconnection.cursor() - + param_values = [] result_sets = [] - + if self.db_api_module_name == "pymysql": cur.callproc(spName, spParams) @@ -419,7 +420,7 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): elif self.db_api_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= spParams.copy() + params_substituted = spParams.copy() cursor_params = [] for i in range(0, len(spParams)): if spParams[i] == "CURSOR": @@ -434,7 +435,7 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): cur = self._dbconnection.cursor() # check if "CURSOR" params were passed - they will be replaced # with cursor variables for storing the result sets - params_substituted= spParams.copy() + params_substituted = spParams.copy() cursor_params = [] for i in range(0, len(spParams)): if spParams[i] == "CURSOR": @@ -453,13 +454,15 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): while result_sets_available: result_sets.append(list(cur.fetchall())) result_sets_available = cur.nextset() - else: + else: result_set = cur.fetchall() result_sets.append(list(result_set)) else: - logger.info(f"CAUTION! Calling a stored procedure for '{self.db_api_module_name}' is not tested, " - "results might be invalid!") + logger.info( + f"CAUTION! Calling a stored procedure for '{self.db_api_module_name}' is not tested, " + "results might be invalid!" + ) cur = self._dbconnection.cursor() param_values = cur.callproc(spName, spParams) logger.info("Reading the procedure results..") @@ -470,11 +473,11 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): result_set.append(row) if result_set: result_sets.append(list(result_set)) - if hasattr(cur, 'nextset') and inspect.isroutine(cur.nextset): + if hasattr(cur, "nextset") and inspect.isroutine(cur.nextset): result_sets_available = cur.nextset() else: result_sets_available = False - + if not sansTran: self._dbconnection.commit() diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index d0e714d..4cf03a8 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = '1.3.1' +VERSION = "1.3.1" From dc7e76209be836c49b2bd325ea03777c9b3e54d3 Mon Sep 17 00:00:00 2001 From: amochin Date: Sun, 5 Nov 2023 13:34:37 +0100 Subject: [PATCH 106/266] remove deprecated python linting setting --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c6a49df..65332e6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,8 +8,5 @@ "robot.interactiveConsole.arguments": [ "--output", "${workspaceRoot}/logs/interactive_console.xml" ], - "python.linting.pylintEnabled": true, - "python.linting.enabled": true, - "python.formatting.provider": "black", "python.analysis.completeFunctionParens": true, } \ No newline at end of file From d71fb8c0b52ea4ec8c4e3e0926c3333eafd54476 Mon Sep 17 00:00:00 2001 From: Rudolf-AT <69890168+Rudolf-AT@users.noreply.github.com> Date: Sun, 5 Nov 2023 13:39:07 +0100 Subject: [PATCH 107/266] Option "driverMode" to support oracledb "thick" mode --- src/DatabaseLibrary/connection_manager.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 09d9e71..1379307 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -46,6 +46,7 @@ def connect_to_database( dbCharset=None, dbDriver=None, dbConfigFile=None, + driverMode=None, ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -98,6 +99,7 @@ def connect_to_database( 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")) + driverMode = driverMode or config.get("default", "driverMode") if dbapiModuleName == "excel" or dbapiModuleName == "excelrw": self.db_api_module_name = "pyodbc" @@ -192,6 +194,12 @@ def connect_to_database( "Connecting using: %s.connect(user=%s, password=***, params=%s) " % (dbapiModuleName, dbUsername, oracle_connection_params) ) + if "thick" in driverMode.lower(): + mode_param = driverMode.lower().split(",lib_dir=") + if len(mode_param) == 2 and mode_param[0].lower() == "thick": + db_api_2.init_oracle_client(lib_dir=mode_param[1]) + else: + db_api_2.init_oracle_client() self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) self.omit_trailing_semicolon = True elif dbapiModuleName in ["teradata"]: From e5b9ae94d84f38e765c662fc4b2015c98264d688 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:02:27 +0100 Subject: [PATCH 108/266] Replace string %s with fstrings --- src/DatabaseLibrary/assertion.py | 12 ++--- src/DatabaseLibrary/connection_manager.py | 62 +++++++++++------------ src/DatabaseLibrary/query.py | 16 +++--- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 5d41e52..186e021 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -228,23 +228,21 @@ def table_must_exist(self, tableName, sansTran=False, msg=None): Using optional `msg` to override the default error message: | Table Must Exist | first_name | msg=my error message | """ - logger.info("Executing : Table Must Exist | %s " % tableName) + logger.info(f"Executing : Table Must Exist | {tableName}") if self.db_api_module_name in ["cx_Oracle", "oracledb"]: selectStatement = ( "SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND " - "owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('%s')" % tableName + f"owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('{tableName}')" ) table_exists = self.row_count(selectStatement, sansTran) > 0 elif self.db_api_module_name in ["sqlite3"]: - selectStatement = ( - "SELECT name FROM sqlite_master WHERE type='table' AND name='%s' COLLATE NOCASE" % tableName - ) + selectStatement = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}' COLLATE NOCASE" table_exists = self.row_count(selectStatement, sansTran) > 0 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 + selectStatement = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{tableName}')" table_exists = self.row_count(selectStatement, sansTran) > 0 elif self.db_api_module_name in ["teradata"]: - selectStatement = "SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='%s'" % tableName + selectStatement = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{tableName}'" table_exists = self.row_count(selectStatement, sansTran) > 0 else: try: diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 09d9e71..27572ef 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -109,8 +109,8 @@ def connect_to_database( if dbapiModuleName in ["MySQLdb", "pymysql"]: dbPort = dbPort or 3306 logger.info( - "Connecting using : %s.connect(db=%s, user=%s, passwd=***, host=%s, port=%s, charset=%s) " - % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort, dbCharset) + f"Connecting using : {dbapiModuleName}.connect(" + f"db={dbName}, user={dbUsername}, passwd=***, host={dbHost}, port={dbPort}, charset={dbCharset})" ) self._dbconnection = db_api_2.connect( db=dbName, @@ -123,8 +123,8 @@ def connect_to_database( elif dbapiModuleName in ["psycopg2"]: dbPort = dbPort or 5432 logger.info( - "Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) " - % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort) + f"Connecting using : {dbapiModuleName}.connect(" + f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort})" ) self._dbconnection = db_api_2.connect( database=dbName, @@ -146,42 +146,39 @@ def connect_to_database( self._dbconnection = db_api_2.connect(con_str) 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) + f"Connecting using : {dbapiModuleName}.connect(" + f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" + f'ReadOnly=1;Extended Properties="Excel 8.0;HDR=YES";)' ) 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), + f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" + f'ReadOnly=1;Extended Properties="Excel 8.0;HDR=YES";)', 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) + f"Connecting using : {dbapiModuleName}.connect(" + f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" + f'ReadOnly=0;Extended Properties="Excel 8.0;HDR=YES";)', ) 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), + f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" + f'ReadOnly=0;Extended Properties="Excel 8.0;HDR=YES";)', 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=***;) " - % (dbapiModuleName, dbName, dbHost, dbPort, dbUsername) - ) + conn_str = f"DATABASE={dbName};HOSTNAME={dbHost};PORT={dbPort};PROTOCOL=TCPIP;UID={dbUsername};" + logger.info(f"Connecting using : {dbapiModuleName}.connect(" f"{conn_str};PWD=***;)") self._dbconnection = db_api_2.connect( - "DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=%s;" - % (dbName, dbHost, dbPort, dbUsername, dbPassword), - "", + f"{conn_str};PWD={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=***, dsn=%s) " - % (dbapiModuleName, dbUsername, oracle_dsn) + f"Connecting using: {dbapiModuleName}.connect(user={dbUsername}, password=***, dsn={oracle_dsn})" ) self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, dsn=oracle_dsn) self.omit_trailing_semicolon = True @@ -189,8 +186,8 @@ def connect_to_database( dbPort = dbPort or 1521 oracle_connection_params = db_api_2.ConnectParams(host=dbHost, port=dbPort, service_name=dbName) logger.info( - "Connecting using: %s.connect(user=%s, password=***, params=%s) " - % (dbapiModuleName, dbUsername, oracle_connection_params) + f"Connecting using: {dbapiModuleName}.connect(" + f"user={dbUsername}, password=***, params={oracle_connection_params})" ) self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) self.omit_trailing_semicolon = True @@ -198,8 +195,8 @@ def connect_to_database( dbPort = dbPort 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=***, host=%s, port=%s) " - % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort) + f"Connecting using : {dbapiModuleName}.connect(" + f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort})" ) self._dbconnection = teradata_udaExec.connect( method="odbc", @@ -213,8 +210,8 @@ def connect_to_database( elif dbapiModuleName in ["ksycopg2"]: dbPort = dbPort or 54321 logger.info( - "Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) " - % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort) + f"Connecting using : {dbapiModuleName}.connect(" + f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort})" ) self._dbconnection = db_api_2.connect( database=dbName, @@ -225,8 +222,8 @@ def connect_to_database( ) else: logger.info( - "Connecting using : %s.connect(database=%s, user=%s, password=***, host=%s, port=%s) " - % (dbapiModuleName, dbName, dbUsername, dbHost, dbPort) + f"Connecting using : {dbapiModuleName}.connect(" + f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort}) " ) self._dbconnection = db_api_2.connect( database=dbName, @@ -265,8 +262,8 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne value_to_hide = splitted[0] connection_string_with_hidden_pass = connection_string_with_hidden_pass.replace(value_to_hide, "***") logger.info( - "Executing : Connect To Database Using Custom Params : %s.connect(%s) " - % (dbapiModuleName, connection_string_with_hidden_pass) + f"Executing : Connect To Database Using Custom Params : {dbapiModuleName}.connect(" + f"{connection_string_with_hidden_pass})" ) self._dbconnection = eval(db_connect_string) @@ -287,7 +284,8 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non db_api_2 = importlib.import_module(dbapiModuleName) self.db_api_module_name = dbapiModuleName logger.info( - f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect('{db_connect_string}')" + f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect(" + f"'{db_connect_string}')" ) self._dbconnection = db_api_2.connect(db_connect_string) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 927031d..57b4758 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -60,7 +60,7 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): cur = None try: cur = self._dbconnection.cursor() - logger.info("Executing : Query | %s " % selectStatement) + logger.info(f"Executing : Query | {selectStatement}") self.__execute_sql(cur, selectStatement) allRows = cur.fetchall() @@ -111,7 +111,7 @@ def row_count(self, selectStatement, sansTran=False): cur = None try: cur = self._dbconnection.cursor() - logger.info("Executing : Row Count | %s " % selectStatement) + logger.info(f"Executing : Row Count | {selectStatement}") self.__execute_sql(cur, selectStatement) data = cur.fetchall() if self.db_api_module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: @@ -148,7 +148,7 @@ def description(self, selectStatement, sansTran=False): cur = None try: cur = self._dbconnection.cursor() - logger.info("Executing : Description | %s " % selectStatement) + logger.info("Executing : Description | {selectStatement}") self.__execute_sql(cur, selectStatement) description = list(cur.description) if sys.version_info[0] < 3: @@ -180,10 +180,10 @@ def delete_all_rows_from_table(self, tableName, sansTran=False): | Delete All Rows From Table | person | True | """ cur = None - selectStatement = "DELETE FROM %s" % tableName + selectStatement = f"DELETE FROM {tableName}" try: cur = self._dbconnection.cursor() - logger.info("Executing : Delete All Rows From Table | %s " % selectStatement) + logger.info(f"Executing : Delete All Rows From Table | {selectStatement}") result = self.__execute_sql(cur, selectStatement) if result is not None: if not sansTran: @@ -259,7 +259,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): try: statements_to_execute = [] cur = self._dbconnection.cursor() - logger.info("Executing : Execute SQL Script | %s " % sqlScriptFileName) + logger.info(f"Executing : Execute SQL Script | {sqlScriptFileName}") current_statement = "" inside_statements_group = False @@ -339,7 +339,7 @@ def execute_sql_string(self, sqlString, sansTran=False): cur = None try: cur = self._dbconnection.cursor() - logger.info("Executing : Execute SQL String | %s " % sqlString) + logger.info(f"Executing : Execute SQL String | {sqlString}") self.__execute_sql(cur, sqlString) if not sansTran: self._dbconnection.commit() @@ -391,7 +391,7 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): spParams = [] cur = None try: - logger.info("Executing : Call Stored Procedure | %s | %s " % (spName, spParams)) + logger.info(f"Executing : Call Stored Procedure | {spName} | {spParams}") if self.db_api_module_name == "pymssql": cur = self._dbconnection.cursor(as_dict=False) else: From 439d33e939023a59c974a622dde9f0ab4ddfa590 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:15:18 +0100 Subject: [PATCH 109/266] Docs fixes --- src/DatabaseLibrary/assertion.py | 2 +- src/DatabaseLibrary/connection_manager.py | 14 ++++++++------ src/DatabaseLibrary/query.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 186e021..4d00825 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -107,7 +107,7 @@ def row_count_is_0(self, selectStatement, sansTran=False, msg=None): Using optional `msg` to override the default error message: | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ - logger.info(f"Executing : Row Count Is 0 | selectStatement") + logger.info(f"Executing : Row Count Is 0 | {selectStatement}") num_rows = self.row_count(selectStatement, sansTran) if num_rows > 0: raise AssertionError(msg or f"Expected 0 rows, but {num_rows} were returned from: '{selectStatement}'") diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 27572ef..de7aa86 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -171,7 +171,8 @@ def connect_to_database( conn_str = f"DATABASE={dbName};HOSTNAME={dbHost};PORT={dbPort};PROTOCOL=TCPIP;UID={dbUsername};" logger.info(f"Connecting using : {dbapiModuleName}.connect(" f"{conn_str};PWD=***;)") self._dbconnection = db_api_2.connect( - f"{conn_str};PWD={dbPassword};" "", + f"{conn_str};PWD={dbPassword};", + "", "", ) elif dbapiModuleName in ["cx_Oracle"]: @@ -279,7 +280,7 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non 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" | + | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb | """ db_api_2 = importlib.import_module(dbapiModuleName) self.db_api_module_name = dbapiModuleName @@ -292,11 +293,12 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non def disconnect_from_database(self, error_if_no_connection=False): """ Disconnects from the database. - By default it's not an error if there was no open database connection - + + 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. + However, you can enforce it using the `error_if_no_connection` parameter. - For example: + Example usage: | Disconnect From Database | # disconnects from current connection to the database | """ logger.info("Executing : Disconnect From Database") @@ -319,7 +321,7 @@ def set_auto_commit(self, autoCommit=True): or database snapshot. By turning on auto commit on the database connection these actions can be performed. - Example: + Example usage: | # Default behaviour, sets auto commit to true | Set Auto Commit | # Explicitly set the desired state diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 57b4758..423823a 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -490,10 +490,10 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): def __execute_sql(self, cur, sql_statement, omit_trailing_semicolon=None): """ Runs the `sql_statement` using `cur` as Cursor object. - Use `omit_trailing_semicolon` parameter (bool) for explicite instruction, + 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. + Otherwise, it's decided based on the current database module in use. """ if omit_trailing_semicolon is None: omit_trailing_semicolon = self.omit_trailing_semicolon From 7d7ab0237062341351503369577be746178afd87 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:18:09 +0100 Subject: [PATCH 110/266] Normalize local variable naming --- src/DatabaseLibrary/assertion.py | 30 +++++++++++++++--------------- src/DatabaseLibrary/query.py | 28 +++++++++++++--------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 4d00825..f798337 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -77,10 +77,10 @@ def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=N | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") - queryResults = self.query(selectStatement, sansTran) - if queryResults: + query_results = self.query(selectStatement, sansTran) + if query_results: raise AssertionError( - msg or f"Expected to have have no rows from '{selectStatement}', but got some rows: {queryResults}" + msg or f"Expected to have have no rows from '{selectStatement}', but got some rows: {query_results}" ) def row_count_is_0(self, selectStatement, sansTran=False, msg=None): @@ -230,29 +230,29 @@ def table_must_exist(self, tableName, sansTran=False, msg=None): """ logger.info(f"Executing : Table Must Exist | {tableName}") if self.db_api_module_name in ["cx_Oracle", "oracledb"]: - selectStatement = ( + query = ( "SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND " f"owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('{tableName}')" ) - table_exists = self.row_count(selectStatement, sansTran) > 0 + table_exists = self.row_count(query, sansTran) > 0 elif self.db_api_module_name in ["sqlite3"]: - selectStatement = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}' COLLATE NOCASE" - table_exists = self.row_count(selectStatement, sansTran) > 0 + query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}' COLLATE NOCASE" + table_exists = self.row_count(query, sansTran) > 0 elif self.db_api_module_name in ["ibm_db", "ibm_db_dbi"]: - selectStatement = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{tableName}')" - table_exists = self.row_count(selectStatement, sansTran) > 0 + query = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{tableName}')" + table_exists = self.row_count(query, sansTran) > 0 elif self.db_api_module_name in ["teradata"]: - selectStatement = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{tableName}'" - table_exists = self.row_count(selectStatement, sansTran) > 0 + query = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{tableName}'" + table_exists = self.row_count(query, sansTran) > 0 else: try: - selectStatement = f"SELECT * FROM information_schema.tables WHERE table_name='{tableName}'" - table_exists = self.row_count(selectStatement, sansTran) > 0 + query = f"SELECT * FROM information_schema.tables WHERE table_name='{tableName}'" + table_exists = self.row_count(query, sansTran) > 0 except: logger.info("Database doesn't support information schema, try using a simple SQL request") try: - selectStatement = f"SELECT 1 from {tableName} where 1=0" - num_rows = self.row_count(selectStatement, sansTran) + query = f"SELECT 1 from {tableName} where 1=0" + num_rows = self.row_count(query, sansTran) table_exists = True except: table_exists = False diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 423823a..3a010f6 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -62,20 +62,20 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): cur = self._dbconnection.cursor() logger.info(f"Executing : Query | {selectStatement}") self.__execute_sql(cur, selectStatement) - allRows = cur.fetchall() + all_rows = cur.fetchall() if returnAsDict: - mappedRows = [] + mapped_rows = [] col_names = [c[0] for c in cur.description] - for rowIdx in range(len(allRows)): + for rowIdx in range(len(all_rows)): d = {} - for colIdx in range(len(allRows[rowIdx])): - d[col_names[colIdx]] = allRows[rowIdx][colIdx] - mappedRows.append(d) - return mappedRows + for colIdx in range(len(all_rows[rowIdx])): + d[col_names[colIdx]] = all_rows[rowIdx][colIdx] + mapped_rows.append(d) + return mapped_rows - return allRows + return all_rows finally: if cur: if not sansTran: @@ -115,10 +115,8 @@ def row_count(self, selectStatement, sansTran=False): self.__execute_sql(cur, selectStatement) data = cur.fetchall() if self.db_api_module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: - rowCount = len(data) - else: - rowCount = cur.rowcount - return rowCount + return len(data) + return cur.rowcount finally: if cur: if not sansTran: @@ -180,11 +178,11 @@ def delete_all_rows_from_table(self, tableName, sansTran=False): | Delete All Rows From Table | person | True | """ cur = None - selectStatement = f"DELETE FROM {tableName}" + query = f"DELETE FROM {tableName}" try: cur = self._dbconnection.cursor() - logger.info(f"Executing : Delete All Rows From Table | {selectStatement}") - result = self.__execute_sql(cur, selectStatement) + logger.info(f"Executing : Delete All Rows From Table | {query}") + result = self.__execute_sql(cur, query) if result is not None: if not sansTran: self._dbconnection.commit() From 6f364b177e8fdecd7ec25f829034f665d731dd10 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:21:12 +0100 Subject: [PATCH 111/266] Simplify conditions --- src/DatabaseLibrary/assertion.py | 2 +- src/DatabaseLibrary/connection_manager.py | 2 +- src/DatabaseLibrary/query.py | 35 +++++++++-------------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index f798337..0de14a5 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -252,7 +252,7 @@ def table_must_exist(self, tableName, sansTran=False, msg=None): logger.info("Database doesn't support information schema, try using a simple SQL request") try: query = f"SELECT 1 from {tableName} where 1=0" - num_rows = self.row_count(query, sansTran) + self.row_count(query, sansTran) table_exists = True except: table_exists = False diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index de7aa86..ad005d2 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -273,7 +273,7 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using the `db_connect_string` - (parsed as single connection connection string or URI). + (parsed as single connection string or URI). Use `connect_to_database_using_custom_params` for passing connection params as named arguments. diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 3a010f6..13cac50 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -77,9 +77,8 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): return all_rows finally: - if cur: - if not sansTran: - self._dbconnection.rollback() + if cur and not sansTran: + self._dbconnection.rollback() def row_count(self, selectStatement, sansTran=False): """ @@ -118,9 +117,8 @@ def row_count(self, selectStatement, sansTran=False): return len(data) return cur.rowcount finally: - if cur: - if not sansTran: - self._dbconnection.rollback() + if cur and not sansTran: + self._dbconnection.rollback() def description(self, selectStatement, sansTran=False): """ @@ -154,9 +152,8 @@ def description(self, selectStatement, sansTran=False): description[row] = (description[row][0].encode("utf-8"),) + description[row][1:] return description finally: - if cur: - if not sansTran: - self._dbconnection.rollback() + if cur and not sansTran: + self._dbconnection.rollback() def delete_all_rows_from_table(self, tableName, sansTran=False): """ @@ -190,9 +187,8 @@ def delete_all_rows_from_table(self, tableName, sansTran=False): if not sansTran: self._dbconnection.commit() finally: - if cur: - if not sansTran: - self._dbconnection.rollback() + if cur and not sansTran: + self._dbconnection.rollback() def execute_sql_script(self, sqlScriptFileName, sansTran=False): """ @@ -314,9 +310,8 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): if not sansTran: self._dbconnection.commit() finally: - if cur: - if not sansTran: - self._dbconnection.rollback() + if cur and not sansTran: + self._dbconnection.rollback() def execute_sql_string(self, sqlString, sansTran=False): """ @@ -342,9 +337,8 @@ def execute_sql_string(self, sqlString, sansTran=False): if not sansTran: self._dbconnection.commit() finally: - if cur: - if not sansTran: - self._dbconnection.rollback() + if cur and not sansTran: + self._dbconnection.rollback() def call_stored_procedure(self, spName, spParams=None, sansTran=False): """ @@ -481,9 +475,8 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): return param_values, result_sets finally: - if cur: - if not sansTran: - self._dbconnection.rollback() + if cur and not sansTran: + self._dbconnection.rollback() def __execute_sql(self, cur, sql_statement, omit_trailing_semicolon=None): """ From 1ce4a5424532093dcc0822c6c39a7462346b6e91 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:36:11 +0100 Subject: [PATCH 112/266] Fix docs typo --- src/DatabaseLibrary/query.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 13cac50..1a635e0 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -63,7 +63,6 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): logger.info(f"Executing : Query | {selectStatement}") self.__execute_sql(cur, selectStatement) all_rows = cur.fetchall() - if returnAsDict: mapped_rows = [] col_names = [c[0] for c in cur.description] @@ -204,7 +203,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): | 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 (';') - they will be executed separately. + SQL commands are expected to be delimited by a semicolon (';') - they will be executed separately. For example: DELETE FROM person_employee_table; @@ -318,7 +317,7 @@ def execute_sql_string(self, sqlString, sansTran=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. - SQL commands are expected to be delimited by a semi-colon (';'). + SQL commands are expected to be delimited by a semicolon (';'). For example: | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | From d65523fd0713cebc1aefcbf87df931bd64eb7cb5 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:37:16 +0100 Subject: [PATCH 113/266] Refactor returnAsDict query mode --- src/DatabaseLibrary/query.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 927031d..e4e4ccf 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -63,18 +63,9 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): 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 [dict(zip(col_names, row)) for row in allRows] return allRows finally: if cur: From 6d240afa59e3d3d28ea766815cc3e731e8be95d0 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:44:19 +0100 Subject: [PATCH 114/266] Add typing --- src/DatabaseLibrary/assertion.py | 21 ++++++++++----- src/DatabaseLibrary/connection_manager.py | 31 +++++++++++++---------- src/DatabaseLibrary/query.py | 17 +++++++------ 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 5d41e52..da4f5ae 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -11,6 +11,7 @@ # 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 Optional from robot.api import logger @@ -20,7 +21,7 @@ class Assertion: Assertion handles all the assertions of Database Library. """ - def check_if_exists_in_database(self, selectStatement, sansTran=False, msg=None): + def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = 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 @@ -50,7 +51,7 @@ def check_if_exists_in_database(self, selectStatement, sansTran=False, msg=None) msg or f"Expected to have have at least one row, " f"but got 0 rows from: '{selectStatement}'" ) - def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=None): + def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None): """ This is the negation of `check_if_exists_in_database`. @@ -83,7 +84,7 @@ def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=N msg or f"Expected to have have no rows from '{selectStatement}', but got some rows: {queryResults}" ) - def row_count_is_0(self, selectStatement, sansTran=False, msg=None): + def row_count_is_0(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = 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 @@ -112,7 +113,9 @@ def row_count_is_0(self, selectStatement, sansTran=False, msg=None): if num_rows > 0: raise AssertionError(msg or f"Expected 0 rows, but {num_rows} were returned from: '{selectStatement}'") - def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg=None): + def row_count_is_equal_to_x( + self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = 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 @@ -144,7 +147,9 @@ def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg= msg or f"Expected {numRows} rows, but {num_rows} were returned from: '{selectStatement}'" ) - def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, msg=None): + def row_count_is_greater_than_x( + self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = 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 @@ -176,7 +181,9 @@ def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, msg or f"Expected more than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" ) - def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg=None): + def row_count_is_less_than_x( + self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = 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 @@ -208,7 +215,7 @@ def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" ) - def table_must_exist(self, tableName, sansTran=False, msg=None): + def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional[str] = 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. The default error message can be overridden with the `msg` argument. diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 09d9e71..6b3e034 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -13,6 +13,7 @@ # limitations under the License. import importlib +from typing import Optional try: import ConfigParser @@ -37,15 +38,15 @@ def __init__(self): def connect_to_database( self, - dbapiModuleName=None, - dbName=None, - dbUsername=None, - dbPassword=None, - dbHost=None, - dbPort=None, - dbCharset=None, - dbDriver=None, - dbConfigFile=None, + dbapiModuleName: Optional[str] = None, + dbName: Optional[str] = None, + dbUsername: Optional[str] = None, + dbPassword: Optional[str] = None, + dbHost: Optional[str] = None, + dbPort: Optional[int] = None, + dbCharset: Optional[str] = None, + dbDriver: Optional[str] = None, + dbConfigFile: Optional[str] = None, ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -236,7 +237,9 @@ def connect_to_database( port=dbPort, ) - def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_connect_string=""): + def connect_to_database_using_custom_params( + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "" + ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using the map string `db_connect_string` @@ -271,7 +274,9 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne self._dbconnection = eval(db_connect_string) - def connect_to_database_using_custom_connection_string(self, dbapiModuleName=None, db_connect_string=""): + def connect_to_database_using_custom_connection_string( + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "" + ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using the `db_connect_string` @@ -291,7 +296,7 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non ) self._dbconnection = db_api_2.connect(db_connect_string) - def disconnect_from_database(self, error_if_no_connection=False): + def disconnect_from_database(self, error_if_no_connection: bool = False): """ Disconnects from the database. By default it's not an error if there was no open database connection - @@ -311,7 +316,7 @@ def disconnect_from_database(self, error_if_no_connection=False): self._dbconnection.close() self._dbconnection = None - def set_auto_commit(self, autoCommit=True): + def set_auto_commit(self, autoCommit: bool = True): """ Turn the autocommit on the database connection ON or OFF. diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 927031d..717997d 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -14,6 +14,7 @@ import inspect import sys +from typing import List, Optional from robot.api import logger @@ -23,7 +24,7 @@ class Query: Query handles all the querying done by the Database Library. """ - def query(self, selectStatement, sansTran=False, returnAsDict=False): + def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False): """ 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. @@ -81,7 +82,7 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): if not sansTran: self._dbconnection.rollback() - def row_count(self, selectStatement, sansTran=False): + def row_count(self, selectStatement: str, sansTran: bool = False): """ 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. @@ -124,7 +125,7 @@ def row_count(self, selectStatement, sansTran=False): if not sansTran: self._dbconnection.rollback() - def description(self, selectStatement, sansTran=False): + def description(self, selectStatement: str, sansTran: bool = False): """ 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. @@ -160,7 +161,7 @@ def description(self, selectStatement, sansTran=False): if not sansTran: self._dbconnection.rollback() - def delete_all_rows_from_table(self, tableName, sansTran=False): + def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False): """ Delete all the rows within a given table. Set optional input `sansTran` to True to run command without an explicit transaction commit or rollback. @@ -196,7 +197,7 @@ def delete_all_rows_from_table(self, tableName, sansTran=False): if not sansTran: self._dbconnection.rollback() - def execute_sql_script(self, sqlScriptFileName, sansTran=False): + def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False): """ 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 @@ -320,7 +321,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): if not sansTran: self._dbconnection.rollback() - def execute_sql_string(self, sqlString, sansTran=False): + def execute_sql_string(self, sqlString: str, sansTran: bool = 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. @@ -348,7 +349,7 @@ def execute_sql_string(self, sqlString, sansTran=False): if not sansTran: self._dbconnection.rollback() - def call_stored_procedure(self, spName, spParams=None, sansTran=False): + def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = None, sansTran: bool = False): """ Calls a stored procedure `spName` with the `spParams` - a *list* of parameters the procedure requires. Use the special *CURSOR* value for OUT params, which should receive result sets - @@ -487,7 +488,7 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): if not sansTran: self._dbconnection.rollback() - def __execute_sql(self, cur, sql_statement, omit_trailing_semicolon=None): + def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None): """ Runs the `sql_statement` using `cur` as Cursor object. Use `omit_trailing_semicolon` parameter (bool) for explicite instruction, From 023562e0ce17046d568c2919e67fa44849ce2ab0 Mon Sep 17 00:00:00 2001 From: amochin Date: Sun, 5 Nov 2023 19:37:03 +0100 Subject: [PATCH 115/266] Driver mode is relevant for oracle only --- src/DatabaseLibrary/connection_manager.py | 26 +++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 1379307..2ecd7db 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -99,7 +99,6 @@ def connect_to_database( 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")) - driverMode = driverMode or config.get("default", "driverMode") if dbapiModuleName == "excel" or dbapiModuleName == "excelrw": self.db_api_module_name = "pyodbc" @@ -189,18 +188,33 @@ def connect_to_database( self.omit_trailing_semicolon = True elif dbapiModuleName in ["oracledb"]: dbPort = dbPort or 1521 + driverMode = driverMode or "thin" oracle_connection_params = db_api_2.ConnectParams(host=dbHost, port=dbPort, service_name=dbName) - logger.info( - "Connecting using: %s.connect(user=%s, password=***, params=%s) " - % (dbapiModuleName, dbUsername, oracle_connection_params) - ) if "thick" in driverMode.lower(): + logger.info("Using thick Oracle client mode") mode_param = driverMode.lower().split(",lib_dir=") if len(mode_param) == 2 and mode_param[0].lower() == "thick": - db_api_2.init_oracle_client(lib_dir=mode_param[1]) + 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 driverMode.lower(): + oracle_thin_mode = True + logger.info("Using thin Oracle client mode") + else: + raise ValueError(f"Invalid Oracle client mode provided: {driverMode}") + logger.info( + "Connecting using: %s.connect(user=%s, password=***, params=%s) " + % (dbapiModuleName, dbUsername, oracle_connection_params) + ) self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) + assert self._dbconnection.thin == oracle_thin_mode, ( + "Expected oracledb to run in thin mode: {oracle_thin_mode}, " + f"but the connection has thin mode: {self._dbconnection.thin}" + ) self.omit_trailing_semicolon = True elif dbapiModuleName in ["teradata"]: dbPort = dbPort or 1025 From 9950c9c15ff1e62132b507ded52c78ac60af5460 Mon Sep 17 00:00:00 2001 From: amochin Date: Sun, 5 Nov 2023 19:37:31 +0100 Subject: [PATCH 116/266] WIP - Tests for oracledb mode switch --- .../custom_db_tests/oracle_thick_mode.robot | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/tests/custom_db_tests/oracle_thick_mode.robot 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 0000000..85dc3c7 --- /dev/null +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -0,0 +1,50 @@ +*** 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 + +Resource ../../resources/common.resource + + +*** Test Cases *** +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 driverMode=thin + +Thick Mode Without Client Dir Specified + [Documentation] No client dir --> oracledb will search it in usual places + Connect And Run Simple Query driverMode=thick + +Thick Mode With Client Dir Specified + [Documentation] Client dir specified --> oracledb will search it in this place + Connect And Run Simple Query driverMode=thick,lib_dir= + +Wrong Mode + [Documentation] Wrong mode --> proper error message from the library + Run Keyword And Expect Error Invalid Oracle client mode provided: wrong + ... Connect And Run Simple Query driverMode=wrong + +Thick Mode With Wrong Client Dir + [Documentation] Wrong mode --> proper error message from oracledb module + Run Keyword And Expect Error expected_error + ... Connect And Run Simple Query driverMode=thick,lib_dir=C:\WRONG + + +*** Keywords *** +Connect And Run Simple Query + [Documentation] Connect using usual params and client mode if provided + [Arguments] &{Extra params} + Connect To Database + ... oracledb + ... 127.0.0.1 + ... 1521 + ... db + ... db_user + ... pass + ... &{Extra params} + Create Person Table + Query SELECT * FROM person From b889b6cedf36b21a9a988bd7cc6b08a55c1c5abf Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:02:18 +0100 Subject: [PATCH 117/266] Typo in var "all_rows" --- src/DatabaseLibrary/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 4d9ce83..5065f0f 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -66,7 +66,7 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): if returnAsDict: col_names = [c[0] for c in cur.description] return [dict(zip(col_names, row)) for row in all_rows] - return al_rows + return all_rows finally: if cur and not sansTran: self._dbconnection.rollback() From 9b9c952139004324e80a207a12dc2c925062100f Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 5 Nov 2023 18:44:19 +0100 Subject: [PATCH 118/266] Add typing --- src/DatabaseLibrary/assertion.py | 21 ++++++++++----- src/DatabaseLibrary/connection_manager.py | 31 +++++++++++++---------- src/DatabaseLibrary/query.py | 17 +++++++------ 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 0de14a5..931b971 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -11,6 +11,7 @@ # 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 Optional from robot.api import logger @@ -20,7 +21,7 @@ class Assertion: Assertion handles all the assertions of Database Library. """ - def check_if_exists_in_database(self, selectStatement, sansTran=False, msg=None): + def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = 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 @@ -50,7 +51,7 @@ def check_if_exists_in_database(self, selectStatement, sansTran=False, msg=None) msg or f"Expected to have have at least one row, " f"but got 0 rows from: '{selectStatement}'" ) - def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=None): + def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None): """ This is the negation of `check_if_exists_in_database`. @@ -83,7 +84,7 @@ def check_if_not_exists_in_database(self, selectStatement, sansTran=False, msg=N msg or f"Expected to have have no rows from '{selectStatement}', but got some rows: {query_results}" ) - def row_count_is_0(self, selectStatement, sansTran=False, msg=None): + def row_count_is_0(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = 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 @@ -112,7 +113,9 @@ def row_count_is_0(self, selectStatement, sansTran=False, msg=None): if num_rows > 0: raise AssertionError(msg or f"Expected 0 rows, but {num_rows} were returned from: '{selectStatement}'") - def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg=None): + def row_count_is_equal_to_x( + self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = 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 @@ -144,7 +147,9 @@ def row_count_is_equal_to_x(self, selectStatement, numRows, sansTran=False, msg= msg or f"Expected {numRows} rows, but {num_rows} were returned from: '{selectStatement}'" ) - def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, msg=None): + def row_count_is_greater_than_x( + self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = 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 @@ -176,7 +181,9 @@ def row_count_is_greater_than_x(self, selectStatement, numRows, sansTran=False, msg or f"Expected more than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" ) - def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg=None): + def row_count_is_less_than_x( + self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = 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 @@ -208,7 +215,7 @@ def row_count_is_less_than_x(self, selectStatement, numRows, sansTran=False, msg msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" ) - def table_must_exist(self, tableName, sansTran=False, msg=None): + def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional[str] = 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. The default error message can be overridden with the `msg` argument. diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index ad005d2..003a38a 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -13,6 +13,7 @@ # limitations under the License. import importlib +from typing import Optional try: import ConfigParser @@ -37,15 +38,15 @@ def __init__(self): def connect_to_database( self, - dbapiModuleName=None, - dbName=None, - dbUsername=None, - dbPassword=None, - dbHost=None, - dbPort=None, - dbCharset=None, - dbDriver=None, - dbConfigFile=None, + dbapiModuleName: Optional[str] = None, + dbName: Optional[str] = None, + dbUsername: Optional[str] = None, + dbPassword: Optional[str] = None, + dbHost: Optional[str] = None, + dbPort: Optional[int] = None, + dbCharset: Optional[str] = None, + dbDriver: Optional[str] = None, + dbConfigFile: Optional[str] = None, ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -234,7 +235,9 @@ def connect_to_database( port=dbPort, ) - def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_connect_string=""): + def connect_to_database_using_custom_params( + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "" + ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using the map string `db_connect_string` @@ -269,7 +272,9 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne self._dbconnection = eval(db_connect_string) - def connect_to_database_using_custom_connection_string(self, dbapiModuleName=None, db_connect_string=""): + def connect_to_database_using_custom_connection_string( + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "" + ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using the `db_connect_string` @@ -290,7 +295,7 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non ) self._dbconnection = db_api_2.connect(db_connect_string) - def disconnect_from_database(self, error_if_no_connection=False): + def disconnect_from_database(self, error_if_no_connection: bool = False): """ Disconnects from the database. @@ -311,7 +316,7 @@ def disconnect_from_database(self, error_if_no_connection=False): self._dbconnection.close() self._dbconnection = None - def set_auto_commit(self, autoCommit=True): + def set_auto_commit(self, autoCommit: bool = True): """ Turn the autocommit on the database connection ON or OFF. diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 5065f0f..f4c4786 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -14,6 +14,7 @@ import inspect import sys +from typing import List, Optional from robot.api import logger @@ -23,7 +24,7 @@ class Query: Query handles all the querying done by the Database Library. """ - def query(self, selectStatement, sansTran=False, returnAsDict=False): + def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False): """ 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. @@ -71,7 +72,7 @@ def query(self, selectStatement, sansTran=False, returnAsDict=False): if cur and not sansTran: self._dbconnection.rollback() - def row_count(self, selectStatement, sansTran=False): + def row_count(self, selectStatement: str, sansTran: bool = False): """ 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. @@ -111,7 +112,7 @@ def row_count(self, selectStatement, sansTran=False): if cur and not sansTran: self._dbconnection.rollback() - def description(self, selectStatement, sansTran=False): + def description(self, selectStatement: str, sansTran: bool = False): """ 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. @@ -146,7 +147,7 @@ def description(self, selectStatement, sansTran=False): if cur and not sansTran: self._dbconnection.rollback() - def delete_all_rows_from_table(self, tableName, sansTran=False): + def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False): """ Delete all the rows within a given table. Set optional input `sansTran` to True to run command without an explicit transaction commit or rollback. @@ -181,7 +182,7 @@ def delete_all_rows_from_table(self, tableName, sansTran=False): if cur and not sansTran: self._dbconnection.rollback() - def execute_sql_script(self, sqlScriptFileName, sansTran=False): + def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False): """ 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 @@ -304,7 +305,7 @@ def execute_sql_script(self, sqlScriptFileName, sansTran=False): if cur and not sansTran: self._dbconnection.rollback() - def execute_sql_string(self, sqlString, sansTran=False): + def execute_sql_string(self, sqlString: str, sansTran: bool = 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. @@ -331,7 +332,7 @@ def execute_sql_string(self, sqlString, sansTran=False): if cur and not sansTran: self._dbconnection.rollback() - def call_stored_procedure(self, spName, spParams=None, sansTran=False): + def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = None, sansTran: bool = False): """ Calls a stored procedure `spName` with the `spParams` - a *list* of parameters the procedure requires. Use the special *CURSOR* value for OUT params, which should receive result sets - @@ -469,7 +470,7 @@ def call_stored_procedure(self, spName, spParams=None, sansTran=False): if cur and not sansTran: self._dbconnection.rollback() - def __execute_sql(self, cur, sql_statement, omit_trailing_semicolon=None): + def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None): """ Runs the `sql_statement` using `cur` as Cursor object. Use `omit_trailing_semicolon` parameter (bool) for explicit instruction, From 5614e97f5226264362044608bbfa1209dc752e9d Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Mon, 6 Nov 2023 09:50:16 +0100 Subject: [PATCH 119/266] Fix numRows int -> str type because of ascii encoding --- src/DatabaseLibrary/assertion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 931b971..824742e 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -114,7 +114,7 @@ def row_count_is_0(self, selectStatement: str, sansTran: bool = False, msg: Opti 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: int, sansTran: bool = False, msg: Optional[str] = None + self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None ): """ Check if the number of rows returned from `selectStatement` is equal to the value submitted. If not, then this From ce260ddceb146bc33eb5582add0b8764e77c8d0c Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Mon, 6 Nov 2023 10:12:02 +0100 Subject: [PATCH 120/266] Fix numRows int -> str type because of ascii encoding --- src/DatabaseLibrary/assertion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 824742e..185c2ba 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -148,7 +148,7 @@ def row_count_is_equal_to_x( ) def row_count_is_greater_than_x( - self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = None + self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None ): """ Check if the number of rows returned from `selectStatement` is greater than the value submitted. If not, then @@ -182,7 +182,7 @@ def row_count_is_greater_than_x( ) def row_count_is_less_than_x( - self, selectStatement: str, numRows: int, sansTran: bool = False, msg: Optional[str] = None + self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None ): """ Check if the number of rows returned from `selectStatement` is less than the value submitted. If not, then this From 21770c2d382860be84b95c7527cde4ca6719e02c Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 10:24:35 +0100 Subject: [PATCH 121/266] Pipeline for oracle_db thick mode switch --- .github/workflows/oracle_thick_mode.yml | 79 +++++++++++++++++++ .../custom_db_tests/oracle_thick_mode.robot | 25 ++++-- 2 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/oracle_thick_mode.yml diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml new file mode 100644 index 0000000..59e6bb0 --- /dev/null +++ b/.github/workflows/oracle_thick_mode.yml @@ -0,0 +1,79 @@ +name: Oracle thick client mode tests + +on: [push, pull_request] + +env: + DB_MODULE: oracledb + DB_NAME: db + DB_USER: db_user + DB_PASS: pass + DB_HOST: 127.0.0.1 + DB_PORT: 1521 + LIB_DIR: /opt/oracle/instantclient_21_4 + +jobs: + tests: + runs-on: ubuntu-latest + services: + oracle: + image: gvenzl/oracle-free:latest + env: + ORACLE_PASSWORD: ${{ env.DB_PASS }} + ORACLE_DATABASE: ${{ env.DB_NAME }} + APP_USER: ${{ env.DB_USER }} + APP_USER_PASSWORD: ${{ env.DB_PASS }} + ports: + - 1521:1521 + # Provide healthcheck script options for startup + options: --health-cmd healthcheck.sh --health-interval 10s --health-timeout 5s --health-retries 10 + + steps: + - name: Install Oracle instant client + run: | + cd /opt/ + sudo mkdir /opt/oracle + sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip + sudo unzip instantclient_21_4 + + - name: Check out repository code + uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.8.14' + + - name: Setup Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Development/Checked out version of DatabaseLibrary + run: | + pip install -e ${{ github.workspace }} + + - name: Setup Python DB module + run: | + pip install ${{ env.DB_MODULE }} + + - name: Launch RF Tests + working-directory: ./test + run: >- + robot + -d results + --xunit result.xml + --loglevel DEBUG:INFO + -v DB_MODULE:${{ env.DB_MODULE }} + -v DB_NAME:${{ env.DB_NAME }} + -v DB_USER:${{ env.DB_USER }} + -v DB_PASS:${{ env.DB_PASS }} + -v DB_HOST:${{ env.DB_HOST }} + -v DB_PORT:${{ env.DB_PORT }} + -v ORACLE_LIB_DIR: ${{ env.LIB_DIR }} + tests/custom_db_tests/oracle_thick_mode.robot + + - name: Upload Robot Logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: log-files + path: ./test/results/ diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index 85dc3c7..ee42798 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -6,6 +6,15 @@ Documentation Tests of switching between thin and thick mode of oracledb clie Resource ../../resources/common.resource +*** 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 *** Thin Mode - Default [Documentation] No mode specified --> thin mode is used @@ -21,7 +30,7 @@ Thick Mode Without Client Dir Specified Thick Mode With Client Dir Specified [Documentation] Client dir specified --> oracledb will search it in this place - Connect And Run Simple Query driverMode=thick,lib_dir= + Connect And Run Simple Query driverMode=thick,lib_dir=${ORACLE_LIB_DIR} Wrong Mode [Documentation] Wrong mode --> proper error message from the library @@ -31,7 +40,7 @@ Wrong Mode Thick Mode With Wrong Client Dir [Documentation] Wrong mode --> proper error message from oracledb module Run Keyword And Expect Error expected_error - ... Connect And Run Simple Query driverMode=thick,lib_dir=C:\WRONG + ... Connect And Run Simple Query driverMode=thick,lib_dir=C:/WRONG *** Keywords *** @@ -39,12 +48,12 @@ Connect And Run Simple Query [Documentation] Connect using usual params and client mode if provided [Arguments] &{Extra params} Connect To Database - ... oracledb - ... 127.0.0.1 - ... 1521 - ... db - ... db_user - ... pass + ... ${DB_MODULE} + ... ${DB_NAME} + ... ${DB_USER} + ... ${DB_PASS} + ... ${DB_HOST} + ... ${DB_PORT} ... &{Extra params} Create Person Table Query SELECT * FROM person From 61380e8cb085c83bae6f25fc6cbde76b2eea588c Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 10:34:24 +0100 Subject: [PATCH 122/266] Fix file name --- .github/workflows/oracle_thick_mode.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 59e6bb0..cbb1402 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -33,7 +33,7 @@ jobs: cd /opt/ sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - sudo unzip instantclient_21_4 + sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - name: Check out repository code uses: actions/checkout@v3 From 7b3a0684f5b15b85346494dcffd183a9c217dcaa Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 10:38:10 +0100 Subject: [PATCH 123/266] Quote path in variable --- .github/workflows/oracle_thick_mode.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index cbb1402..3bb5672 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -68,7 +68,7 @@ jobs: -v DB_PASS:${{ env.DB_PASS }} -v DB_HOST:${{ env.DB_HOST }} -v DB_PORT:${{ env.DB_PORT }} - -v ORACLE_LIB_DIR: ${{ env.LIB_DIR }} + -v ORACLE_LIB_DIR:"${{ env.LIB_DIR }}" tests/custom_db_tests/oracle_thick_mode.robot - name: Upload Robot Logs From 516e0764140b9f11a27257368565bd67329cb242 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 10:43:40 +0100 Subject: [PATCH 124/266] Tests for oracle thin and thick mode require closing the connection --- test/tests/custom_db_tests/oracle_thick_mode.robot | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index ee42798..f4dc01b 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -4,6 +4,7 @@ Documentation Tests of switching between thin and thick mode of oracledb clie ... See more here: https://python-oracledb.readthedocs.io/en/latest/user_guide/initialization.html#initialization Resource ../../resources/common.resource +Test Teardown Disconnect From Database *** Variables *** @@ -15,6 +16,7 @@ ${DB_USER} db_user ${DB_NAME} db ${ORACLE_LIB_DIR} ${EMPTY} + *** Test Cases *** Thin Mode - Default [Documentation] No mode specified --> thin mode is used From 245d6a6eb3cb545bc09a9d44136cb2504b963b55 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 10:53:55 +0100 Subject: [PATCH 125/266] Fix expected error message in tests --- test/tests/custom_db_tests/oracle_thick_mode.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index f4dc01b..0d96e2a 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -36,7 +36,7 @@ Thick Mode With Client Dir Specified Wrong Mode [Documentation] Wrong mode --> proper error message from the library - Run Keyword And Expect Error Invalid Oracle client mode provided: wrong + Run Keyword And Expect Error ValueError: Invalid Oracle client mode provided: wrong ... Connect And Run Simple Query driverMode=wrong Thick Mode With Wrong Client Dir From 146e47f5a6cfa50911c7313445c478f636d49558 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 11:57:06 +0100 Subject: [PATCH 126/266] Oracle thick mode tests - clean data in test teardown --- test/tests/custom_db_tests/oracle_thick_mode.robot | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index 0d96e2a..e52a8d8 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -4,7 +4,7 @@ Documentation Tests of switching between thin and thick mode of oracledb clie ... See more here: https://python-oracledb.readthedocs.io/en/latest/user_guide/initialization.html#initialization Resource ../../resources/common.resource -Test Teardown Disconnect From Database +Test Teardown Drop Tables And Disconnect *** Variables *** @@ -59,3 +59,8 @@ Connect And Run Simple Query ... &{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 From 44711c772c2b079c338b6dd590b157f0149e92d3 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 12:11:32 +0100 Subject: [PATCH 127/266] [debug] switch Oracle thick mode tests for debugging --- test/tests/custom_db_tests/oracle_thick_mode.robot | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index e52a8d8..2f78007 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -18,6 +18,10 @@ ${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 driverMode=thick + Thin Mode - Default [Documentation] No mode specified --> thin mode is used Connect And Run Simple Query @@ -26,10 +30,6 @@ Thin Mode Explicitely Specified [Documentation] Thin mode specified --> thin mode is used Connect And Run Simple Query driverMode=thin -Thick Mode Without Client Dir Specified - [Documentation] No client dir --> oracledb will search it in usual places - Connect And Run Simple Query driverMode=thick - Thick Mode With Client Dir Specified [Documentation] Client dir specified --> oracledb will search it in this place Connect And Run Simple Query driverMode=thick,lib_dir=${ORACLE_LIB_DIR} From b435289be201e6c3558201ae5e202ff6f73f51e6 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 12:36:34 +0100 Subject: [PATCH 128/266] copy oracle client libs in usual search path for test without lib dir specified --- .github/workflows/oracle_thick_mode.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 3bb5672..3d599ba 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,6 +34,7 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip + cp instantclient_21_4 /usr/local/lib/ - name: Check out repository code uses: actions/checkout@v3 From cbfadcb17648733b587ed69118d51e27a081635b Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 12:57:46 +0100 Subject: [PATCH 129/266] [debug] try if adding module reload helps resetting oracledb connection mode --- src/DatabaseLibrary/connection_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 2ecd7db..072ac95 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -106,6 +106,7 @@ def connect_to_database( else: self.db_api_module_name = dbapiModuleName db_api_2 = importlib.import_module(dbapiModuleName) + db_api_2 = importlib.reload(dbapiModuleName) if dbapiModuleName in ["MySQLdb", "pymysql"]: dbPort = dbPort or 3306 @@ -274,6 +275,7 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne | Connect To Database Using Custom Params | sqlite3 | database="./my_database.db", isolation_level=None | """ db_api_2 = importlib.import_module(dbapiModuleName) + db_api_2 = importlib.reload(dbapiModuleName) self.db_api_module_name = dbapiModuleName db_connect_string = f"db_api_2.connect({db_connect_string})" @@ -307,6 +309,7 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb" | """ db_api_2 = importlib.import_module(dbapiModuleName) + db_api_2 = importlib.reload(dbapiModuleName) self.db_api_module_name = dbapiModuleName logger.info( f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect('{db_connect_string}')" From 19e7cd556b24e2a01b153fc8ecc08bc7d63bf045 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 13:01:08 +0100 Subject: [PATCH 130/266] Forgot flag for copy command --- .github/workflows/oracle_thick_mode.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 3d599ba..36b6c7e 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,7 +34,7 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - cp instantclient_21_4 /usr/local/lib/ + cp -r instantclient_21_4 /usr/local/lib/ - name: Check out repository code uses: actions/checkout@v3 From 7a32d51b5bd4634d1fb3f7ece120ffca0a8dd318 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 13:04:18 +0100 Subject: [PATCH 131/266] Copy as sudo --- .github/workflows/oracle_thick_mode.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 36b6c7e..27051c8 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,7 +34,7 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - cp -r instantclient_21_4 /usr/local/lib/ + sudo cp -r instantclient_21_4 /usr/local/lib/ - name: Check out repository code uses: actions/checkout@v3 From 1c4e65121ecf035d0a7ab43c59587ee6e7ff0a86 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 13:06:05 +0100 Subject: [PATCH 132/266] reload a module, not a name --- src/DatabaseLibrary/connection_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 072ac95..007bec2 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -275,7 +275,7 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne | Connect To Database Using Custom Params | sqlite3 | database="./my_database.db", isolation_level=None | """ db_api_2 = importlib.import_module(dbapiModuleName) - db_api_2 = importlib.reload(dbapiModuleName) + db_api_2 = importlib.reload(db_api_2) self.db_api_module_name = dbapiModuleName db_connect_string = f"db_api_2.connect({db_connect_string})" From 7004d1143976b69302ed2462ccec182e7b298303 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 13:15:19 +0100 Subject: [PATCH 133/266] EVERYWHERE reload a module, not a name --- src/DatabaseLibrary/connection_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 007bec2..27d2ce6 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -106,7 +106,7 @@ def connect_to_database( else: self.db_api_module_name = dbapiModuleName db_api_2 = importlib.import_module(dbapiModuleName) - db_api_2 = importlib.reload(dbapiModuleName) + db_api_2 = importlib.reload(db_api_2) if dbapiModuleName in ["MySQLdb", "pymysql"]: dbPort = dbPort or 3306 @@ -309,7 +309,7 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb" | """ db_api_2 = importlib.import_module(dbapiModuleName) - db_api_2 = importlib.reload(dbapiModuleName) + db_api_2 = importlib.reload(db_api_2) self.db_api_module_name = dbapiModuleName logger.info( f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect('{db_connect_string}')" From 21b761a1413f8996580462539f25c7ec6c87c0e0 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 13:22:47 +0100 Subject: [PATCH 134/266] module reload didn't work, revert --- src/DatabaseLibrary/connection_manager.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 27d2ce6..2ecd7db 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -106,7 +106,6 @@ def connect_to_database( else: self.db_api_module_name = dbapiModuleName db_api_2 = importlib.import_module(dbapiModuleName) - db_api_2 = importlib.reload(db_api_2) if dbapiModuleName in ["MySQLdb", "pymysql"]: dbPort = dbPort or 3306 @@ -275,7 +274,6 @@ def connect_to_database_using_custom_params(self, dbapiModuleName=None, db_conne | Connect To Database Using Custom Params | sqlite3 | database="./my_database.db", isolation_level=None | """ db_api_2 = importlib.import_module(dbapiModuleName) - db_api_2 = importlib.reload(db_api_2) self.db_api_module_name = dbapiModuleName db_connect_string = f"db_api_2.connect({db_connect_string})" @@ -309,7 +307,6 @@ def connect_to_database_using_custom_connection_string(self, dbapiModuleName=Non | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb" | """ db_api_2 = importlib.import_module(dbapiModuleName) - db_api_2 = importlib.reload(db_api_2) self.db_api_module_name = dbapiModuleName logger.info( f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect('{db_connect_string}')" From d855f8c70276d62185dcc7e5aafa1733e30b3821 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 13:27:09 +0100 Subject: [PATCH 135/266] Reorder tests for thick/thin mode - see if it helps --- .../custom_db_tests/oracle_thick_mode.robot | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index 2f78007..6a36d29 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -22,6 +22,15 @@ Thick Mode Without Client Dir Specified [Documentation] No client dir --> oracledb will search it in usual places Connect And Run Simple Query driverMode=thick +Thick Mode With Client Dir Specified + [Documentation] Client dir specified --> oracledb will search it in this place + Connect And Run Simple Query driverMode=thick,lib_dir=${ORACLE_LIB_DIR} + +Thick Mode With Wrong Client Dir + [Documentation] Wrong mode --> proper error message from oracledb module + Run Keyword And Expect Error expected_error + ... Connect And Run Simple Query driverMode=thick,lib_dir=C:/WRONG + Thin Mode - Default [Documentation] No mode specified --> thin mode is used Connect And Run Simple Query @@ -30,20 +39,11 @@ Thin Mode Explicitely Specified [Documentation] Thin mode specified --> thin mode is used Connect And Run Simple Query driverMode=thin -Thick Mode With Client Dir Specified - [Documentation] Client dir specified --> oracledb will search it in this place - Connect And Run Simple Query driverMode=thick,lib_dir=${ORACLE_LIB_DIR} - 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 driverMode=wrong -Thick Mode With Wrong Client Dir - [Documentation] Wrong mode --> proper error message from oracledb module - Run Keyword And Expect Error expected_error - ... Connect And Run Simple Query driverMode=thick,lib_dir=C:/WRONG - *** Keywords *** Connect And Run Simple Query From c4de2e3b2319ef217c6a9a045b9cc218d2cca930 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 13:59:41 +0100 Subject: [PATCH 136/266] Set export LD_LIBRARY_PATH to fix loading oracle instant client on linux --- .github/workflows/oracle_thick_mode.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 27051c8..8c7b512 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,7 +34,7 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - sudo cp -r instantclient_21_4 /usr/local/lib/ + export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH - name: Check out repository code uses: actions/checkout@v3 From f62140c012b5b9929734470311d4f2170fbcc26f Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 14:01:23 +0100 Subject: [PATCH 137/266] Can't test wrong oracle client path without windows --- test/tests/custom_db_tests/oracle_thick_mode.robot | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index 6a36d29..4dfb5ae 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -26,11 +26,6 @@ Thick Mode With Client Dir Specified [Documentation] Client dir specified --> oracledb will search it in this place Connect And Run Simple Query driverMode=thick,lib_dir=${ORACLE_LIB_DIR} -Thick Mode With Wrong Client Dir - [Documentation] Wrong mode --> proper error message from oracledb module - Run Keyword And Expect Error expected_error - ... Connect And Run Simple Query driverMode=thick,lib_dir=C:/WRONG - Thin Mode - Default [Documentation] No mode specified --> thin mode is used Connect And Run Simple Query From 1d98fd51b8bf54ce2b6bd5b92e2165c9901e14a6 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 14:38:54 +0100 Subject: [PATCH 138/266] Try moving setting PATH var directly in front of robot launch --- .github/workflows/oracle_thick_mode.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 8c7b512..e52877b 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,7 +34,6 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH - name: Check out repository code uses: actions/checkout@v3 @@ -59,7 +58,8 @@ jobs: - name: Launch RF Tests working-directory: ./test run: >- - robot + export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH + robot -d results --xunit result.xml --loglevel DEBUG:INFO From 98d0ffce52683fd80ec9526682c4bd70c7009de8 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 14:43:33 +0100 Subject: [PATCH 139/266] [DEBUG] messing up lines in shell script in CI --- .github/workflows/oracle_thick_mode.yml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index e52877b..4e9ddbe 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -57,20 +57,9 @@ jobs: - name: Launch RF Tests working-directory: ./test - run: >- + run: | export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH - robot - -d results - --xunit result.xml - --loglevel DEBUG:INFO - -v DB_MODULE:${{ env.DB_MODULE }} - -v DB_NAME:${{ env.DB_NAME }} - -v DB_USER:${{ env.DB_USER }} - -v DB_PASS:${{ env.DB_PASS }} - -v DB_HOST:${{ env.DB_HOST }} - -v DB_PORT:${{ env.DB_PORT }} - -v ORACLE_LIB_DIR:"${{ env.LIB_DIR }}" - tests/custom_db_tests/oracle_thick_mode.robot + robot -d results --xunit result.xml --loglevel DEBUG:INFO -v DB_MODULE:${{ env.DB_MODULE }} -v DB_NAME:${{ env.DB_NAME }} -v DB_USER:${{ env.DB_USER }} -v DB_PASS:${{ env.DB_PASS }} -v DB_HOST:${{ env.DB_HOST }} -v DB_PORT:${{ env.DB_PORT }} -v ORACLE_LIB_DIR:"${{ env.LIB_DIR }}" tests/custom_db_tests/oracle_thick_mode.robot - name: Upload Robot Logs if: ${{ always() }} From 8c7dbefc4b3e20779407ab7d3a9f208f666fe800 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 14:57:56 +0100 Subject: [PATCH 140/266] Revert "[DEBUG] messing up lines in shell script in CI" This reverts commit 98d0ffce52683fd80ec9526682c4bd70c7009de8. --- .github/workflows/oracle_thick_mode.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 4e9ddbe..e52877b 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -57,9 +57,20 @@ jobs: - name: Launch RF Tests working-directory: ./test - run: | + run: >- export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH - robot -d results --xunit result.xml --loglevel DEBUG:INFO -v DB_MODULE:${{ env.DB_MODULE }} -v DB_NAME:${{ env.DB_NAME }} -v DB_USER:${{ env.DB_USER }} -v DB_PASS:${{ env.DB_PASS }} -v DB_HOST:${{ env.DB_HOST }} -v DB_PORT:${{ env.DB_PORT }} -v ORACLE_LIB_DIR:"${{ env.LIB_DIR }}" tests/custom_db_tests/oracle_thick_mode.robot + robot + -d results + --xunit result.xml + --loglevel DEBUG:INFO + -v DB_MODULE:${{ env.DB_MODULE }} + -v DB_NAME:${{ env.DB_NAME }} + -v DB_USER:${{ env.DB_USER }} + -v DB_PASS:${{ env.DB_PASS }} + -v DB_HOST:${{ env.DB_HOST }} + -v DB_PORT:${{ env.DB_PORT }} + -v ORACLE_LIB_DIR:"${{ env.LIB_DIR }}" + tests/custom_db_tests/oracle_thick_mode.robot - name: Upload Robot Logs if: ${{ always() }} From 587aad48c81e05815bde01b5e90969fbf06c94b2 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 14:58:14 +0100 Subject: [PATCH 141/266] Revert "Try moving setting PATH var directly in front of robot launch" This reverts commit 1d98fd51b8bf54ce2b6bd5b92e2165c9901e14a6. --- .github/workflows/oracle_thick_mode.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index e52877b..8c7b512 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,6 +34,7 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip + export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH - name: Check out repository code uses: actions/checkout@v3 @@ -58,8 +59,7 @@ jobs: - name: Launch RF Tests working-directory: ./test run: >- - export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH - robot + robot -d results --xunit result.xml --loglevel DEBUG:INFO From bd744e9d9bac39e43bf6d6b3d673073762fcd591 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 14:59:46 +0100 Subject: [PATCH 142/266] Trying to fix the env var.. --- .github/workflows/oracle_thick_mode.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 8c7b512..2ac0880 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,7 +34,7 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - export LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH + echo "LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH" >> $GITHUB_ENV - name: Check out repository code uses: actions/checkout@v3 From 4f1c10dd0a7fe688eb548c8d56fbde4046fdc977 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 15:09:14 +0100 Subject: [PATCH 143/266] Try ldconfig.. --- .github/workflows/oracle_thick_mode.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 2ac0880..654e366 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,7 +34,8 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - echo "LD_LIBRARY_PATH=/opt/oracle/instantclient_21_4:$LD_LIBRARY_PATH" >> $GITHUB_ENV + sudo sh -c "echo /opt/oracle/instantclient_21_4 > /etc/ld.so.conf.d/oracle-instantclient.conf" + sudo ldconfig - name: Check out repository code uses: actions/checkout@v3 From 6955377eb7ab8b5e764283de2912e92bab646f4e Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 15:31:23 +0100 Subject: [PATCH 144/266] Try adding the libaio... --- .github/workflows/oracle_thick_mode.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml index 654e366..b949aa1 100644 --- a/.github/workflows/oracle_thick_mode.yml +++ b/.github/workflows/oracle_thick_mode.yml @@ -34,6 +34,7 @@ jobs: sudo mkdir /opt/oracle sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip + sudo apt install libaio1 sudo sh -c "echo /opt/oracle/instantclient_21_4 > /etc/ld.so.conf.d/oracle-instantclient.conf" sudo ldconfig From 924889c12aea766596c8dd3fa54978dc67029cb5 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 18:28:36 +0100 Subject: [PATCH 145/266] Remove oracle thick mode pipeline as they don't work anyway - local tests only --- .github/workflows/oracle_thick_mode.yml | 82 ------------------------- 1 file changed, 82 deletions(-) delete mode 100644 .github/workflows/oracle_thick_mode.yml diff --git a/.github/workflows/oracle_thick_mode.yml b/.github/workflows/oracle_thick_mode.yml deleted file mode 100644 index b949aa1..0000000 --- a/.github/workflows/oracle_thick_mode.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Oracle thick client mode tests - -on: [push, pull_request] - -env: - DB_MODULE: oracledb - DB_NAME: db - DB_USER: db_user - DB_PASS: pass - DB_HOST: 127.0.0.1 - DB_PORT: 1521 - LIB_DIR: /opt/oracle/instantclient_21_4 - -jobs: - tests: - runs-on: ubuntu-latest - services: - oracle: - image: gvenzl/oracle-free:latest - env: - ORACLE_PASSWORD: ${{ env.DB_PASS }} - ORACLE_DATABASE: ${{ env.DB_NAME }} - APP_USER: ${{ env.DB_USER }} - APP_USER_PASSWORD: ${{ env.DB_PASS }} - ports: - - 1521:1521 - # Provide healthcheck script options for startup - options: --health-cmd healthcheck.sh --health-interval 10s --health-timeout 5s --health-retries 10 - - steps: - - name: Install Oracle instant client - run: | - cd /opt/ - sudo mkdir /opt/oracle - sudo wget https://download.oracle.com/otn_software/linux/instantclient/214000/instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - sudo unzip instantclient-basic-linux.x64-21.4.0.0.0dbru.zip - sudo apt install libaio1 - sudo sh -c "echo /opt/oracle/instantclient_21_4 > /etc/ld.so.conf.d/oracle-instantclient.conf" - sudo ldconfig - - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Setup Python DB module - run: | - pip install ${{ env.DB_MODULE }} - - - name: Launch RF Tests - working-directory: ./test - run: >- - robot - -d results - --xunit result.xml - --loglevel DEBUG:INFO - -v DB_MODULE:${{ env.DB_MODULE }} - -v DB_NAME:${{ env.DB_NAME }} - -v DB_USER:${{ env.DB_USER }} - -v DB_PASS:${{ env.DB_PASS }} - -v DB_HOST:${{ env.DB_HOST }} - -v DB_PORT:${{ env.DB_PORT }} - -v ORACLE_LIB_DIR:"${{ env.LIB_DIR }}" - tests/custom_db_tests/oracle_thick_mode.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ From 593b8dd6644ebb769d19481a67071f0c220ecac8 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 6 Nov 2023 19:22:24 +0100 Subject: [PATCH 146/266] Docs about oracledb client mode --- README.md | 2 ++ src/DatabaseLibrary/connection_manager.py | 6 ++++++ test/tests/custom_db_tests/oracle_thick_mode.robot | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 309a05b..9a714ba 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ Therefore there are some modules, which are "natively" supported in the library ## Python modules currently "natively" supported ### Oracle - [oracledb](https://oracle.github.io/python-oracledb/) + - Both thick and thin client modes are supported - you can select one using the `driverMode` 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. - [cx_Oracle](https://oracle.github.io/python-cx_Oracle/) ### MySQL - [pymysql](https://github.com/PyMySQL/PyMySQL) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 2ecd7db..72a5c63 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -52,6 +52,12 @@ def connect_to_database( Loads the DB API 2.0 module given `dbapiModuleName` then uses it to connect to the database using `dbName`, `dbUsername`, and `dbPassword`. + The `driverMode` is used to select the *oracledb* client mode. + Allowed values are: + - _thin_ (default if omitted) + - _thick_ + - _thick,lib_dir=_ + Optionally, you can specify a `dbConfigFile` wherein it will load the default property values for `dbapiModuleName`, `dbName` `dbUsername` and `dbPassword` (note: specifying `dbapiModuleName`, `dbName` diff --git a/test/tests/custom_db_tests/oracle_thick_mode.robot b/test/tests/custom_db_tests/oracle_thick_mode.robot index 4dfb5ae..e9ba1e9 100644 --- a/test/tests/custom_db_tests/oracle_thick_mode.robot +++ b/test/tests/custom_db_tests/oracle_thick_mode.robot @@ -2,6 +2,10 @@ 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 From edbede84910c9e6f3a462882df185d1d659a1f08 Mon Sep 17 00:00:00 2001 From: Luke Dziewanowski Date: Thu, 21 Oct 2021 09:03:56 +0200 Subject: [PATCH 147/266] Add alias with backward compability --- .gitignore | 8 +- run_tests.sh | 30 ++++++ src/DatabaseLibrary/assertion.py | 56 ++++++----- src/DatabaseLibrary/connection_manager.py | 115 ++++++++++++++-------- src/DatabaseLibrary/query.py | 89 ++++++++++------- test/Aliases_SQLite3_DB_Tests.robot | 55 +++++++++++ test/tests/_old/DB2SQL_DB_Tests.robot | 29 ++++-- test/tests/_old/MySQL_DB_Tests.robot | 43 ++------ test/tests/_old/PostgreSQL_DB_Tests.robot | 8 +- test/tests/_old/PyODBC_DB_Tests.robot | 5 + 10 files changed, 283 insertions(+), 155 deletions(-) create mode 100755 run_tests.sh create mode 100644 test/Aliases_SQLite3_DB_Tests.robot diff --git a/.gitignore b/.gitignore index a9f49bc..0c61da2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,9 @@ build/ .py*/ **/my_db_test.db logs -**/output.xml -**/interactive_console_output.xml -**/log.html -**/report.html +interactive_console_output.xml +log.html +output.xml +report.html venv .runNumber diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..8d7caf4 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,30 @@ +#!/bin/bash -xe + +function startup { + docker-compose up -d + sleep 10 +} + +function cleanup { + docker-compose down +} + +if [[ $1 == "clean" ]] +then + trap cleanup EXIT + startup + sleep 10 +fi + +export MYSQL_PORT=`docker-compose port mysqldb 3306 | cut -d ":" -f 2` +export POSTGRESQL_PORT=`docker-compose port postgresqldb 5432 | cut -d ":" -f 2` +export DB2_PORT=`docker-compose port db2db 50000 | cut -d ":" -f 2` + +yq e -i ' + .MYSQL_DBPort = env(MYSQL_PORT) | + .POSTGRESQL_DBPort = env(POSTGRESQL_PORT) | + .DB2_DBPort = env(DB2_PORT) +' test/DB_Variables.yaml + + +robot --randomize none -V test/DB_Variables.yaml -i main test diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 185c2ba..ea58694 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -21,7 +21,7 @@ class Assertion: Assertion handles all the assertions of Database Library. """ - def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None): + def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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 @@ -34,6 +34,7 @@ def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = Fal 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' | + | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | alias=my_alias | Then you will get the following: | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | # PASS | @@ -46,12 +47,12 @@ def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = Fal | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | msg=my error message | """ logger.info(f"Executing : Check If Exists In Database | {selectStatement}") - if not self.query(selectStatement, sansTran): + if not self.query(selectStatement, sansTran, alias=alias): raise AssertionError( - msg or f"Expected to have have at least one row, " f"but got 0 rows from: '{selectStatement}'" + msg or f"Expected to have have at least one row, but got 0 rows from: '{selectStatement}'" ) - def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None): + def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None): """ This is the negation of `check_if_exists_in_database`. @@ -66,6 +67,7 @@ def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = 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' | + | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | alias=my_alias | Then you will get the following: | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' | # PASS | @@ -78,13 +80,13 @@ def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") - query_results = self.query(selectStatement, sansTran) + query_results = self.query(selectStatement, sansTran, alias=alias) 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): + def row_count_is_0(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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 @@ -97,6 +99,7 @@ def row_count_is_0(self, selectStatement: str, sansTran: bool = False, msg: Opti 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' | + | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | alias=my_alias | Then you will get the following: | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | # FAIL | @@ -109,12 +112,12 @@ def row_count_is_0(self, selectStatement: str, sansTran: bool = False, msg: Opti | Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ logger.info(f"Executing : Row Count Is 0 | {selectStatement}") - num_rows = self.row_count(selectStatement, sansTran) + num_rows = self.row_count(selectStatement, sansTran, alias=alias) if num_rows > 0: 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 + self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None ): """ Check if the number of rows returned from `selectStatement` is equal to the value submitted. If not, then this @@ -129,6 +132,7 @@ def row_count_is_equal_to_x( 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 | + | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | alias=my_alias | Then you will get the following: | Row Count Is Equal To X | SELECT id FROM person | 1 | # FAIL | @@ -141,14 +145,14 @@ def row_count_is_equal_to_x( | Row Count Is Equal To X | SELECT id FROM person | 1 | msg=my error message | """ logger.info(f"Executing : Row Count Is Equal To X | {selectStatement} | {numRows}") - num_rows = self.row_count(selectStatement, sansTran) + num_rows = self.row_count(selectStatement, sansTran, alias=alias) 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 + self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None ): """ Check if the number of rows returned from `selectStatement` is greater than the value submitted. If not, then @@ -163,6 +167,7 @@ def row_count_is_greater_than_x( 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 | + | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | alias=my_alias | Then you will get the following: | Row Count Is Greater Than X | SELECT id FROM person | 1 | # PASS | @@ -175,14 +180,14 @@ def row_count_is_greater_than_x( | Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | msg=my error message | """ logger.info(f"Executing : Row Count Is Greater Than X | {selectStatement} | {numRows}") - num_rows = self.row_count(selectStatement, sansTran) + num_rows = self.row_count(selectStatement, sansTran, alias=alias) 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 + self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None ): """ Check if the number of rows returned from `selectStatement` is less than the value submitted. If not, then this @@ -197,6 +202,7 @@ def row_count_is_less_than_x( When you have the following assertions in your robot | Row Count Is Less Than X | SELECT id FROM person | 3 | | 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' | 1 | alias=my_alias | Then you will get the following: | Row Count Is Less Than X | SELECT id FROM person | 3 | # PASS | @@ -209,13 +215,13 @@ def row_count_is_less_than_x( | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 | msg=my error message | """ logger.info(f"Executing : Row Count Is Less Than X | {selectStatement} | {numRows}") - num_rows = self.row_count(selectStatement, sansTran) + num_rows = self.row_count(selectStatement, sansTran, alias=alias) if num_rows >= int(numRows.encode("ascii")): raise AssertionError( msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" ) - def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional[str] = None): + def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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. The default error message can be overridden with the `msg` argument. @@ -228,6 +234,7 @@ def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional Then you will get the following: | Table Must Exist | person | # PASS | | Table Must Exist | first_name | # FAIL | + | Table Must Exist | first_name | alias=my_alias | Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Table Must Exist | person | True | @@ -236,30 +243,31 @@ def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional | Table Must Exist | first_name | msg=my error message | """ logger.info(f"Executing : Table Must Exist | {tableName}") - if self.db_api_module_name in ["cx_Oracle", "oracledb"]: + _, db_api_module_name = self._cache.switch(alias) + if db_api_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('{tableName}')" ) - table_exists = self.row_count(query, sansTran) > 0 - elif self.db_api_module_name in ["sqlite3"]: + table_exists = self.row_count(query, sansTran, alias=alias) > 0 + elif db_api_module_name in ["sqlite3"]: query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}' COLLATE NOCASE" - table_exists = self.row_count(query, sansTran) > 0 - elif self.db_api_module_name in ["ibm_db", "ibm_db_dbi"]: + table_exists = self.row_count(query, sansTran, alias=alias) > 0 + elif db_api_module_name in ["ibm_db", "ibm_db_dbi"]: query = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{tableName}')" - table_exists = self.row_count(query, sansTran) > 0 - elif self.db_api_module_name in ["teradata"]: + table_exists = self.row_count(query, sansTran, alias=alias) > 0 + elif db_api_module_name in ["teradata"]: query = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{tableName}'" - table_exists = self.row_count(query, sansTran) > 0 + table_exists = self.row_count(query, sansTran, alias=alias) > 0 else: try: query = f"SELECT * FROM information_schema.tables WHERE table_name='{tableName}'" - table_exists = self.row_count(query, sansTran) > 0 + table_exists = self.row_count(query, sansTran, alias=alias) > 0 except: logger.info("Database doesn't support information schema, try using a simple SQL request") try: query = f"SELECT 1 from {tableName} where 1=0" - self.row_count(query, sansTran) + self.row_count(query, sansTran, alias=alias) table_exists = True except: table_exists = False diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index f4d05e3..13259b0 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -21,6 +21,7 @@ import configparser as ConfigParser from robot.api import logger +from robot.utils import ConnectionCache class ConnectionManager: @@ -29,12 +30,8 @@ class ConnectionManager: """ def __init__(self): - """ - Initializes _dbconnection to None. - """ - self._dbconnection = None - self.db_api_module_name = None self.omit_trailing_semicolon = False + self._cache = ConnectionCache("No sessions created") def connect_to_database( self, @@ -48,6 +45,7 @@ def connect_to_database( dbDriver: Optional[str] = None, dbConfigFile: Optional[str] = None, driverMode: Optional[str] = None, + alias: Optional[str] = "default", ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -60,7 +58,7 @@ def connect_to_database( - _thick,lib_dir=_ Optionally, you can specify a `dbConfigFile` wherein it will load the - default property values for `dbapiModuleName`, `dbName` `dbUsername` + alias (or alias will be "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 @@ -70,7 +68,7 @@ def connect_to_database( your database credentials. Example db.cfg file - | [default] + | [alias] | dbapiModuleName=pymysqlforexample | dbName=yourdbname | dbUsername=yourusername @@ -81,6 +79,7 @@ def connect_to_database( Example usage: | # explicitly specifies all db property values | | Connect To Database | psycopg2 | my_db | postgres | s3cr3t | tiger.foobar.com | 5432 | + | Connect To Database | psycopg2 | my_db | postgres | s3cr3t | tiger.foobar.com | 5432 | alias=my_alias | | # loads all property values from default.cfg | | Connect To Database | dbConfigFile=default.cfg | @@ -100,18 +99,18 @@ def connect_to_database( config = ConfigParser.ConfigParser() config.read([dbConfigFile]) - 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")) + dbapiModuleName = dbapiModuleName or config.get(alias, "dbapiModuleName") + dbName = dbName or config.get(alias, "dbName") + dbUsername = dbUsername or config.get(alias, "dbUsername") + dbPassword = dbPassword if dbPassword is not None else config.get(alias, "dbPassword") + dbHost = dbHost or config.get(alias, "dbHost") or "localhost" + dbPort = int(dbPort or config.get(alias, "dbPort")) if dbapiModuleName == "excel" or dbapiModuleName == "excelrw": - self.db_api_module_name = "pyodbc" + db_api_module_name = "pyodbc" db_api_2 = importlib.import_module("pyodbc") else: - self.db_api_module_name = dbapiModuleName + db_api_module_name = dbapiModuleName db_api_2 = importlib.import_module(dbapiModuleName) if dbapiModuleName in ["MySQLdb", "pymysql"]: @@ -120,7 +119,7 @@ def connect_to_database( f"Connecting using : {dbapiModuleName}.connect(" f"db={dbName}, user={dbUsername}, passwd=***, host={dbHost}, port={dbPort}, charset={dbCharset})" ) - self._dbconnection = db_api_2.connect( + db_connection = db_api_2.connect( db=dbName, user=dbUsername, passwd=dbPassword, @@ -134,7 +133,7 @@ def connect_to_database( f"Connecting using : {dbapiModuleName}.connect(" f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort})" ) - self._dbconnection = db_api_2.connect( + db_connection = db_api_2.connect( database=dbName, user=dbUsername, password=dbPassword, @@ -151,14 +150,14 @@ def connect_to_database( else: con_str += f"SERVER={dbHost},{dbPort}" logger.info(f'Connecting using : {dbapiModuleName}.connect({con_str.replace(dbPassword, "***")})') - self._dbconnection = db_api_2.connect(con_str) + db_connection = db_api_2.connect(con_str) elif dbapiModuleName in ["excel"]: logger.info( f"Connecting using : {dbapiModuleName}.connect(" f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" f'ReadOnly=1;Extended Properties="Excel 8.0;HDR=YES";)' ) - self._dbconnection = db_api_2.connect( + db_connection = db_api_2.connect( f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" f'ReadOnly=1;Extended Properties="Excel 8.0;HDR=YES";)', autocommit=True, @@ -169,7 +168,7 @@ def connect_to_database( f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" f'ReadOnly=0;Extended Properties="Excel 8.0;HDR=YES";)', ) - self._dbconnection = db_api_2.connect( + db_connection = db_api_2.connect( f"DRIVER={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DBQ={dbName};" f'ReadOnly=0;Extended Properties="Excel 8.0;HDR=YES";)', autocommit=True, @@ -178,7 +177,7 @@ def connect_to_database( dbPort = dbPort or 50000 conn_str = f"DATABASE={dbName};HOSTNAME={dbHost};PORT={dbPort};PROTOCOL=TCPIP;UID={dbUsername};" logger.info(f"Connecting using : {dbapiModuleName}.connect(" f"{conn_str};PWD=***;)") - self._dbconnection = db_api_2.connect( + db_connection = db_api_2.connect( f"{conn_str};PWD={dbPassword};", "", "", @@ -189,7 +188,7 @@ def connect_to_database( logger.info( f"Connecting using: {dbapiModuleName}.connect(user={dbUsername}, password=***, dsn={oracle_dsn})" ) - self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, dsn=oracle_dsn) + db_connection = db_api_2.connect(user=dbUsername, password=dbPassword, dsn=oracle_dsn) self.omit_trailing_semicolon = True elif dbapiModuleName in ["oracledb"]: dbPort = dbPort or 1521 @@ -215,10 +214,10 @@ def connect_to_database( f"Connecting using: {dbapiModuleName}.connect(" f"user={dbUsername}, password=***, params={oracle_connection_params})" ) - self._dbconnection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) - assert self._dbconnection.thin == oracle_thin_mode, ( + db_connection = db_api_2.connect(user=dbUsername, password=dbPassword, params=oracle_connection_params) + assert db_connection.thin == oracle_thin_mode, ( "Expected oracledb to run in thin mode: {oracle_thin_mode}, " - f"but the connection has thin mode: {self._dbconnection.thin}" + f"but the connection has thin mode: {db_connection.thin}" ) self.omit_trailing_semicolon = True elif dbapiModuleName in ["teradata"]: @@ -228,7 +227,7 @@ def connect_to_database( f"Connecting using : {dbapiModuleName}.connect(" f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort})" ) - self._dbconnection = teradata_udaExec.connect( + db_connection = teradata_udaExec.connect( method="odbc", system=dbHost, database=dbName, @@ -243,7 +242,7 @@ def connect_to_database( f"Connecting using : {dbapiModuleName}.connect(" f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort})" ) - self._dbconnection = db_api_2.connect( + db_connection = db_api_2.connect( database=dbName, user=dbUsername, password=dbPassword, @@ -255,16 +254,17 @@ def connect_to_database( f"Connecting using : {dbapiModuleName}.connect(" f"database={dbName}, user={dbUsername}, password=***, host={dbHost}, port={dbPort}) " ) - self._dbconnection = db_api_2.connect( + db_connection = db_api_2.connect( database=dbName, user=dbUsername, password=dbPassword, host=dbHost, port=dbPort, ) + self._cache.register((db_connection, db_api_module_name), alias=alias) def connect_to_database_using_custom_params( - self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "" + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: Optional[str] = "default" ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -281,7 +281,7 @@ def connect_to_database_using_custom_params( | Connect To Database Using Custom Params | sqlite3 | database="./my_database.db", isolation_level=None | """ db_api_2 = importlib.import_module(dbapiModuleName) - self.db_api_module_name = dbapiModuleName + db_api_module_name = dbapiModuleName db_connect_string = f"db_api_2.connect({db_connect_string})" @@ -298,10 +298,11 @@ def connect_to_database_using_custom_params( f"{connection_string_with_hidden_pass})" ) - self._dbconnection = eval(db_connect_string) + db_connection = eval(db_connect_string) + self._cache.register((db_connection, db_api_module_name), alias=alias) def connect_to_database_using_custom_connection_string( - self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "" + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: Optional[str] = "default" ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -316,14 +317,15 @@ def connect_to_database_using_custom_connection_string( | Connect To Database Using Custom Connection String | oracledb | username/pass@localhost:1521/orclpdb | """ db_api_2 = importlib.import_module(dbapiModuleName) - self.db_api_module_name = dbapiModuleName + db_api_module_name = dbapiModuleName logger.info( f"Executing : Connect To Database Using Custom Connection String : {dbapiModuleName}.connect(" f"'{db_connect_string}')" ) - self._dbconnection = db_api_2.connect(db_connect_string) + db_connection = db_api_2.connect(db_connect_string) + self._cache.register((db_connection, db_api_module_name), alias=alias) - def disconnect_from_database(self, error_if_no_connection: bool = False): + def disconnect_from_database(self, error_if_no_connection: bool = False, alias: Optional[str] = "default"): """ Disconnects from the database. @@ -333,18 +335,35 @@ def disconnect_from_database(self, error_if_no_connection: bool = False): Example usage: | Disconnect From Database | # disconnects from current connection to the database | + | Disconnect From Database | alias=my_alias | # disconnects from current connection to the database | """ logger.info("Executing : Disconnect From Database") - if self._dbconnection is None: + try: + db_connection, _ = self._cache.switch(alias) + except RuntimeError: # Non-existing index or alias + db_connection = None + if db_connection: + db_connection.close() + else: log_msg = "No open database connection to close" if error_if_no_connection: - raise ConnectionError(log_msg) + raise ConnectionError(log_msg) from None logger.info(log_msg) - else: - self._dbconnection.close() - self._dbconnection = None - def set_auto_commit(self, autoCommit: bool = True): + def disconnect_from_all_databases(self): + """ + Disconnects from all the databases. + + For example: + | Disconnect From All Databases | # disconnects from all connections to the database | + """ + logger.info("Executing : Disconnect From All Databases") + for db_connection, _ in self._cache: + if db_connection: + db_connection.close() + self._cache.empty_cache() + + def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = "default"): """ Turn the autocommit on the database connection ON or OFF. @@ -357,8 +376,20 @@ def set_auto_commit(self, autoCommit: bool = True): Example usage: | # Default behaviour, sets auto commit to true | Set Auto Commit + | Set Auto Commit | alias=my_alias | | # Explicitly set the desired state | Set Auto Commit | False """ logger.info("Executing : Set Auto Commit") - self._dbconnection.autocommit = autoCommit + db_connection, _ = self._cache.switch(alias) + db_connection.autocommit = autoCommit + + def switch_database(self, alias): + """ + Switch default database. + + Example: + | Switch Database | my_alias | + | Switch Database | alias=my_alias | + """ + self._cache.switch(alias) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index f4c4786..a59ba53 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -24,7 +24,7 @@ class Query: Query handles all the querying done by the Database Library. """ - def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False): + def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = 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. @@ -43,6 +43,7 @@ def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool When you do the following: | @{queryResults} | Query | SELECT * FROM person | + | @{queryResults} | Query | SELECT * FROM person | alias=my_alias | | Log Many | @{queryResults} | You will get the following: @@ -58,10 +59,11 @@ def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{queryResults} | Query | SELECT * FROM person | True | """ + db_connection, _ = self._cache.switch(alias) cur = None try: - cur = self._dbconnection.cursor() - logger.info(f"Executing : Query | {selectStatement}") + cur = db_connection.cursor() + logger.info(f"Executing : Query | {selectStatement} ") self.__execute_sql(cur, selectStatement) all_rows = cur.fetchall() if returnAsDict: @@ -70,9 +72,9 @@ def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool return all_rows finally: if cur and not sansTran: - self._dbconnection.rollback() + db_connection.rollback() - def row_count(self, selectStatement: str, sansTran: bool = False): + def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = 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. @@ -84,6 +86,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False): When you do the following: | ${rowCount} | Row Count | SELECT * FROM person | + | ${rowCount} | Row Count | SELECT * FROM person | alias=my_alias | | Log | ${rowCount} | You will get the following: @@ -99,20 +102,21 @@ def row_count(self, selectStatement: str, sansTran: bool = False): Using optional `sansTran` to run command without an explicit transaction commit or rollback: | ${rowCount} | Row Count | SELECT * FROM person | True | """ + db_connection, db_api_module_name = self._cache.switch(alias) cur = None try: - cur = self._dbconnection.cursor() + cur = db_connection.cursor() logger.info(f"Executing : Row Count | {selectStatement}") self.__execute_sql(cur, selectStatement) data = cur.fetchall() - if self.db_api_module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: + if db_api_module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: return len(data) return cur.rowcount finally: if cur and not sansTran: - self._dbconnection.rollback() + db_connection.rollback() - def description(self, selectStatement: str, sansTran: bool = False): + def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = 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. @@ -123,6 +127,7 @@ def description(self, selectStatement: str, sansTran: bool = False): When you do the following: | @{queryResults} | Description | SELECT * FROM person | + | @{queryResults} | Description | SELECT * FROM person | alias=my_alias | | Log Many | @{queryResults} | You will get the following: @@ -133,9 +138,10 @@ def description(self, selectStatement: str, sansTran: bool = False): Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{queryResults} | Description | SELECT * FROM person | True | """ + db_connection, _ = self._cache.switch(alias) cur = None try: - cur = self._dbconnection.cursor() + cur = db_connection.cursor() logger.info("Executing : Description | {selectStatement}") self.__execute_sql(cur, selectStatement) description = list(cur.description) @@ -145,9 +151,9 @@ def description(self, selectStatement: str, sansTran: bool = False): return description finally: if cur and not sansTran: - self._dbconnection.rollback() + db_connection.rollback() - def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False): + def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, alias: Optional[str] = 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. @@ -156,6 +162,7 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False): When you do the following: | Delete All Rows From Table | person | + | Delete All Rows From Table | person | alias=my_alias | If all the rows can be successfully deleted, then you will get: | Delete All Rows From Table | person | # PASS | @@ -166,23 +173,24 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False): Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Delete All Rows From Table | person | True | """ + db_connection, _ = self._cache.switch(alias) cur = None query = f"DELETE FROM {tableName}" try: - cur = self._dbconnection.cursor() + cur = db_connection.cursor() logger.info(f"Executing : Delete All Rows From Table | {query}") result = self.__execute_sql(cur, query) if result is not None: if not sansTran: - self._dbconnection.commit() + db_connection.commit() return result if not sansTran: - self._dbconnection.commit() + db_connection.commit() finally: if cur and not sansTran: - self._dbconnection.rollback() + db_connection.rollback() - def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False): + def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, alias: Optional[str] = None): """ Executes the content of the `sqlScriptFileName` as SQL commands. Useful for setting the database to a known state before running your tests, or clearing out your test data after running each a test. Set optional input @@ -192,6 +200,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False): Sample usage : | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-setup.sql | + | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | alias=my_alias | | #interesting stuff here | | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-teardown.sql | | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-teardown.sql | @@ -240,11 +249,12 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False): Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | """ + db_connection, _ = self._cache.switch(alias) with open(sqlScriptFileName, encoding="UTF-8") as sql_file: cur = None try: statements_to_execute = [] - cur = self._dbconnection.cursor() + cur = db_connection.cursor() logger.info(f"Executing : Execute SQL Script | {sqlScriptFileName}") current_statement = "" inside_statements_group = False @@ -300,12 +310,12 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False): omit_semicolon = not statement.lower().endswith("end;") self.__execute_sql(cur, statement, omit_semicolon) if not sansTran: - self._dbconnection.commit() + db_connection.commit() finally: if cur and not sansTran: - self._dbconnection.rollback() + db_connection.rollback() - def execute_sql_string(self, sqlString: str, sansTran: bool = False): + def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None): """ 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. @@ -314,6 +324,7 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False): For example: | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | + | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | alias=my_alias | For example with an argument: | Execute Sql String | SELECT * FROM person WHERE first_name = ${FIRSTNAME} | @@ -321,18 +332,19 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False): 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 | """ + db_connection, _ = self._cache.switch(alias) cur = None try: - cur = self._dbconnection.cursor() + cur = db_connection.cursor() logger.info(f"Executing : Execute SQL String | {sqlString}") self.__execute_sql(cur, sqlString) if not sansTran: - self._dbconnection.commit() + db_connection.commit() finally: if cur and not sansTran: - self._dbconnection.rollback() + db_connection.rollback() - def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = None, sansTran: bool = False): + def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = None, sansTran: bool = False, alias: Optional[str] = None): """ Calls a stored procedure `spName` with the `spParams` - a *list* of parameters the procedure requires. Use the special *CURSOR* value for OUT params, which should receive result sets - @@ -371,20 +383,21 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{Param values} @{Result sets} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | ${Params} | True | """ + db_connection, db_api_module_name = self._cache.switch(alias) if spParams is None: spParams = [] cur = None try: logger.info(f"Executing : Call Stored Procedure | {spName} | {spParams}") - if self.db_api_module_name == "pymssql": - cur = self._dbconnection.cursor(as_dict=False) + if db_api_module_name == "pymssql": + cur = db_connection.cursor(as_dict=False) else: - cur = self._dbconnection.cursor() + cur = db_connection.cursor() param_values = [] result_sets = [] - if self.db_api_module_name == "pymysql": + if db_api_module_name == "pymysql": cur.callproc(spName, spParams) # first proceed the result sets if available @@ -401,22 +414,22 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non cur.execute(f"select @_{spName}_{i}") param_values.append(cur.fetchall()[0][0]) - elif self.db_api_module_name in ["oracledb", "cx_Oracle"]: + elif db_api_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 = spParams.copy() cursor_params = [] for i in range(0, len(spParams)): if spParams[i] == "CURSOR": - cursor_param = self._dbconnection.cursor() + cursor_param = db_connection.cursor() params_substituted[i] = cursor_param cursor_params.append(cursor_param) param_values = cur.callproc(spName, params_substituted) for result_set in cursor_params: result_sets.append(list(result_set)) - elif self.db_api_module_name in ["psycopg2", "psycopg3"]: - cur = self._dbconnection.cursor() + elif db_api_module_name in ["psycopg2", "psycopg3"]: + cur = db_connection.cursor() # check if "CURSOR" params were passed - they will be replaced # with cursor variables for storing the result sets params_substituted = spParams.copy() @@ -433,7 +446,7 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non result_set = cur.fetchall() result_sets.append(list(result_set)) else: - if self.db_api_module_name in ["psycopg3"]: + if db_api_module_name in ["psycopg3"]: result_sets_available = True while result_sets_available: result_sets.append(list(cur.fetchall())) @@ -444,10 +457,10 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non else: logger.info( - f"CAUTION! Calling a stored procedure for '{self.db_api_module_name}' is not tested, " + f"CAUTION! Calling a stored procedure for '{db_api_module_name}' is not tested, " "results might be invalid!" ) - cur = self._dbconnection.cursor() + cur = db_connection.cursor() param_values = cur.callproc(spName, spParams) logger.info("Reading the procedure results..") result_sets_available = True @@ -463,12 +476,12 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non result_sets_available = False if not sansTran: - self._dbconnection.commit() + db_connection.commit() return param_values, result_sets finally: if cur and not sansTran: - self._dbconnection.rollback() + db_connection.rollback() def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None): """ diff --git a/test/Aliases_SQLite3_DB_Tests.robot b/test/Aliases_SQLite3_DB_Tests.robot new file mode 100644 index 0000000..40d7d72 --- /dev/null +++ b/test/Aliases_SQLite3_DB_Tests.robot @@ -0,0 +1,55 @@ +*** Settings *** +Library DatabaseLibrary +Library OperatingSystem +Force Tags main db smoke + +*** Variables *** +${DBName1} my_db_test1 +${DBName2} my_db_test2 + +*** Keywords *** +Remove DB file if exists + [Arguments] ${DB_FILE} + Run Keyword And Ignore Error Remove File ${DB_FILE} + File Should Not Exist ${DB_FILE} + Comment Sleep 1s + +*** Test Cases *** +Remove old DB if exists + Remove DB file if exists ${CURDIR}/${DBName1}.db + Remove DB file if exists ${CURDIR}/${DBName2}.db + +Connect to SQLiteDB + Comment Connect To Database Using Custom Params sqlite3 database='path_to_dbfile\dbname.db' + Connect To Database Using Custom Params sqlite3 database="${CURDIR}/${DBName1}.db", isolation_level=None alias=db1 + Connect To Database Using Custom Params sqlite3 database="${CURDIR}/${DBName2}.db", isolation_level=None alias=db2 + +Create person table + ${output} = Execute SQL String CREATE TABLE person (id integer unique,first_name varchar,last_name varchar); alias=db2 + Log ${output} + Should Be Equal As Strings ${output} None + +Create foobar table + ${output} = Execute SQL String create table foobar (id integer primary key, firstname varchar unique) alias=db1 + Log ${output} + Should Be Equal As Strings ${output} None + +Table Must Exist - person + Table Must Exist person alias=db2 + +Table Shouldn't Exist - person + Run Keyword And Expect Error Table 'person' does not exist in the db Table Must Exist person alias=db1 + +Table Shouldn't Exist - foobar + Run Keyword And Expect Error Table 'foobar' does not exist in the db Table Must Exist foobar alias=db2 + +Switch database without alias + Switch Database db2 + Table Must Exist person + Run Keyword And Expect Error Table 'foobar' does not exist in the db Table Must Exist foobar + +Disconnect from database db1 + Disconnect From Database alias=db1 + +Disconnect from all databases + Disconnect From All Databases diff --git a/test/tests/_old/DB2SQL_DB_Tests.robot b/test/tests/_old/DB2SQL_DB_Tests.robot index 46e50f5..e3e4d48 100644 --- a/test/tests/_old/DB2SQL_DB_Tests.robot +++ b/test/tests/_old/DB2SQL_DB_Tests.robot @@ -1,8 +1,9 @@ *** 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 +Library Collections +Force Tags optional *** Test Cases *** Create person table @@ -11,8 +12,8 @@ Create person table 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 + Comment ${output} = Execute SQL Script ${CURDIR}/my_db_test_insertData.sql + ${output} = Execute SQL Script ${CURDIR}/my_db_test_insertData.sql Log ${output} Should Be Equal As Strings ${output} None @@ -53,11 +54,20 @@ Verify person Description @{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] + Should Be Equal As Strings ${output[0]} ID + ${expected}= Evaluate ['DEC', 'NUMERIC', 'DECIMAL', 'NUM'] + Lists Should Be Equal ${output[1].col_types} ${expected} ignore_order=True + Should Be Equal As Strings ${output[2:]} [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] + Should Be Equal As Strings ${output[0]} FIRST_NAME + ${expected}= Evaluate ['VARCHAR', 'CHARACTER VARYING', 'STRING', 'CHARACTER', 'CHAR', 'CHAR VARYING'] + Lists Should Be Equal ${output[1].col_types} ${expected} ignore_order=True + Should Be Equal As Strings ${output[2:]} [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] + Should Be Equal As Strings ${output[0]} LAST_NAME + ${expected}= Evaluate ['CHAR', 'CHAR VARYING', 'VARCHAR', 'CHARACTER VARYING', 'CHARACTER', 'STRING'] + Lists Should Be Equal ${output[1].col_types} ${expected} ignore_order=True + Should Be Equal As Strings ${output[2:]} [30, 30, 30, 0, True] ${NumColumns} = Get Length ${queryResults} Should Be Equal As Integers ${NumColumns} 3 @@ -75,8 +85,8 @@ 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 Insert Data Into Table foobar ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry'); @@ -97,3 +107,6 @@ Verify Query - Row Count foobar table 0 row Drop person and foobar table Execute SQL String DROP TABLE person; Execute SQL String DROP TABLE foobar; + +Disconnect from all databases + Disconnect From All Databases diff --git a/test/tests/_old/MySQL_DB_Tests.robot b/test/tests/_old/MySQL_DB_Tests.robot index c7f16d0..ac0114c 100644 --- a/test/tests/_old/MySQL_DB_Tests.robot +++ b/test/tests/_old/MySQL_DB_Tests.robot @@ -3,6 +3,8 @@ Suite Setup Connect To Database ${DBModule} ${DBName} ${DBUser} Suite Teardown Disconnect From Database Library DatabaseLibrary Library OperatingSystem +Test Tags main db smoke + *** Variables *** ${DBHost} 127.0.0.1 @@ -13,66 +15,53 @@ ${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 + Comment ${output} = Execute SQL Script ./my_db_test_insertData.sql + ${output} = Execute SQL Script ${CURDIR}/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} @@ -86,7 +75,6 @@ Verify person Description 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} @@ -98,114 +86,97 @@ Verify foobar Description 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 + +Disconnect from all databases + Disconnect From All Databases diff --git a/test/tests/_old/PostgreSQL_DB_Tests.robot b/test/tests/_old/PostgreSQL_DB_Tests.robot index d77c106..d099820 100644 --- a/test/tests/_old/PostgreSQL_DB_Tests.robot +++ b/test/tests/_old/PostgreSQL_DB_Tests.robot @@ -4,6 +4,8 @@ Suite Teardown Disconnect From Database Library DatabaseLibrary Library OperatingSystem Library Collections +Test Tags main db smoke + *** Variables *** ${DBHost} localhost @@ -61,7 +63,6 @@ Retrieve records from person table 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} @@ -75,7 +76,6 @@ Verify person Description 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} @@ -103,7 +103,6 @@ Verify Query - Row Count foobar table 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 @@ -146,3 +145,6 @@ Drop person and foobar tables ${output} = Execute SQL String DROP TABLE IF EXISTS person,foobar; Log ${output} Should Be Equal As Strings ${output} None + +Disconnect from all databases + Disconnect From All Databases diff --git a/test/tests/_old/PyODBC_DB_Tests.robot b/test/tests/_old/PyODBC_DB_Tests.robot index 874b121..a28cec0 100644 --- a/test/tests/_old/PyODBC_DB_Tests.robot +++ b/test/tests/_old/PyODBC_DB_Tests.robot @@ -4,6 +4,8 @@ Suite Teardown Disconnect From Database Library DatabaseLibrary Library Collections Library OperatingSystem +Test Tags main db smoke + *** Variables *** ${DBModule} pyodbc @@ -178,3 +180,6 @@ Drop person and foobar tables ${output} = Execute SQL String DROP TABLE IF EXISTS foobar; Log ${output} Should Be Equal As Strings ${output} None + +Disconnect from all databases + Disconnect From All Databases From 69dc9b16fb160381225cd715331b4be271229a5f Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Tue, 7 Nov 2023 21:23:50 +0100 Subject: [PATCH 148/266] Add tests for aliased connections --- test/Aliases_SQLite3_DB_Tests.robot | 55 -------- test/resources/common.resource | 9 +- .../common_tests/aliased_connection.robot | 129 ++++++++++++++++++ 3 files changed, 135 insertions(+), 58 deletions(-) delete mode 100644 test/Aliases_SQLite3_DB_Tests.robot create mode 100644 test/tests/common_tests/aliased_connection.robot diff --git a/test/Aliases_SQLite3_DB_Tests.robot b/test/Aliases_SQLite3_DB_Tests.robot deleted file mode 100644 index 40d7d72..0000000 --- a/test/Aliases_SQLite3_DB_Tests.robot +++ /dev/null @@ -1,55 +0,0 @@ -*** Settings *** -Library DatabaseLibrary -Library OperatingSystem -Force Tags main db smoke - -*** Variables *** -${DBName1} my_db_test1 -${DBName2} my_db_test2 - -*** Keywords *** -Remove DB file if exists - [Arguments] ${DB_FILE} - Run Keyword And Ignore Error Remove File ${DB_FILE} - File Should Not Exist ${DB_FILE} - Comment Sleep 1s - -*** Test Cases *** -Remove old DB if exists - Remove DB file if exists ${CURDIR}/${DBName1}.db - Remove DB file if exists ${CURDIR}/${DBName2}.db - -Connect to SQLiteDB - Comment Connect To Database Using Custom Params sqlite3 database='path_to_dbfile\dbname.db' - Connect To Database Using Custom Params sqlite3 database="${CURDIR}/${DBName1}.db", isolation_level=None alias=db1 - Connect To Database Using Custom Params sqlite3 database="${CURDIR}/${DBName2}.db", isolation_level=None alias=db2 - -Create person table - ${output} = Execute SQL String CREATE TABLE person (id integer unique,first_name varchar,last_name varchar); alias=db2 - Log ${output} - Should Be Equal As Strings ${output} None - -Create foobar table - ${output} = Execute SQL String create table foobar (id integer primary key, firstname varchar unique) alias=db1 - Log ${output} - Should Be Equal As Strings ${output} None - -Table Must Exist - person - Table Must Exist person alias=db2 - -Table Shouldn't Exist - person - Run Keyword And Expect Error Table 'person' does not exist in the db Table Must Exist person alias=db1 - -Table Shouldn't Exist - foobar - Run Keyword And Expect Error Table 'foobar' does not exist in the db Table Must Exist foobar alias=db2 - -Switch database without alias - Switch Database db2 - Table Must Exist person - Run Keyword And Expect Error Table 'foobar' does not exist in the db Table Must Exist foobar - -Disconnect from database db1 - Disconnect From Database alias=db1 - -Disconnect from all databases - Disconnect From All Databases diff --git a/test/resources/common.resource b/test/resources/common.resource index 6b78a1b..3856e19 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -24,13 +24,14 @@ ${DB_DRIVER} ODBC Driver 18 for SQL Server Connect To DB [Documentation] Connects to the database based on the current DB module under test ... and connection params set in global variables + [Arguments] ${alias}=${None} IF "${DB_MODULE_MODE}" == "custom" IF "${DB_MODULE}" == "sqlite3" - Remove File ${DBName}.db - Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None + IF $alias is None Remove File ${DBName}.db + Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None alias=${alias} ELSE ${Connection String}= Build Connection String - Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} + Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} alias=${alias} END ELSE IF "${DB_MODULE_MODE}" == "standard" IF "${DB_MODULE}" == "pyodbc" @@ -42,6 +43,7 @@ Connect To DB ... ${DB_HOST} ... ${DB_PORT} ... dbDriver=${DB_DRIVER} + ... alias=${alias} ELSE Connect To Database ... ${DB_MODULE} @@ -50,6 +52,7 @@ Connect To DB ... ${DB_PASS} ... ${DB_HOST} ... ${DB_PORT} + ... alias=${alias} END ELSE Fail Unexpected mode - ${DB_MODULE_MODE} diff --git a/test/tests/common_tests/aliased_connection.robot b/test/tests/common_tests/aliased_connection.robot new file mode 100644 index 0000000..29b8902 --- /dev/null +++ b/test/tests/common_tests/aliased_connection.robot @@ -0,0 +1,129 @@ +*** Settings *** +Resource ../../resources/common.resource +Suite Setup Run Keywords +... Connect To DB +... AND +... Create Person Table +Suite Teardown Run Keywords +... Connect To DB +... AND +... Drop Tables Person And Foobar +Test Teardown Disconnect From All Databases + + +*** 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 ConnectionError: Non-existing index or alias 'second' + ... Switch Database second + +Execute SQL Script - Insert Data In Person table + Connect To DB alias=aliased_conn + ${output}= Execute SQL Script ${CURDIR}/../insert_data_in_person_table.sql 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 + +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 - 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 foobar alias=aliased_conn + + + +*** Keywords *** +Aliases Suite Setup + Connect To DB + Create Person Table + +Aliases Suite Teardown + Connect To DB + Drop Tables Person And Foobar + Disconnect From All Databases From 69c31434d084e8f5d65a06a3a675b80e87c1cfea Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Wed, 8 Nov 2023 10:58:02 +0100 Subject: [PATCH 149/266] Redesign connection cache to clear connection after disconnect --- src/DatabaseLibrary/__init__.py | 2 +- src/DatabaseLibrary/assertion.py | 47 +++++++--- src/DatabaseLibrary/connection_manager.py | 86 +++++++++++++------ src/DatabaseLibrary/query.py | 82 +++++++++--------- test/resources/common.resource | 43 ++++++++-- .../common_tests/aliased_connection.robot | 60 ++++++------- 6 files changed, 205 insertions(+), 115 deletions(-) diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index efb757f..cd70e0b 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -69,7 +69,7 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): 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. + Therefore, there are some modules, which are "natively" supported in the library - and others, which may work and may not. See more on the [https://github.com/MarketSquare/Robotframework-Database-Library|project page on GitHub]. """ diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index ea58694..50c3c19 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -21,7 +21,9 @@ class Assertion: Assertion handles all the assertions of Database Library. """ - def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None): + def check_if_exists_in_database( + self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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 @@ -52,7 +54,9 @@ def check_if_exists_in_database(self, selectStatement: str, sansTran: bool = Fal msg or f"Expected to have have at least one row, but got 0 rows from: '{selectStatement}'" ) - def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None): + def check_if_not_exists_in_database( + self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None + ): """ This is the negation of `check_if_exists_in_database`. @@ -86,7 +90,9 @@ def check_if_not_exists_in_database(self, selectStatement: str, sansTran: bool = 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): + def row_count_is_0( + self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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 @@ -117,7 +123,12 @@ def row_count_is_0(self, selectStatement: str, sansTran: bool = False, msg: Opti 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 + self, + selectStatement: str, + numRows: str, + sansTran: bool = False, + msg: Optional[str] = None, + alias: str = "default", ): """ Check if the number of rows returned from `selectStatement` is equal to the value submitted. If not, then this @@ -152,7 +163,12 @@ def row_count_is_equal_to_x( ) def row_count_is_greater_than_x( - self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None + self, + selectStatement: str, + numRows: str, + sansTran: bool = False, + msg: Optional[str] = None, + alias: str = "default", ): """ Check if the number of rows returned from `selectStatement` is greater than the value submitted. If not, then @@ -187,7 +203,12 @@ def row_count_is_greater_than_x( ) def row_count_is_less_than_x( - self, selectStatement: str, numRows: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None + self, + selectStatement: str, + numRows: str, + sansTran: bool = False, + msg: Optional[str] = None, + alias: str = "default", ): """ Check if the number of rows returned from `selectStatement` is less than the value submitted. If not, then this @@ -221,7 +242,9 @@ def row_count_is_less_than_x( msg or f"Expected less than {numRows} rows, but {num_rows} were returned from '{selectStatement}'" ) - def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None): + def table_must_exist( + self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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. The default error message can be overridden with the `msg` argument. @@ -243,20 +266,20 @@ def table_must_exist(self, tableName: str, sansTran: bool = False, msg: Optional | Table Must Exist | first_name | msg=my error message | """ logger.info(f"Executing : Table Must Exist | {tableName}") - _, db_api_module_name = self._cache.switch(alias) - if db_api_module_name in ["cx_Oracle", "oracledb"]: + db_connection = self._get_connection_with_alias(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('{tableName}')" ) table_exists = self.row_count(query, sansTran, alias=alias) > 0 - elif db_api_module_name in ["sqlite3"]: + elif db_connection.module_name in ["sqlite3"]: query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{tableName}' COLLATE NOCASE" table_exists = self.row_count(query, sansTran, alias=alias) > 0 - elif db_api_module_name in ["ibm_db", "ibm_db_dbi"]: + elif db_connection.module_name in ["ibm_db", "ibm_db_dbi"]: query = f"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('{tableName}')" table_exists = self.row_count(query, sansTran, alias=alias) > 0 - elif db_api_module_name in ["teradata"]: + elif db_connection.module_name in ["teradata"]: query = f"SELECT TableName FROM DBC.TablesV WHERE TableKind='T' AND TableName='{tableName}'" table_exists = self.row_count(query, sansTran, alias=alias) > 0 else: diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 13259b0..2561cc8 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -13,7 +13,8 @@ # limitations under the License. import importlib -from typing import Optional +from dataclasses import dataclass +from typing import Any, Dict, Optional try: import ConfigParser @@ -21,7 +22,12 @@ import configparser as ConfigParser from robot.api import logger -from robot.utils import ConnectionCache + + +@dataclass +class Connection: + client: Any + module_name: str class ConnectionManager: @@ -30,8 +36,14 @@ class ConnectionManager: """ def __init__(self): - self.omit_trailing_semicolon = False - self._cache = ConnectionCache("No sessions created") + self.omit_trailing_semicolon: bool = False + self._connections: Dict[str, Connection] = {} + self.default_alias: str = "default" + + def _register_connection(self, client: Any, module_name: str, alias: str): + if alias in self._connections: + logger.warn(f"Overwriting not closed connection for alias = '{alias}'") + self._connections[alias] = Connection(client, module_name) def connect_to_database( self, @@ -45,11 +57,14 @@ def connect_to_database( dbDriver: Optional[str] = None, dbConfigFile: Optional[str] = None, driverMode: Optional[str] = None, - alias: Optional[str] = "default", + alias: str = "default", ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to - connect to the database using `dbName`, `dbUsername`, and `dbPassword`. + connect to the database using provided parameters such as `dbName`, `dbUsername`, and `dbPassword`. + + Optional ``alias`` parameter can be used for creating multiple open connections, even for different databases. + If the same alias is given twice then previous connection will be overriden. The `driverMode` is used to select the *oracledb* client mode. Allowed values are: @@ -261,10 +276,10 @@ def connect_to_database( host=dbHost, port=dbPort, ) - self._cache.register((db_connection, db_api_module_name), alias=alias) + self._register_connection(db_connection, db_api_module_name, alias) def connect_to_database_using_custom_params( - self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: Optional[str] = "default" + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: str = "default" ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -299,10 +314,10 @@ def connect_to_database_using_custom_params( ) db_connection = eval(db_connect_string) - self._cache.register((db_connection, db_api_module_name), alias=alias) + self._register_connection(db_connection, db_api_module_name, alias) def connect_to_database_using_custom_connection_string( - self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: Optional[str] = "default" + self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: str = "default" ): """ Loads the DB API 2.0 module given `dbapiModuleName` then uses it to @@ -323,9 +338,9 @@ def connect_to_database_using_custom_connection_string( f"'{db_connect_string}')" ) db_connection = db_api_2.connect(db_connect_string) - self._cache.register((db_connection, db_api_module_name), alias=alias) + self._register_connection(db_connection, db_api_module_name, alias) - def disconnect_from_database(self, error_if_no_connection: bool = False, alias: Optional[str] = "default"): + def disconnect_from_database(self, error_if_no_connection: bool = False, alias: Optional[str] = None): """ Disconnects from the database. @@ -338,13 +353,12 @@ def disconnect_from_database(self, error_if_no_connection: bool = False, alias: | Disconnect From Database | alias=my_alias | # disconnects from current connection to the database | """ logger.info("Executing : Disconnect From Database") + if not alias: + alias = self.default_alias try: - db_connection, _ = self._cache.switch(alias) - except RuntimeError: # Non-existing index or alias - db_connection = None - if db_connection: - db_connection.close() - else: + db_connection = self._connections.pop(alias) + db_connection.client.close() + except KeyError: # Non-existing alias log_msg = "No open database connection to close" if error_if_no_connection: raise ConnectionError(log_msg) from None @@ -358,12 +372,11 @@ def disconnect_from_all_databases(self): | Disconnect From All Databases | # disconnects from all connections to the database | """ logger.info("Executing : Disconnect From All Databases") - for db_connection, _ in self._cache: - if db_connection: - db_connection.close() - self._cache.empty_cache() + for db_connection in self._connections.values(): + db_connection.client.close() + self._connections = {} - def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = "default"): + def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = None): """ Turn the autocommit on the database connection ON or OFF. @@ -381,10 +394,10 @@ def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = "defau | Set Auto Commit | False """ logger.info("Executing : Set Auto Commit") - db_connection, _ = self._cache.switch(alias) - db_connection.autocommit = autoCommit + db_connection = self._get_connection_with_alias(alias) + db_connection.client.autocommit = autoCommit - def switch_database(self, alias): + def switch_database(self, alias: str): """ Switch default database. @@ -392,4 +405,23 @@ def switch_database(self, alias): | Switch Database | my_alias | | Switch Database | alias=my_alias | """ - self._cache.switch(alias) + if alias not in self._connections: + raise ValueError(f"Alias '{alias}' not found in existing connections.") + self.default_alias = alias + + def _get_connection_with_alias(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] diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index a59ba53..899cd75 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -24,7 +24,9 @@ class Query: Query handles all the querying done by the Database Library. """ - def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None): + def query( + self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = 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. @@ -59,10 +61,10 @@ def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{queryResults} | Query | SELECT * FROM person | True | """ - db_connection, _ = self._cache.switch(alias) + db_connection = self._get_connection_with_alias(alias) cur = None try: - cur = db_connection.cursor() + cur = db_connection.client.cursor() logger.info(f"Executing : Query | {selectStatement} ") self.__execute_sql(cur, selectStatement) all_rows = cur.fetchall() @@ -72,7 +74,7 @@ def query(self, selectStatement: str, sansTran: bool = False, returnAsDict: bool return all_rows finally: if cur and not sansTran: - db_connection.rollback() + db_connection.client.rollback() def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None): """ @@ -102,19 +104,19 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona Using optional `sansTran` to run command without an explicit transaction commit or rollback: | ${rowCount} | Row Count | SELECT * FROM person | True | """ - db_connection, db_api_module_name = self._cache.switch(alias) + db_connection = self._get_connection_with_alias(alias) cur = None try: - cur = db_connection.cursor() + cur = db_connection.client.cursor() logger.info(f"Executing : Row Count | {selectStatement}") self.__execute_sql(cur, selectStatement) data = cur.fetchall() - if db_api_module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: + if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: return len(data) return cur.rowcount finally: if cur and not sansTran: - db_connection.rollback() + db_connection.client.rollback() def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None): """ @@ -138,10 +140,10 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{queryResults} | Description | SELECT * FROM person | True | """ - db_connection, _ = self._cache.switch(alias) + db_connection = self._get_connection_with_alias(alias) cur = None try: - cur = db_connection.cursor() + cur = db_connection.client.cursor() logger.info("Executing : Description | {selectStatement}") self.__execute_sql(cur, selectStatement) description = list(cur.description) @@ -151,7 +153,7 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio return description finally: if cur and not sansTran: - db_connection.rollback() + db_connection.client.rollback() def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, alias: Optional[str] = None): """ @@ -173,22 +175,22 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Delete All Rows From Table | person | True | """ - db_connection, _ = self._cache.switch(alias) + db_connection = self._get_connection_with_alias(alias) cur = None query = f"DELETE FROM {tableName}" try: - cur = db_connection.cursor() + cur = db_connection.client.cursor() logger.info(f"Executing : Delete All Rows From Table | {query}") result = self.__execute_sql(cur, query) if result is not None: if not sansTran: - db_connection.commit() + db_connection.client.commit() return result if not sansTran: - db_connection.commit() + db_connection.client.commit() finally: if cur and not sansTran: - db_connection.rollback() + db_connection.client.rollback() def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, alias: Optional[str] = None): """ @@ -249,12 +251,12 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | """ - db_connection, _ = self._cache.switch(alias) + db_connection = self._get_connection_with_alias(alias) with open(sqlScriptFileName, encoding="UTF-8") as sql_file: cur = None try: statements_to_execute = [] - cur = db_connection.cursor() + cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL Script | {sqlScriptFileName}") current_statement = "" inside_statements_group = False @@ -310,10 +312,10 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali omit_semicolon = not statement.lower().endswith("end;") self.__execute_sql(cur, statement, omit_semicolon) if not sansTran: - db_connection.commit() + db_connection.client.commit() finally: if cur and not sansTran: - db_connection.rollback() + db_connection.client.rollback() def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None): """ @@ -332,19 +334,21 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Opti 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 | """ - db_connection, _ = self._cache.switch(alias) + db_connection = self._get_connection_with_alias(alias) cur = None try: - cur = db_connection.cursor() + cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL String | {sqlString}") self.__execute_sql(cur, sqlString) if not sansTran: - db_connection.commit() + db_connection.client.commit() finally: if cur and not sansTran: - db_connection.rollback() + db_connection.client.rollback() - def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = None, sansTran: bool = False, alias: Optional[str] = None): + def call_stored_procedure( + self, spName: str, spParams: Optional[List[str]] = None, sansTran: bool = False, alias: Optional[str] = None + ): """ Calls a stored procedure `spName` with the `spParams` - a *list* of parameters the procedure requires. Use the special *CURSOR* value for OUT params, which should receive result sets - @@ -383,21 +387,21 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{Param values} @{Result sets} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | ${Params} | True | """ - db_connection, db_api_module_name = self._cache.switch(alias) + db_connection = self._get_connection_with_alias(alias) if spParams is None: spParams = [] cur = None try: logger.info(f"Executing : Call Stored Procedure | {spName} | {spParams}") - if db_api_module_name == "pymssql": - cur = db_connection.cursor(as_dict=False) + if db_connection.module_name == "pymssql": + cur = db_connection.client.cursor(as_dict=False) else: - cur = db_connection.cursor() + cur = db_connection.client.cursor() param_values = [] result_sets = [] - if db_api_module_name == "pymysql": + if db_connection.module_name == "pymysql": cur.callproc(spName, spParams) # first proceed the result sets if available @@ -414,22 +418,22 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non cur.execute(f"select @_{spName}_{i}") param_values.append(cur.fetchall()[0][0]) - elif db_api_module_name in ["oracledb", "cx_Oracle"]: + 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 = spParams.copy() cursor_params = [] for i in range(0, len(spParams)): if spParams[i] == "CURSOR": - cursor_param = db_connection.cursor() + cursor_param = db_connection.client.cursor() params_substituted[i] = cursor_param cursor_params.append(cursor_param) param_values = cur.callproc(spName, params_substituted) for result_set in cursor_params: result_sets.append(list(result_set)) - elif db_api_module_name in ["psycopg2", "psycopg3"]: - cur = db_connection.cursor() + elif db_connection.module_name in ["psycopg2", "psycopg3"]: + cur = db_connection.client.cursor() # check if "CURSOR" params were passed - they will be replaced # with cursor variables for storing the result sets params_substituted = spParams.copy() @@ -446,7 +450,7 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non result_set = cur.fetchall() result_sets.append(list(result_set)) else: - if db_api_module_name in ["psycopg3"]: + if db_connection.module_name in ["psycopg3"]: result_sets_available = True while result_sets_available: result_sets.append(list(cur.fetchall())) @@ -457,10 +461,10 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non else: logger.info( - f"CAUTION! Calling a stored procedure for '{db_api_module_name}' is not tested, " + f"CAUTION! Calling a stored procedure for '{db_connection.module_name}' is not tested, " "results might be invalid!" ) - cur = db_connection.cursor() + cur = db_connection.client.cursor() param_values = cur.callproc(spName, spParams) logger.info("Reading the procedure results..") result_sets_available = True @@ -476,12 +480,12 @@ def call_stored_procedure(self, spName: str, spParams: Optional[List[str]] = Non result_sets_available = False if not sansTran: - db_connection.commit() + db_connection.client.commit() return param_values, result_sets finally: if cur and not sansTran: - db_connection.rollback() + db_connection.client.rollback() def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None): """ diff --git a/test/resources/common.resource b/test/resources/common.resource index 3856e19..1acf6e3 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -24,14 +24,13 @@ ${DB_DRIVER} ODBC Driver 18 for SQL Server Connect To DB [Documentation] Connects to the database based on the current DB module under test ... and connection params set in global variables - [Arguments] ${alias}=${None} IF "${DB_MODULE_MODE}" == "custom" IF "${DB_MODULE}" == "sqlite3" - IF $alias is None Remove File ${DBName}.db - Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None alias=${alias} + Remove File ${DBName}.db + Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None ELSE ${Connection String}= Build Connection String - Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} alias=${alias} + Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} END ELSE IF "${DB_MODULE_MODE}" == "standard" IF "${DB_MODULE}" == "pyodbc" @@ -43,7 +42,6 @@ Connect To DB ... ${DB_HOST} ... ${DB_PORT} ... dbDriver=${DB_DRIVER} - ... alias=${alias} ELSE Connect To Database ... ${DB_MODULE} @@ -52,12 +50,40 @@ Connect To DB ... ${DB_PASS} ... ${DB_HOST} ... ${DB_PORT} - ... alias=${alias} END ELSE Fail Unexpected mode - ${DB_MODULE_MODE} END +Connect To DB Aliased + [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} + IF "${DB_MODULE_MODE}" == "custom" + IF "${DB_MODULE}" == "sqlite3" + IF $alias is None + # Remove File ${DBName}.db TODO Figure out when clear db for aliased tests + Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None + ELSE + Connect To Database Using Custom Params + ... sqlite3 + ... database="./${DBName}.db", isolation_level=None + ... alias=${alias} + END + 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} dbDriver=${DB_DRIVER} + 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. @@ -87,6 +113,11 @@ Insert Data In Person Table Using SQL Script ${output}= Execute SQL Script ${CURDIR}/insert_data_in_person_table.sql RETURN ${output} +Insert Data In Person Table Using SQL Script Aliased + [Arguments] ${alias} + ${output}= Execute SQL Script ${CURDIR}/insert_data_in_person_table.sql alias=${alias} + RETURN ${output} + Create Foobar Table ${sql}= Catenate ... CREATE TABLE foobar diff --git a/test/tests/common_tests/aliased_connection.robot b/test/tests/common_tests/aliased_connection.robot index 29b8902..1beabbf 100644 --- a/test/tests/common_tests/aliased_connection.robot +++ b/test/tests/common_tests/aliased_connection.robot @@ -13,26 +13,26 @@ Test Teardown Disconnect From All Databases *** Test Cases *** Connections Can Be Aliased - Connect To DB # default alias - Connect To DB alias=second + Connect To DB Aliased # default alias + Connect To DB Aliased alias=second Default Alias Can Be Empty - Connect To DB # default alias + Connect To DB Aliased # default alias Query SELECT * FROM person - Connect To DB alias=second + Connect To DB Aliased 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 + Connect To DB Aliased # default alias + Connect To DB Aliased 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 + Connect To DB Aliased # 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} @@ -40,62 +40,63 @@ Disconnect Not Existing Alias Disconnect From Database error_if_no_connection=${True} Switch Not Existing Alias - Run Keyword And Expect Error ConnectionError: Non-existing index or alias 'second' + Run Keyword And Expect Error ValueError: Alias 'second' not found in existing connections. ... Switch Database second Execute SQL Script - Insert Data In Person table - Connect To DB alias=aliased_conn - ${output}= Execute SQL Script ${CURDIR}/../insert_data_in_person_table.sql alias=aliased_conn + Connect To DB Aliased alias=aliased_conn + ${output} Insert Data In Person Table Using SQL Script Aliased alias=aliased_conn Should Be Equal As Strings ${output} None Check If Exists In DB - Franz Allan - Connect To DB alias=aliased_conn + Connect To DB Aliased 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 + Connect To DB Aliased 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 + Connect To DB Aliased alias=aliased_conn Table Must Exist person alias=aliased_conn Verify Row Count is 0 - Connect To DB alias=aliased_conn + Connect To DB Aliased 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 + Connect To DB Aliased 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 + Connect To DB Aliased 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 + Connect To DB Aliased 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 + Connect To DB Aliased 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 + Connect To DB Aliased alias=aliased_conn + ${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 +Use Last Connected If Not Alias Provided + Connect To DB Aliased 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 + Connect To DB Aliased 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 @@ -112,18 +113,17 @@ Verify Query - Get results as a list of dictionaries Should Be Equal As Strings ${value 2} Jerry Verify Delete All Rows From Table - Connect To DB alias=aliased_conn + Connect To DB Aliased alias=aliased_conn Delete All Rows From Table person alias=aliased_conn - Row Count Is 0 SELECT * FROM foobar alias=aliased_conn - + Row Count Is 0 SELECT * FROM person alias=aliased_conn *** Keywords *** Aliases Suite Setup - Connect To DB + Connect To DB Aliased Create Person Table Aliases Suite Teardown - Connect To DB + Connect To DB Aliased Drop Tables Person And Foobar Disconnect From All Databases From d619a925fd9cdaa6fbfd1a6df23846aa76165f33 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Thu, 9 Nov 2023 14:16:00 +0100 Subject: [PATCH 150/266] Update documentation with aliases and improved display --- src/DatabaseLibrary/assertion.py | 49 ++++++++++++++++------- src/DatabaseLibrary/connection_manager.py | 12 +++--- src/DatabaseLibrary/query.py | 40 +++++++++++++----- 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 50c3c19..de09111 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -25,9 +25,9 @@ def check_if_exists_in_database( self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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 - commit or rollback. The default error message can be overridden with the `msg` argument. + 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 + commit or rollback. The default error message can be overridden with the ``msg`` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -42,10 +42,13 @@ def check_if_exists_in_database( | 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 | - Using optional `sansTran` to run command without an explicit transaction commit or rollback: + 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 Exists In Database | SELECT id FROM person WHERE first_name = 'John' | True | - Using optional `msg` to override the default error message: + Using optional ``msg`` to override the default error message: | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | msg=my error message | """ logger.info(f"Executing : Check If Exists In Database | {selectStatement}") @@ -60,9 +63,9 @@ def check_if_not_exists_in_database( """ 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. The default error message can be overridden with the `msg` argument. + 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. The default error message can be overridden with the ``msg`` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -77,10 +80,13 @@ def check_if_not_exists_in_database( | 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 | - Using optional `sansTran` to run command without an explicit transaction commit or rollback: + 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 | - Using optional `msg` to override the default error message: + Using optional ``msg`` to override the default error message: | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | msg=my error message | """ logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") @@ -94,9 +100,9 @@ def row_count_is_0( self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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 - rollback. The default error message can be overridden with the `msg` argument. + 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. The default error message can be overridden with the ``msg`` argument. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -111,7 +117,10 @@ def row_count_is_0( | 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 | - Using optional `sansTran` to run command without an explicit transaction commit or rollback: + 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: | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | True | Using optional `msg` to override the default error message: @@ -149,6 +158,9 @@ def row_count_is_equal_to_x( | 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 | + 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: | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | True | @@ -189,6 +201,9 @@ def row_count_is_greater_than_x( | 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 | + 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: | Row Count Is Greater Than X | SELECT id FROM person | 1 | True | @@ -229,6 +244,9 @@ def row_count_is_less_than_x( | 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 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: | Row Count Is Less Than X | SELECT id FROM person | 3 | True | @@ -259,6 +277,9 @@ def table_must_exist( | Table Must Exist | first_name | # FAIL | | Table Must Exist | first_name | alias=my_alias | + 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: | Table Must Exist | person | True | diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 2561cc8..d2d2699 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -60,21 +60,21 @@ def connect_to_database( alias: str = "default", ): """ - Loads the DB API 2.0 module given `dbapiModuleName` then uses it to - connect to the database using provided parameters such as `dbName`, `dbUsername`, and `dbPassword`. + Loads the DB API 2.0 module given ``dbapiModuleName`` then uses it to + connect to the database using provided parameters such as ``dbName``, ``dbUsername``, and ``dbPassword``. Optional ``alias`` parameter can be used for creating multiple open connections, even for different databases. If the same alias is given twice then previous connection will be overriden. - The `driverMode` is used to select the *oracledb* client mode. + The ``driverMode`` is used to select the *oracledb* client mode. Allowed values are: - _thin_ (default if omitted) - _thick_ - _thick,lib_dir=_ - Optionally, you can specify a `dbConfigFile` wherein it will load the - alias (or alias will be "default") property values for `dbapiModuleName`, `dbName` `dbUsername` - and `dbPassword` (note: specifying `dbapiModuleName`, `dbName` + Optionally, you can specify a ``dbConfigFile`` wherein it will load the + alias (or alias will be "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`. diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 899cd75..e5b6cb4 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -28,9 +28,12 @@ def query( self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = 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. + 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. + + Use optional ``alias`` parameter to specify what connection should be used for the query if you have more + than one connection open. Tip: Unless you want to log all column values of the specified rows, try specifying the column names in your select statements @@ -58,7 +61,7 @@ def query( And get the following See, Franz Allan - Using optional `sansTran` to run command without an explicit transaction commit or rollback: + Using optional ``sansTran`` to run command without an explicit transaction commit or rollback: | @{queryResults} | Query | SELECT * FROM person | True | """ db_connection = self._get_connection_with_alias(alias) @@ -78,8 +81,8 @@ def query( def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = 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. + 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. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -101,7 +104,10 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona And get the following 1 - Using optional `sansTran` to run command without an explicit transaction commit or rollback: + 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: | ${rowCount} | Row Count | SELECT * FROM person | True | """ db_connection = self._get_connection_with_alias(alias) @@ -120,8 +126,8 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = 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. + 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. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -137,6 +143,9 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio [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)] + 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: | @{queryResults} | Description | SELECT * FROM person | True | """ @@ -172,6 +181,9 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali will get: | Delete All Rows From Table | first_name | # 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: | Delete All Rows From Table | person | True | """ @@ -198,7 +210,6 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali 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 | @@ -248,6 +259,9 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali The slash signs ("/") are always ignored and have no impact on execution order. + 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: | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | """ @@ -331,6 +345,9 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Opti For example with an argument: | Execute Sql String | SELECT * FROM person WHERE first_name = ${FIRSTNAME} | + 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: | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | True | """ @@ -384,6 +401,9 @@ def call_stored_procedure( | # ${Param values} = [>, >] | | # ${result sets} = [[('Franz Allan',), ('Jerry',)], [('See',), ('Schneider',)]] | + 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: | @{Param values} @{Result sets} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | ${Params} | True | """ From 32722b194bf2babec826be4426ecfa3d54a7bf5f Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Thu, 9 Nov 2023 14:27:11 +0100 Subject: [PATCH 151/266] Update connection setup & teardown in tests --- run_tests.sh | 30 ------------------ src/DatabaseLibrary/assertion.py | 6 ++-- src/DatabaseLibrary/connection_manager.py | 10 ++++-- test/resources/common.resource | 10 ++++++ test/tests/_old/DB2SQL_DB_Tests.robot | 31 ++++++------------- test/tests/_old/MySQL_DB_Tests.robot | 3 -- test/tests/_old/PostgreSQL_DB_Tests.robot | 3 -- .../common_tests/aliased_connection.robot | 21 ++----------- 8 files changed, 32 insertions(+), 82 deletions(-) delete mode 100755 run_tests.sh diff --git a/run_tests.sh b/run_tests.sh deleted file mode 100755 index 8d7caf4..0000000 --- a/run_tests.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -xe - -function startup { - docker-compose up -d - sleep 10 -} - -function cleanup { - docker-compose down -} - -if [[ $1 == "clean" ]] -then - trap cleanup EXIT - startup - sleep 10 -fi - -export MYSQL_PORT=`docker-compose port mysqldb 3306 | cut -d ":" -f 2` -export POSTGRESQL_PORT=`docker-compose port postgresqldb 5432 | cut -d ":" -f 2` -export DB2_PORT=`docker-compose port db2db 50000 | cut -d ":" -f 2` - -yq e -i ' - .MYSQL_DBPort = env(MYSQL_PORT) | - .POSTGRESQL_DBPort = env(POSTGRESQL_PORT) | - .DB2_DBPort = env(DB2_PORT) -' test/DB_Variables.yaml - - -robot --randomize none -V test/DB_Variables.yaml -i main test diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index de09111..104a85b 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -137,7 +137,7 @@ def row_count_is_equal_to_x( numRows: str, sansTran: bool = False, msg: Optional[str] = None, - alias: str = "default", + alias: Optional[str] = None, ): """ Check if the number of rows returned from `selectStatement` is equal to the value submitted. If not, then this @@ -180,7 +180,7 @@ def row_count_is_greater_than_x( numRows: str, sansTran: bool = False, msg: Optional[str] = None, - alias: str = "default", + alias: Optional[str] = None, ): """ Check if the number of rows returned from `selectStatement` is greater than the value submitted. If not, then @@ -223,7 +223,7 @@ def row_count_is_less_than_x( numRows: str, sansTran: bool = False, msg: Optional[str] = None, - alias: str = "default", + alias: Optional[str] = None, ): """ Check if the number of rows returned from `selectStatement` is less than the value submitted. If not, then this diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index d2d2699..964b214 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -42,7 +42,10 @@ def __init__(self): def _register_connection(self, client: Any, module_name: str, alias: str): if alias in self._connections: - logger.warn(f"Overwriting not closed connection for alias = '{alias}'") + 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) def connect_to_database( @@ -354,7 +357,10 @@ def disconnect_from_database(self, error_if_no_connection: bool = False, alias: """ logger.info("Executing : Disconnect From Database") if not alias: - alias = self.default_alias + if not self._connections or self.default_alias in self._connections: + alias = self.default_alias + else: + alias = list(self._connections.keys())[-1] try: db_connection = self._connections.pop(alias) db_connection.client.close() diff --git a/test/resources/common.resource b/test/resources/common.resource index 1acf6e3..9ae0764 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -84,6 +84,16 @@ Connect To DB Aliased Fail Unexpected mode - ${DB_MODULE_MODE} END +Aliased Suite Setup + Connect To DB + Create Person Table + Disconnect From Database + +Aliased Suite Teardown + Connect To DB + Drop Tables Person And Foobar + Disconnect From Database + Build Connection String [Documentation] Returns the connection string variable depending on the DB module ... currently under test. diff --git a/test/tests/_old/DB2SQL_DB_Tests.robot b/test/tests/_old/DB2SQL_DB_Tests.robot index e3e4d48..a66bdaa 100644 --- a/test/tests/_old/DB2SQL_DB_Tests.robot +++ b/test/tests/_old/DB2SQL_DB_Tests.robot @@ -1,9 +1,8 @@ *** 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 -Library Collections -Force Tags optional *** Test Cases *** Create person table @@ -12,8 +11,8 @@ Create person table Should Be Equal As Strings ${output} None Execute SQL Script - Insert Data person table - Comment ${output} = Execute SQL Script ${CURDIR}/my_db_test_insertData.sql - ${output} = Execute SQL Script ${CURDIR}/my_db_test_insertData.sql + 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 @@ -54,20 +53,11 @@ Verify person Description @{queryResults} = Description SELECT * FROM person fetch first 1 rows only; Log Many @{queryResults} ${output} = Set Variable ${queryResults[0]} - Should Be Equal As Strings ${output[0]} ID - ${expected}= Evaluate ['DEC', 'NUMERIC', 'DECIMAL', 'NUM'] - Lists Should Be Equal ${output[1].col_types} ${expected} ignore_order=True - Should Be Equal As Strings ${output[2:]} [12, 12, 10, 0, True] + 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[0]} FIRST_NAME - ${expected}= Evaluate ['VARCHAR', 'CHARACTER VARYING', 'STRING', 'CHARACTER', 'CHAR', 'CHAR VARYING'] - Lists Should Be Equal ${output[1].col_types} ${expected} ignore_order=True - Should Be Equal As Strings ${output[2:]} [30, 30, 30, 0, True] + 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[0]} LAST_NAME - ${expected}= Evaluate ['CHAR', 'CHAR VARYING', 'VARCHAR', 'CHARACTER VARYING', 'CHARACTER', 'STRING'] - Lists Should Be Equal ${output[1].col_types} ${expected} ignore_order=True - Should Be Equal As Strings ${output[2:]} [30, 30, 30, 0, True] + 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 @@ -85,8 +75,8 @@ 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 Insert Data Into Table foobar ${output} = Execute SQL String INSERT INTO foobar VALUES(1,'Jerry'); @@ -106,7 +96,4 @@ Verify Query - Row Count foobar table 0 row Drop person and foobar table Execute SQL String DROP TABLE person; - Execute SQL String DROP TABLE foobar; - -Disconnect from all databases - Disconnect From All Databases + Execute SQL String DROP TABLE foobar; \ No newline at end of file diff --git a/test/tests/_old/MySQL_DB_Tests.robot b/test/tests/_old/MySQL_DB_Tests.robot index ac0114c..5960485 100644 --- a/test/tests/_old/MySQL_DB_Tests.robot +++ b/test/tests/_old/MySQL_DB_Tests.robot @@ -177,6 +177,3 @@ Drop person and foobar tables ${output} = Execute SQL String DROP TABLE IF EXISTS person,foobar; Log ${output} Should Be Equal As Strings ${output} None - -Disconnect from all databases - Disconnect From All Databases diff --git a/test/tests/_old/PostgreSQL_DB_Tests.robot b/test/tests/_old/PostgreSQL_DB_Tests.robot index d099820..5cbc3f1 100644 --- a/test/tests/_old/PostgreSQL_DB_Tests.robot +++ b/test/tests/_old/PostgreSQL_DB_Tests.robot @@ -145,6 +145,3 @@ Drop person and foobar tables ${output} = Execute SQL String DROP TABLE IF EXISTS person,foobar; Log ${output} Should Be Equal As Strings ${output} None - -Disconnect from all databases - Disconnect From All Databases diff --git a/test/tests/common_tests/aliased_connection.robot b/test/tests/common_tests/aliased_connection.robot index 1beabbf..2b30ae1 100644 --- a/test/tests/common_tests/aliased_connection.robot +++ b/test/tests/common_tests/aliased_connection.robot @@ -1,13 +1,7 @@ *** Settings *** Resource ../../resources/common.resource -Suite Setup Run Keywords -... Connect To DB -... AND -... Create Person Table -Suite Teardown Run Keywords -... Connect To DB -... AND -... Drop Tables Person And Foobar +Suite Setup Aliased Suite Setup +Suite Teardown Aliased Suite Teardown Test Teardown Disconnect From All Databases @@ -116,14 +110,3 @@ Verify Delete All Rows From Table Connect To DB Aliased alias=aliased_conn Delete All Rows From Table person alias=aliased_conn Row Count Is 0 SELECT * FROM person alias=aliased_conn - - -*** Keywords *** -Aliases Suite Setup - Connect To DB Aliased - Create Person Table - -Aliases Suite Teardown - Connect To DB Aliased - Drop Tables Person And Foobar - Disconnect From All Databases From c4f2a72668ee3840d6e6b08a9413e6ae084794be Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Fri, 10 Nov 2023 11:08:34 +0100 Subject: [PATCH 152/266] Move connection to ConnectionStore class --- src/DatabaseLibrary/assertion.py | 2 +- src/DatabaseLibrary/connection_manager.py | 102 +++++++++++++--------- src/DatabaseLibrary/query.py | 14 +-- 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 104a85b..bb19b63 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -287,7 +287,7 @@ def table_must_exist( | Table Must Exist | first_name | msg=my error message | """ logger.info(f"Executing : Table Must Exist | {tableName}") - db_connection = self._get_connection_with_alias(alias) + 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 " diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 964b214..09a4279 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -30,17 +30,12 @@ class Connection: module_name: str -class ConnectionManager: - """ - Connection Manager handles the connection & disconnection to the database. - """ - +class ConnectionStore: def __init__(self): - self.omit_trailing_semicolon: bool = False self._connections: Dict[str, Connection] = {} self.default_alias: str = "default" - def _register_connection(self, client: Any, module_name: str, alias: str): + def register_connection(self, client: Any, module_name: str, alias: str): if alias in self._connections: if alias == self.default_alias: logger.warn("Overwriting not closed connection.") @@ -48,6 +43,53 @@ def _register_connection(self, client: Any, module_name: str, alias: str): logger.warn(f"Overwriting not closed connection for alias = '{alias}'") self._connections[alias] = Connection(client, module_name) + def get_connection(self, alias: Optional[str]): + """ + 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]): + 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 + + def __iter__(self): + return iter(self._connections.values()) + + +class ConnectionManager: + """ + Connection Manager handles the connection & disconnection to the database. + """ + + def __init__(self): + self.omit_trailing_semicolon: bool = False + self.connection_store: ConnectionStore = ConnectionStore() + def connect_to_database( self, dbapiModuleName: Optional[str] = None, @@ -279,7 +321,7 @@ def connect_to_database( host=dbHost, port=dbPort, ) - self._register_connection(db_connection, db_api_module_name, alias) + self.connection_store.register_connection(db_connection, db_api_module_name, alias) def connect_to_database_using_custom_params( self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: str = "default" @@ -317,7 +359,7 @@ def connect_to_database_using_custom_params( ) db_connection = eval(db_connect_string) - self._register_connection(db_connection, db_api_module_name, alias) + self.connection_store.register_connection(db_connection, db_api_module_name, alias) def connect_to_database_using_custom_connection_string( self, dbapiModuleName: Optional[str] = None, db_connect_string: str = "", alias: str = "default" @@ -341,7 +383,7 @@ def connect_to_database_using_custom_connection_string( f"'{db_connect_string}')" ) db_connection = db_api_2.connect(db_connect_string) - self._register_connection(db_connection, db_api_module_name, alias) + 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): """ @@ -356,19 +398,14 @@ def disconnect_from_database(self, error_if_no_connection: bool = False, alias: | Disconnect From Database | alias=my_alias | # disconnects from current connection to the database | """ logger.info("Executing : Disconnect From Database") - if not alias: - if not self._connections or self.default_alias in self._connections: - alias = self.default_alias - else: - alias = list(self._connections.keys())[-1] - try: - db_connection = self._connections.pop(alias) - db_connection.client.close() - except KeyError: # Non-existing alias + 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: + db_connection.client.close() def disconnect_from_all_databases(self): """ @@ -378,9 +415,9 @@ def disconnect_from_all_databases(self): | Disconnect From All Databases | # disconnects from all connections to the database | """ logger.info("Executing : Disconnect From All Databases") - for db_connection in self._connections.values(): + for db_connection in self.connection_store: db_connection.client.close() - self._connections = {} + self.connection_store.clear() def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = None): """ @@ -400,7 +437,7 @@ def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = None): | Set Auto Commit | False """ logger.info("Executing : Set Auto Commit") - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) db_connection.client.autocommit = autoCommit def switch_database(self, alias: str): @@ -411,23 +448,4 @@ def switch_database(self, alias: str): | Switch Database | my_alias | | Switch Database | alias=my_alias | """ - if alias not in self._connections: - raise ValueError(f"Alias '{alias}' not found in existing connections.") - self.default_alias = alias - - def _get_connection_with_alias(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] + self.connection_store.switch(alias) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index e5b6cb4..2da52fe 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -64,7 +64,7 @@ def query( Using optional ``sansTran`` to run command without an explicit transaction commit or rollback: | @{queryResults} | Query | SELECT * FROM person | True | """ - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() @@ -110,7 +110,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona Using optional ``sansTran`` to run command without an explicit transaction commit or rollback: | ${rowCount} | Row Count | SELECT * FROM person | True | """ - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() @@ -149,7 +149,7 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{queryResults} | Description | SELECT * FROM person | True | """ - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() @@ -187,7 +187,7 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Delete All Rows From Table | person | True | """ - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) cur = None query = f"DELETE FROM {tableName}" try: @@ -265,7 +265,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali Using optional `sansTran` to run command without an explicit transaction commit or rollback: | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | """ - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) with open(sqlScriptFileName, encoding="UTF-8") as sql_file: cur = None try: @@ -351,7 +351,7 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Opti 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 | """ - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) cur = None try: cur = db_connection.client.cursor() @@ -407,7 +407,7 @@ def call_stored_procedure( Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{Param values} @{Result sets} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | ${Params} | True | """ - db_connection = self._get_connection_with_alias(alias) + db_connection = self.connection_store.get_connection(alias) if spParams is None: spParams = [] cur = None From e353506469e6aac5bace0e8067b78b404ae8266f Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 18:31:38 +0100 Subject: [PATCH 153/266] Improve keywords docs in assertions --- src/DatabaseLibrary/assertion.py | 182 ++++++++++--------------------- 1 file changed, 58 insertions(+), 124 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index bb19b63..64a8723 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -26,30 +26,21 @@ def check_if_exists_in_database( ): """ 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 - commit or rollback. The default error message can be overridden with the ``msg`` argument. + 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 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' | - | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | alias=my_alias | - - 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 | + The default error message can be overridden with the ``msg`` argument. 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 Exists In Database | SELECT id FROM person WHERE first_name = 'John' | True | - - Using optional ``msg`` to override the default error message: + 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' | sansTran=True | """ logger.info(f"Executing : Check If Exists In Database | {selectStatement}") if not self.query(selectStatement, sansTran, alias=alias): @@ -64,30 +55,20 @@ def check_if_not_exists_in_database( 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. The default error message can be overridden with the ``msg`` argument. - - For example, given we have a table `person` with the following data: - | id | first_name | last_name | - | 1 | Franz Allan | See | + will throw an AssertionError. - 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' | - | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | alias=my_alias | + Set optional input ``sansTran`` to _True_ to run command without an explicit transaction commit or rollback. - 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 | + The default error message can be overridden with the ``msg`` argument. 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 | - - Using optional ``msg`` to override the default error message: + 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 | """ logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") query_results = self.query(selectStatement, sansTran, alias=alias) @@ -101,30 +82,21 @@ def row_count_is_0( ): """ 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. The default error message can be overridden with the ``msg`` argument. - - For example, given we have a table `person` with the following data: - | id | first_name | last_name | - | 1 | Franz Allan | See | + AssertionError. - 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' | - | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | alias=my_alias | + 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 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | # FAIL | - | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | # PASS | + The default error message can be overridden with the ``msg`` argument. 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: - | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | True | - - Using optional `msg` to override the default error message: + 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 | """ logger.info(f"Executing : Row Count Is 0 | {selectStatement}") num_rows = self.row_count(selectStatement, sansTran, alias=alias) @@ -140,32 +112,21 @@ def row_count_is_equal_to_x( alias: Optional[str] = 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. The default error message can be overridden with the `msg` argument. + Check if the number of rows returned from ``selectStatement`` is equal to 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 Equal To X | SELECT id FROM person | 1 | - | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | - | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | alias=my_alias | - - 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. 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: - | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | True | - - Using optional `msg` to override the default error message: - | Row Count Is Equal To X | SELECT id FROM person | 1 | msg=my error message | + 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 | """ logger.info(f"Executing : Row Count Is Equal To X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran, alias=alias) @@ -183,32 +144,21 @@ def row_count_is_greater_than_x( alias: Optional[str] = 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. The default error message can be overridden with the `msg` argument. + Check if the number of rows returned from ``selectStatement`` is greater 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 Greater Than X | SELECT id FROM person | 1 | - | 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 | alias=my_alias | - - 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. 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: - | Row Count Is Greater Than X | SELECT id FROM person | 1 | True | - - Using optional `msg` to override the default error message: + 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 | """ logger.info(f"Executing : Row Count Is Greater Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran, alias=alias) @@ -226,32 +176,22 @@ def row_count_is_less_than_x( alias: Optional[str] = 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. + 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 | - | 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' | 1 | alias=my_alias | - - 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 | + 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. - 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 | + 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 | - Using optional `msg` to override the default error message: - | Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 | msg=my error message | """ logger.info(f"Executing : Row Count Is Less Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran, alias=alias) @@ -264,27 +204,21 @@ def table_must_exist( self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = 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. The default error message can be overridden with the `msg` argument. - - For example, given we have a table `person` in a database + Check if the table given exists in the database. - When you do the following: - | Table Must Exist | person | + Set optional input ``sansTran`` to True to run command without an + explicit transaction commit or rollback. - Then you will get the following: - | Table Must Exist | person | # PASS | - | Table Must Exist | first_name | # FAIL | - | Table Must Exist | first_name | alias=my_alias | + The default error message can be overridden with the ``msg`` argument. 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: - | Table Must Exist | person | True | - - Using optional `msg` to override the default error message: - | Table Must Exist | first_name | msg=my error message | + Examples: + | Table Must Exist | person | + | Table Must Exist | person | msg=my error message | + | Table Must Exist | person | alias=my_alias | + | Table Must Exist | person | sansTran=True | """ logger.info(f"Executing : Table Must Exist | {tableName}") db_connection = self.connection_store.get_connection(alias) From 82d27bfa51fa4729336f5c652d6e8117506223ec Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 18:39:22 +0100 Subject: [PATCH 154/266] Improve docs for new keyword "disconnect from all databases" --- src/DatabaseLibrary/connection_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 09a4279..9b66aee 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -409,10 +409,11 @@ def disconnect_from_database(self, error_if_no_connection: bool = False, alias: def disconnect_from_all_databases(self): """ - Disconnects from all the databases. + Disconnects from all the databases - + useful when testing with multiple database connections (aliases). For example: - | Disconnect From All Databases | # disconnects from all connections to the database | + | Disconnect From All Databases | # Closes connections to all databases | """ logger.info("Executing : Disconnect From All Databases") for db_connection in self.connection_store: From 3cfbd5e03182c0f81a476df2cc21010643268f49 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 19:24:18 +0100 Subject: [PATCH 155/266] Improve docs for new keyword "switch database" --- src/DatabaseLibrary/connection_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 9b66aee..9ae23d6 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -443,9 +443,9 @@ def set_auto_commit(self, autoCommit: bool = True, alias: Optional[str] = None): def switch_database(self, alias: str): """ - Switch default database. + Switch the default database connection to ``alias``. - Example: + Examples: | Switch Database | my_alias | | Switch Database | alias=my_alias | """ From 73797422119cd97103c9e095615caeddf363fbde Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 19:53:13 +0100 Subject: [PATCH 156/266] Improve naming and location of keywords for aliased connection tests --- test/resources/common.resource | 10 --- .../common_tests/aliased_connection.robot | 71 +++++++++++-------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/test/resources/common.resource b/test/resources/common.resource index 9ae0764..1acf6e3 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -84,16 +84,6 @@ Connect To DB Aliased Fail Unexpected mode - ${DB_MODULE_MODE} END -Aliased Suite Setup - Connect To DB - Create Person Table - Disconnect From Database - -Aliased Suite Teardown - Connect To DB - Drop Tables Person And Foobar - Disconnect From Database - Build Connection String [Documentation] Returns the connection string variable depending on the DB module ... currently under test. diff --git a/test/tests/common_tests/aliased_connection.robot b/test/tests/common_tests/aliased_connection.robot index 2b30ae1..2cb007c 100644 --- a/test/tests/common_tests/aliased_connection.robot +++ b/test/tests/common_tests/aliased_connection.robot @@ -1,107 +1,108 @@ *** Settings *** Resource ../../resources/common.resource -Suite Setup Aliased Suite Setup -Suite Teardown Aliased Suite Teardown -Test Teardown Disconnect From All Databases + +Suite Setup Connect, Create Some Data And Disconnect +Suite Teardown Connect, Clean Up Data And Disconnect +Test Teardown Disconnect From All Databases *** Test Cases *** Connections Can Be Aliased - Connect To DB Aliased # default alias - Connect To DB Aliased alias=second + Connect To DB # default alias + Connect To DB alias=second Default Alias Can Be Empty - Connect To DB Aliased # default alias + Connect To DB # default alias Query SELECT * FROM person - Connect To DB Aliased alias=second + Connect To DB alias=second Query SELECT * FROM person Query SELECT * FROM person alias=second Switch From Default And Disconnect - Connect To DB Aliased # default alias - Connect To DB Aliased alias=second + Connect To DB # default alias + Connect To DB alias=second Switch Database second - Query SELECT * FROM person # query with 'second' connection + Query SELECT * FROM person # query with 'second' connection Disconnect From Database alias=second - Query SELECT * FROM person # query with 'default' connection + Query SELECT * FROM person # query with 'default' connection Disconnect Not Existing Alias - Connect To DB Aliased # default alias - Disconnect From Database alias=idontexist # silent warning + 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} + 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 - Connect To DB Aliased alias=aliased_conn + Connect To DB alias=aliased_conn ${output} Insert Data In Person Table Using SQL Script Aliased alias=aliased_conn Should Be Equal As Strings ${output} None Check If Exists In DB - Franz Allan - Connect To DB Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + Connect To DB alias=aliased_conn Table Must Exist person alias=aliased_conn Verify Row Count is 0 - Connect To DB Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 Aliased alias=aliased_conn + 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 + ${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 + ${value 1} Get From Dictionary ${output}[0] first_name END TRY - ${value 2}= Get From Dictionary ${output}[1] FIRST_NAME + ${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 + ${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 @@ -110,3 +111,13 @@ Verify Delete All Rows From Table Connect To DB Aliased 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 + Connect To DB + Create Person Table + Disconnect From Database + +Connect, Clean Up Data And Disconnect + Connect To DB + Drop Tables Person And Foobar + Disconnect From Database From 89c95826adf1a69841bcf9ace2dd426718a39398 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 19:53:49 +0100 Subject: [PATCH 157/266] Merge two global "connect to db" keywords in tests --- test/resources/common.resource | 48 +++---------------- .../common_tests/aliased_connection.robot | 4 +- 2 files changed, 9 insertions(+), 43 deletions(-) diff --git a/test/resources/common.resource b/test/resources/common.resource index 1acf6e3..9e2a43c 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -22,40 +22,6 @@ ${DB_DRIVER} ODBC Driver 18 for SQL Server *** Keywords *** Connect To DB - [Documentation] Connects to the database based on the current DB module under test - ... and connection params set in global variables - IF "${DB_MODULE_MODE}" == "custom" - IF "${DB_MODULE}" == "sqlite3" - Remove File ${DBName}.db - Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None - ELSE - ${Connection String}= Build Connection String - Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} - END - ELSE IF "${DB_MODULE_MODE}" == "standard" - IF "${DB_MODULE}" == "pyodbc" - Connect To Database - ... ${DB_MODULE} - ... ${DB_NAME} - ... ${DB_USER} - ... ${DB_PASS} - ... ${DB_HOST} - ... ${DB_PORT} - ... dbDriver=${DB_DRIVER} - ELSE - Connect To Database - ... ${DB_MODULE} - ... ${DB_NAME} - ... ${DB_USER} - ... ${DB_PASS} - ... ${DB_HOST} - ... ${DB_PORT} - END - ELSE - Fail Unexpected mode - ${DB_MODULE_MODE} - END - -Connect To DB Aliased [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} @@ -63,15 +29,13 @@ Connect To DB Aliased IF $alias is not None Set To Dictionary ${DB_KWARGS} alias=${alias} IF "${DB_MODULE_MODE}" == "custom" IF "${DB_MODULE}" == "sqlite3" - IF $alias is None - # Remove File ${DBName}.db TODO Figure out when clear db for aliased tests - Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None - ELSE - Connect To Database Using Custom Params - ... sqlite3 - ... database="./${DBName}.db", isolation_level=None - ... alias=${alias} + ${DB_Filename}= Set Variable ${DBName} + IF $alias is not None + ${DB_Filename}= Set Variable ${DB_Filename}_${alias} END + Remove File ${DB_Filename}.db + Connect To Database Using Custom Params sqlite3 database="./${DB_Filename}.db", isolation_level=None + ... &{DB_KWARGS} ELSE ${Connection String}= Build Connection String Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} &{DB_KWARGS} diff --git a/test/tests/common_tests/aliased_connection.robot b/test/tests/common_tests/aliased_connection.robot index 2cb007c..ab2c84b 100644 --- a/test/tests/common_tests/aliased_connection.robot +++ b/test/tests/common_tests/aliased_connection.robot @@ -108,9 +108,11 @@ Verify Query - Get results as a list of dictionaries Should Be Equal As Strings ${value 2} Jerry Verify Delete All Rows From Table - Connect To DB Aliased alias=aliased_conn + 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 Connect To DB From d3adab0b1f27b96f193b55eca3b2150e4e53f358 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 20:41:13 +0100 Subject: [PATCH 158/266] Aliases tests don't work for SQLite as each connection is always a new file --- test/resources/common.resource | 20 ++++++++----------- .../common_tests/aliased_connection.robot | 15 ++++++++++---- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/test/resources/common.resource b/test/resources/common.resource index 9e2a43c..34831bc 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -29,12 +29,8 @@ Connect To DB IF $alias is not None Set To Dictionary ${DB_KWARGS} alias=${alias} IF "${DB_MODULE_MODE}" == "custom" IF "${DB_MODULE}" == "sqlite3" - ${DB_Filename}= Set Variable ${DBName} - IF $alias is not None - ${DB_Filename}= Set Variable ${DB_Filename}_${alias} - END - Remove File ${DB_Filename}.db - Connect To Database Using Custom Params sqlite3 database="./${DB_Filename}.db", isolation_level=None + Remove File ${DBName}.db + Connect To Database Using Custom Params sqlite3 database="./${DBName}.db", isolation_level=None ... &{DB_KWARGS} ELSE ${Connection String}= Build Connection String @@ -74,12 +70,12 @@ Create Person Table And Insert Data Insert Data In Person Table Using SQL Script Insert Data In Person Table Using SQL Script - ${output}= Execute SQL Script ${CURDIR}/insert_data_in_person_table.sql - RETURN ${output} - -Insert Data In Person Table Using SQL Script Aliased - [Arguments] ${alias} - ${output}= Execute SQL Script ${CURDIR}/insert_data_in_person_table.sql alias=${alias} + [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 diff --git a/test/tests/common_tests/aliased_connection.robot b/test/tests/common_tests/aliased_connection.robot index ab2c84b..e61b390 100644 --- a/test/tests/common_tests/aliased_connection.robot +++ b/test/tests/common_tests/aliased_connection.robot @@ -1,9 +1,10 @@ *** 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 -Suite Setup Connect, Create Some Data And Disconnect -Suite Teardown Connect, Clean Up Data And Disconnect -Test Teardown Disconnect From All Databases +Test Setup Connect, Create Some Data And Disconnect +Test Teardown Connect, Clean Up Data And Disconnect *** Test Cases *** @@ -39,8 +40,9 @@ Switch Not Existing Alias ... 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 Aliased 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 @@ -115,11 +117,16 @@ Verify Delete All Rows From Table *** 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 From abca11055d37c7b41e9771deeccbeb58dfb0a975 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 21:07:23 +0100 Subject: [PATCH 159/266] Test for real multiple connections --- .../multiple_connections.robot | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 test/tests/custom_db_tests/multiple_connections.robot 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 0000000..540aee7 --- /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} From 5f5092054c286fafdeedfe8630ea3039739ab79a Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 21:22:50 +0100 Subject: [PATCH 160/266] Add example with aliases into readme --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a714ba..6c37d4d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ Here you can find the [keyword docs](http://marketsquare.github.io/Robotframewor ``` pip install robotframework-databaselibrary ``` -# Usage example +# Usage examples +## Basic usage ```RobotFramework *** Settings *** Library DatabaseLibrary @@ -42,6 +43,32 @@ Person Table Contains No Joe ... WHERE FIRST_NAME= 'Joe' Check If Not Exists In Database ${sql} ``` +## Handling multiple database connections +```RobotFramework +*** 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 db_user pass 127.0.0.1 5432 + ... alias=postgres + Connect To Database pymysql db db_user pass 127.0.0.1 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 +``` + See more examples in the folder `tests`. # Database modules compatibility The library is basically compatible with any [Python Database API Specification 2.0](https://peps.python.org/pep-0249/) module. From 3721d08124b45679c8ad257629abb640a90209a5 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 18 Nov 2023 21:24:58 +0100 Subject: [PATCH 161/266] Bump version to 1.4 - slowly getting ready for release --- src/DatabaseLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 4cf03a8..5d4d63c 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "1.3.1" +VERSION = "1.4" From 7df2cca017dd24ca3ba5c8c1012aa923e92c72a6 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Fri, 10 Nov 2023 13:39:55 +0100 Subject: [PATCH 162/266] Fix and improve error handling for missing or invalid configuration file --- .github/workflows/unit_tests.yml | 59 ++++++++++++++++++++ src/DatabaseLibrary/connection_manager.py | 56 +++++++++++++------ test/tests/__init__.py | 0 test/tests/utests/__init__.py | 0 test/tests/utests/test_connection_manager.py | 48 ++++++++++++++++ test/tests/utests/test_data/alias.cfg | 2 + test/tests/utests/test_data/empty.cfg | 0 test/tests/utests/test_data/no_option.cfg | 3 + 8 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/unit_tests.yml create mode 100644 test/tests/__init__.py create mode 100644 test/tests/utests/__init__.py create mode 100644 test/tests/utests/test_connection_manager.py create mode 100644 test/tests/utests/test_data/alias.cfg create mode 100644 test/tests/utests/test_data/empty.cfg create mode 100644 test/tests/utests/test_data/no_option.cfg diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..1f284dc --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,59 @@ +--- +# This workflow will install Python dependencies +# and run unit tests for given OSes + +name: Unit tests + +on: [push, pull_request] + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - os: 'ubuntu-latest' + python-version: '3.7' + rf-version: '3.2.2' + - os: 'ubuntu-latest' + python-version: '3.8' + rf-version: '4.1.3' + - os: 'ubuntu-latest' + python-version: '3.9' + rf-version: '5.0.1' + - os: 'ubuntu-latest' + python-version: '3.10' + rf-version: '6.1.1' + - os: 'ubuntu-latest' + python-version: '3.11' + rf-version: '6.1.1' + - os: 'ubuntu-latest' + python-version: '3.12' + rf-version: '7.0a1' + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install robotframework==${{ matrix.rf-version }} coverage pytest + pip install . + + - name: Run unit tests with coverage + run: + coverage run -m pytest + + - name: Codecov + uses: codecov/codecov-action@v3 + with: + name: ${{ matrix.python-version }}-${{ matrix.os }}-${{ matrix.rf-version }} diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 9ae23d6..18c042b 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -13,14 +13,11 @@ # limitations under the License. import importlib +from configparser import ConfigParser, NoOptionError, NoSectionError from dataclasses import dataclass +from pathlib import Path from typing import Any, Dict, Optional -try: - import ConfigParser -except: - import configparser as ConfigParser - from robot.api import logger @@ -81,6 +78,35 @@ 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) + if not config_path.exists(): + return None + config = ConfigParser() + config.read([config_path]) + return config + + def get(self, param: str) -> str: + if self.config is None: + raise ValueError(f"Required '{param}' parameter was not provided in keyword arguments.") from None + try: + return self.config.get(self.alias, param) + except NoSectionError: + raise ValueError(f"Configuration file does not have [{self.alias}] section.") from None + except NoOptionError: + raise ValueError( + f"Required '{param}' parameter missing in both keyword arguments and configuration file." + ) from None + + class ConnectionManager: """ Connection Manager handles the connection & disconnection to the database. @@ -153,18 +179,14 @@ def connect_to_database( | # uses explicit `dbapiModuleName` and `dbName` but uses the `dbUsername` and `dbPassword` in './resources/db.cfg' | | Connect To Database | psycopg2 | my_db_test | """ - - if dbConfigFile is None: - dbConfigFile = "./resources/db.cfg" - config = ConfigParser.ConfigParser() - config.read([dbConfigFile]) - - dbapiModuleName = dbapiModuleName or config.get(alias, "dbapiModuleName") - dbName = dbName or config.get(alias, "dbName") - dbUsername = dbUsername or config.get(alias, "dbUsername") - dbPassword = dbPassword if dbPassword is not None else config.get(alias, "dbPassword") - dbHost = dbHost or config.get(alias, "dbHost") or "localhost" - dbPort = int(dbPort or config.get(alias, "dbPort")) + config = ConfigReader(dbConfigFile, alias) + + dbapiModuleName = dbapiModuleName or config.get("dbapiModuleName") + dbName = dbName or config.get("dbName") + dbUsername = dbUsername or config.get("dbUsername") + dbPassword = dbPassword if dbPassword is not None else config.get("dbPassword") + dbHost = dbHost or config.get("dbHost") or "localhost" + dbPort = int(dbPort if dbPort is not None else config.get("dbPort")) if dbapiModuleName == "excel" or dbapiModuleName == "excelrw": db_api_module_name = "pyodbc" diff --git a/test/tests/__init__.py b/test/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/tests/utests/__init__.py b/test/tests/utests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/tests/utests/test_connection_manager.py b/test/tests/utests/test_connection_manager.py new file mode 100644 index 0000000..bbc9ea2 --- /dev/null +++ b/test/tests/utests/test_connection_manager.py @@ -0,0 +1,48 @@ +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=re.escape("Configuration file does not have [default] section.")): + conn_manager.connect_to_database("my_client", dbConfigFile=config_path) + + def test_connect_no_params_no_config(self): + conn_manager = ConnectionManager() + with pytest.raises(ValueError, match="Required 'dbName' parameter was not provided in keyword arguments."): + conn_manager.connect_to_database("my_client") + + def test_connect_missing_option(self): + conn_manager = ConnectionManager() + config_path = str(TEST_DATA / "no_option.cfg") + with pytest.raises( + ValueError, + match="Required 'dbPassword' parameter missing in both keyword arguments and configuration file.", + ): + conn_manager.connect_to_database("my_client", dbConfigFile=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", + dbUsername="name", + dbPassword="password", + dbHost="host", + dbPort=0, + dbConfigFile=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 0000000..06d0431 --- /dev/null +++ b/test/tests/utests/test_data/alias.cfg @@ -0,0 +1,2 @@ +[alias2] +dbName = example diff --git a/test/tests/utests/test_data/empty.cfg b/test/tests/utests/test_data/empty.cfg new file mode 100644 index 0000000..e69de29 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 0000000..4e0db9e --- /dev/null +++ b/test/tests/utests/test_data/no_option.cfg @@ -0,0 +1,3 @@ +[default] +dbName = example +dbUsername = example \ No newline at end of file From 8240bdb83e479f9d0221e3a5d42eb0f06753c028 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:02:22 +0200 Subject: [PATCH 163/266] Update query.py add parameters argument --- src/DatabaseLibrary/query.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 2da52fe..dc483c0 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -25,7 +25,7 @@ class Query: """ def query( - self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None + self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None ): """ Uses the input ``selectStatement`` to query for the values that will be returned as a list of tuples. Set @@ -79,7 +79,7 @@ def query( if cur and not sansTran: db_connection.client.rollback() - def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None): + def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. @@ -124,7 +124,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona if cur and not sansTran: db_connection.client.rollback() - def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None): + def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. @@ -331,7 +331,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali if cur and not sansTran: db_connection.client.rollback() - def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None): + def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None): """ 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. @@ -507,7 +507,7 @@ def call_stored_procedure( if cur and not sansTran: db_connection.client.rollback() - def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None): + def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None, parameters: Optional[List] = None): """ Runs the `sql_statement` using `cur` as Cursor object. Use `omit_trailing_semicolon` parameter (bool) for explicit instruction, @@ -519,5 +519,7 @@ def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Option omit_trailing_semicolon = self.omit_trailing_semicolon if omit_trailing_semicolon: sql_statement = sql_statement.rstrip(";") + if parameters is None: + parameters = [] logger.debug(f"Executing sql: {sql_statement}") - return cur.execute(sql_statement) + return cur.execute(sql_statement, parameters) From a8cbb94589027c2566c4c717ef19844607af3aad Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:15:47 +0200 Subject: [PATCH 164/266] Update query.py pass parameters argument to execute function --- src/DatabaseLibrary/query.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index dc483c0..4c4bc2b 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -69,7 +69,7 @@ def query( try: cur = db_connection.client.cursor() logger.info(f"Executing : Query | {selectStatement} ") - self.__execute_sql(cur, selectStatement) + self.__execute_sql(cur, selectStatement, parameters=parameters) all_rows = cur.fetchall() if returnAsDict: col_names = [c[0] for c in cur.description] @@ -115,7 +115,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona try: cur = db_connection.client.cursor() logger.info(f"Executing : Row Count | {selectStatement}") - self.__execute_sql(cur, selectStatement) + self.__execute_sql(cur, selectStatement, parameters=parameters) data = cur.fetchall() if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: return len(data) @@ -154,7 +154,7 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio try: cur = db_connection.client.cursor() logger.info("Executing : Description | {selectStatement}") - self.__execute_sql(cur, selectStatement) + self.__execute_sql(cur, selectStatement, parameters=parameters) description = list(cur.description) if sys.version_info[0] < 3: for row in range(0, len(description)): @@ -356,7 +356,7 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Opti try: cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL String | {sqlString}") - self.__execute_sql(cur, sqlString) + self.__execute_sql(cur, sqlString, parameters=parameters) if not sansTran: db_connection.client.commit() finally: From 7d0332398412ac093a96fc1357626ed5e5db92e5 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:32:00 +0200 Subject: [PATCH 165/266] Update basic_tests.robot added test using parameters argument --- test/tests/common_tests/basic_tests.robot | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 2fe17bc..2f28772 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -16,6 +16,10 @@ SQL Statement Ending With Semicolon Works SQL Statement Ending Without Semicolon Works Query SELECT * FROM person; +SQL Statement With Parameters Works + ${output}= Query SELECT * FROM person WHERE id < ? parameters=[1] + Length Should Be ${output} 1 + Create Person Table [Setup] Log No setup for this test ${output}= Create Person Table From 7095c0878a53394558b5f84986c8207da1519f6f Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:55:05 +0200 Subject: [PATCH 166/266] Update basic_tests.robot use correct list parameter --- test/tests/common_tests/basic_tests.robot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 2f28772..386c5ae 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -17,7 +17,8 @@ SQL Statement Ending Without Semicolon Works Query SELECT * FROM person; SQL Statement With Parameters Works - ${output}= Query SELECT * FROM person WHERE id < ? parameters=[1] + @{params}= Create List 2 + ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} Length Should Be ${output} 1 Create Person Table From b88358244854f8105b09cd7c733a850203a62715 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:42:47 +0200 Subject: [PATCH 167/266] Update basic_tests.robot try different syntax for postgres, oracle etc. --- test/tests/common_tests/basic_tests.robot | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 386c5ae..8f1f28e 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -18,7 +18,11 @@ SQL Statement Ending Without Semicolon Works SQL Statement With Parameters Works @{params}= Create List 2 - ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} + TRY + ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} + EXCEPT + ${output}= Query SELECT * FROM person WHERE id < :id parameters=${params} + END Length Should Be ${output} 1 Create Person Table From 1174c7e2988e2da830b5e32b33275ddd862b57d2 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:09:11 +0200 Subject: [PATCH 168/266] Update basic_tests.robot extra syntax alternative for postgres, pymsql --- test/tests/common_tests/basic_tests.robot | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 8f1f28e..4db398c 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -18,11 +18,15 @@ SQL Statement Ending Without Semicolon Works SQL Statement With Parameters Works @{params}= Create List 2 - TRY - ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} - EXCEPT + + IF "${DB_MODULE}" in ["oracledb"] ${output}= Query SELECT * FROM person WHERE id < :id parameters=${params} + ELSE IF "${DB_MODULE}" in ["sqlite3", "pyodbc"] + ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} + ELSE + ${output}= Query SELECT * FROM person WHERE id < %s parameters=${params} END + Length Should Be ${output} 1 Create Person Table From 03c5f5db3034d34af0ce96a80d8b0fba6ec03c93 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 19 Nov 2023 16:56:23 +0100 Subject: [PATCH 169/266] Update documentation with parameters examples --- src/DatabaseLibrary/query.py | 52 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 4c4bc2b..01a4842 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -28,8 +28,7 @@ def query( self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. + Uses the input ``selectStatement`` to query for the values that will be returned as a list of tuples. Set optional input ``returnAsDict`` to True to return values as a list of dictionaries. Use optional ``alias`` parameter to specify what connection should be used for the query if you have more @@ -61,7 +60,12 @@ def query( And get the following See, Franz Allan - Using optional ``sansTran`` to run command without an explicit transaction commit or rollback: + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person | + | Query | SELECT * FROM %s | parameters=${parameters} | + + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: | @{queryResults} | Query | SELECT * FROM person | True | """ db_connection = self.connection_store.get_connection(alias) @@ -81,8 +85,7 @@ def query( def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. + Uses the input ``selectStatement`` to query the database and returns the number of rows from the query. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -107,7 +110,12 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona 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: + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person | + | ${rowCount} | Row Count | SELECT * FROM %s | parameters=${parameters} | + + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: | ${rowCount} | Row Count | SELECT * FROM person | True | """ db_connection = self.connection_store.get_connection(alias) @@ -126,8 +134,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. + Uses the input ``selectStatement`` to query a table in the db which will be used to determine the description. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -146,6 +153,11 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person | + | ${desc} | Description | SELECT * FROM %s | parameters=${parameters} | + Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{queryResults} | Description | SELECT * FROM person | True | """ @@ -166,8 +178,7 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, alias: Optional[str] = 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. + Delete all the rows within a given table. For example, given we have a table `person` in a database @@ -184,7 +195,7 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali 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: + Use optional `sansTran` to run command without an explicit transaction commit or rollback: | Delete All Rows From Table | person | True | """ db_connection = self.connection_store.get_connection(alias) @@ -207,8 +218,7 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, alias: Optional[str] = None): """ Executes the content of the `sqlScriptFileName` as SQL commands. Useful for setting the database to a known - state before running your tests, or clearing out your test data after running each a test. Set optional input - `sansTran` to True to run command without an explicit transaction commit or rollback. + state before running your tests, or clearing out your test data after running each a test. Sample usage : | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | @@ -262,7 +272,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali 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: + Use optional `sansTran` to run command without an explicit transaction commit or rollback: | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | """ db_connection = self.connection_store.get_connection(alias) @@ -333,8 +343,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None): """ - 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. + Executes the sqlString as SQL commands. Useful to pass arguments to your sql. SQL commands are expected to be delimited by a semicolon (';'). @@ -348,7 +357,12 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Opti 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: + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person_employee_table | + | Execute Sql String | SELECT * FROM %s | parameters=${parameters} | + + Use 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 | """ db_connection = self.connection_store.get_connection(alias) @@ -381,8 +395,6 @@ def call_stored_procedure( It also depends on the database, how the procedure returns the values - as params or as result sets. E.g. calling a procedure in *PostgreSQL* returns even a single value of an OUT param as a result set. - Set optional input `sansTran` to True to run command without an explicit transaction commit or rollback. - Simple example: | @{Params} = | Create List | Jerry | out_second_name | | @{Param values} @{Result sets} = | Call Stored Procedure | Get_second_name | ${Params} | @@ -404,7 +416,7 @@ def call_stored_procedure( 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: + Use optional `sansTran` to run command without an explicit transaction commit or rollback: | @{Param values} @{Result sets} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | ${Params} | True | """ db_connection = self.connection_store.get_connection(alias) From 1a9987dbdd98eed70670e91496750f64c37e0368 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:02:22 +0200 Subject: [PATCH 170/266] Update query.py add parameters argument --- src/DatabaseLibrary/query.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 2da52fe..dc483c0 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -25,7 +25,7 @@ class Query: """ def query( - self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None + self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None ): """ Uses the input ``selectStatement`` to query for the values that will be returned as a list of tuples. Set @@ -79,7 +79,7 @@ def query( if cur and not sansTran: db_connection.client.rollback() - def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None): + def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. @@ -124,7 +124,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona if cur and not sansTran: db_connection.client.rollback() - def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None): + def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. @@ -331,7 +331,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali if cur and not sansTran: db_connection.client.rollback() - def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None): + def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None): """ 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. @@ -507,7 +507,7 @@ def call_stored_procedure( if cur and not sansTran: db_connection.client.rollback() - def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None): + def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None, parameters: Optional[List] = None): """ Runs the `sql_statement` using `cur` as Cursor object. Use `omit_trailing_semicolon` parameter (bool) for explicit instruction, @@ -519,5 +519,7 @@ def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Option omit_trailing_semicolon = self.omit_trailing_semicolon if omit_trailing_semicolon: sql_statement = sql_statement.rstrip(";") + if parameters is None: + parameters = [] logger.debug(f"Executing sql: {sql_statement}") - return cur.execute(sql_statement) + return cur.execute(sql_statement, parameters) From 402664c42acf1bf41555ff031299ae764bd71798 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:15:47 +0200 Subject: [PATCH 171/266] Update query.py pass parameters argument to execute function --- src/DatabaseLibrary/query.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index dc483c0..4c4bc2b 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -69,7 +69,7 @@ def query( try: cur = db_connection.client.cursor() logger.info(f"Executing : Query | {selectStatement} ") - self.__execute_sql(cur, selectStatement) + self.__execute_sql(cur, selectStatement, parameters=parameters) all_rows = cur.fetchall() if returnAsDict: col_names = [c[0] for c in cur.description] @@ -115,7 +115,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona try: cur = db_connection.client.cursor() logger.info(f"Executing : Row Count | {selectStatement}") - self.__execute_sql(cur, selectStatement) + self.__execute_sql(cur, selectStatement, parameters=parameters) data = cur.fetchall() if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: return len(data) @@ -154,7 +154,7 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio try: cur = db_connection.client.cursor() logger.info("Executing : Description | {selectStatement}") - self.__execute_sql(cur, selectStatement) + self.__execute_sql(cur, selectStatement, parameters=parameters) description = list(cur.description) if sys.version_info[0] < 3: for row in range(0, len(description)): @@ -356,7 +356,7 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Opti try: cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL String | {sqlString}") - self.__execute_sql(cur, sqlString) + self.__execute_sql(cur, sqlString, parameters=parameters) if not sansTran: db_connection.client.commit() finally: From 3f94ad43d8758181b3323f501baa2229372e599b Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:32:00 +0200 Subject: [PATCH 172/266] Update basic_tests.robot added test using parameters argument --- test/tests/common_tests/basic_tests.robot | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 2fe17bc..2f28772 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -16,6 +16,10 @@ SQL Statement Ending With Semicolon Works SQL Statement Ending Without Semicolon Works Query SELECT * FROM person; +SQL Statement With Parameters Works + ${output}= Query SELECT * FROM person WHERE id < ? parameters=[1] + Length Should Be ${output} 1 + Create Person Table [Setup] Log No setup for this test ${output}= Create Person Table From 16a281e51ffea0106ceec745deaaa8523f406390 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Fri, 11 Aug 2023 18:55:05 +0200 Subject: [PATCH 173/266] Update basic_tests.robot use correct list parameter --- test/tests/common_tests/basic_tests.robot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 2f28772..386c5ae 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -17,7 +17,8 @@ SQL Statement Ending Without Semicolon Works Query SELECT * FROM person; SQL Statement With Parameters Works - ${output}= Query SELECT * FROM person WHERE id < ? parameters=[1] + @{params}= Create List 2 + ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} Length Should Be ${output} 1 Create Person Table From 5f2c0226fcb4e45fd4928d0a2f0cee1b62fc9043 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Mon, 14 Aug 2023 13:42:47 +0200 Subject: [PATCH 174/266] Update basic_tests.robot try different syntax for postgres, oracle etc. --- test/tests/common_tests/basic_tests.robot | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 386c5ae..8f1f28e 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -18,7 +18,11 @@ SQL Statement Ending Without Semicolon Works SQL Statement With Parameters Works @{params}= Create List 2 - ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} + TRY + ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} + EXCEPT + ${output}= Query SELECT * FROM person WHERE id < :id parameters=${params} + END Length Should Be ${output} 1 Create Person Table From 6ce795c32c215526ec3590e9b6376ca86921dc94 Mon Sep 17 00:00:00 2001 From: carnegiemedal <126577777+carnegiemedal@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:09:11 +0200 Subject: [PATCH 175/266] Update basic_tests.robot extra syntax alternative for postgres, pymsql --- test/tests/common_tests/basic_tests.robot | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 8f1f28e..4db398c 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -18,11 +18,15 @@ SQL Statement Ending Without Semicolon Works SQL Statement With Parameters Works @{params}= Create List 2 - TRY - ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} - EXCEPT + + IF "${DB_MODULE}" in ["oracledb"] ${output}= Query SELECT * FROM person WHERE id < :id parameters=${params} + ELSE IF "${DB_MODULE}" in ["sqlite3", "pyodbc"] + ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} + ELSE + ${output}= Query SELECT * FROM person WHERE id < %s parameters=${params} END + Length Should Be ${output} 1 Create Person Table From 8902334e6b7748ba085e13f74b21f2d87ecd3cf4 Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Sun, 19 Nov 2023 16:56:23 +0100 Subject: [PATCH 176/266] Update documentation with parameters examples --- src/DatabaseLibrary/query.py | 52 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 4c4bc2b..01a4842 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -28,8 +28,7 @@ def query( self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. + Uses the input ``selectStatement`` to query for the values that will be returned as a list of tuples. Set optional input ``returnAsDict`` to True to return values as a list of dictionaries. Use optional ``alias`` parameter to specify what connection should be used for the query if you have more @@ -61,7 +60,12 @@ def query( And get the following See, Franz Allan - Using optional ``sansTran`` to run command without an explicit transaction commit or rollback: + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person | + | Query | SELECT * FROM %s | parameters=${parameters} | + + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: | @{queryResults} | Query | SELECT * FROM person | True | """ db_connection = self.connection_store.get_connection(alias) @@ -81,8 +85,7 @@ def query( def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. + Uses the input ``selectStatement`` to query the database and returns the number of rows from the query. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -107,7 +110,12 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona 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: + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person | + | ${rowCount} | Row Count | SELECT * FROM %s | parameters=${parameters} | + + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: | ${rowCount} | Row Count | SELECT * FROM person | True | """ db_connection = self.connection_store.get_connection(alias) @@ -126,8 +134,7 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = 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. + Uses the input ``selectStatement`` to query a table in the db which will be used to determine the description. For example, given we have a table `person` with the following data: | id | first_name | last_name | @@ -146,6 +153,11 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person | + | ${desc} | Description | SELECT * FROM %s | parameters=${parameters} | + Using optional `sansTran` to run command without an explicit transaction commit or rollback: | @{queryResults} | Description | SELECT * FROM person | True | """ @@ -166,8 +178,7 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, alias: Optional[str] = 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. + Delete all the rows within a given table. For example, given we have a table `person` in a database @@ -184,7 +195,7 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali 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: + Use optional `sansTran` to run command without an explicit transaction commit or rollback: | Delete All Rows From Table | person | True | """ db_connection = self.connection_store.get_connection(alias) @@ -207,8 +218,7 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, alias: Optional[str] = None): """ Executes the content of the `sqlScriptFileName` as SQL commands. Useful for setting the database to a known - state before running your tests, or clearing out your test data after running each a test. Set optional input - `sansTran` to True to run command without an explicit transaction commit or rollback. + state before running your tests, or clearing out your test data after running each a test. Sample usage : | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | @@ -262,7 +272,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali 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: + Use optional `sansTran` to run command without an explicit transaction commit or rollback: | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | True | """ db_connection = self.connection_store.get_connection(alias) @@ -333,8 +343,7 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None): """ - 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. + Executes the sqlString as SQL commands. Useful to pass arguments to your sql. SQL commands are expected to be delimited by a semicolon (';'). @@ -348,7 +357,12 @@ def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Opti 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: + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | parameters | Create List | person_employee_table | + | Execute Sql String | SELECT * FROM %s | parameters=${parameters} | + + Use 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 | """ db_connection = self.connection_store.get_connection(alias) @@ -381,8 +395,6 @@ def call_stored_procedure( It also depends on the database, how the procedure returns the values - as params or as result sets. E.g. calling a procedure in *PostgreSQL* returns even a single value of an OUT param as a result set. - Set optional input `sansTran` to True to run command without an explicit transaction commit or rollback. - Simple example: | @{Params} = | Create List | Jerry | out_second_name | | @{Param values} @{Result sets} = | Call Stored Procedure | Get_second_name | ${Params} | @@ -404,7 +416,7 @@ def call_stored_procedure( 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: + Use optional `sansTran` to run command without an explicit transaction commit or rollback: | @{Param values} @{Result sets} = | Call Stored Procedure | DBName.SchemaName.StoredProcName | ${Params} | True | """ db_connection = self.connection_store.get_connection(alias) From 2981b161d2b9494df0b4ad8e48190d6f868fc9ee Mon Sep 17 00:00:00 2001 From: Bartlomiej Hirsz Date: Mon, 20 Nov 2023 17:59:12 +0100 Subject: [PATCH 177/266] Add missing parameters arg to assertions keywords --- src/DatabaseLibrary/assertion.py | 40 +++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 64a8723..7e0862f 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -11,7 +11,7 @@ # 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 Optional +from typing import List, Optional from robot.api import logger @@ -22,7 +22,12 @@ class Assertion: """ def check_if_exists_in_database( - self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None + self, + selectStatement: str, + sansTran: bool = False, + msg: Optional[str] = None, + alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ Check if any row would be returned by given the input ``selectStatement``. If there are no results, then this will @@ -43,13 +48,18 @@ def check_if_exists_in_database( | Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | sansTran=True | """ logger.info(f"Executing : Check If Exists In Database | {selectStatement}") - if not self.query(selectStatement, sansTran, alias=alias): + if not self.query(selectStatement, sansTran, alias=alias, parameters=parameters): raise AssertionError( msg or f"Expected to have have at least one row, but got 0 rows from: '{selectStatement}'" ) def check_if_not_exists_in_database( - self, selectStatement: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None + self, + selectStatement: str, + sansTran: bool = False, + msg: Optional[str] = None, + alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ This is the negation of `check_if_exists_in_database`. @@ -71,14 +81,19 @@ def check_if_not_exists_in_database( | Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' | sansTran=True | """ logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") - query_results = self.query(selectStatement, sansTran, alias=alias) + 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 + self, + selectStatement: str, + sansTran: bool = False, + msg: Optional[str] = None, + alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ Check if any rows are returned from the submitted ``selectStatement``. If there are, then this will throw an @@ -99,7 +114,7 @@ def row_count_is_0( | Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | sansTran=True | """ logger.info(f"Executing : Row Count Is 0 | {selectStatement}") - num_rows = self.row_count(selectStatement, sansTran, alias=alias) + num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters) if num_rows > 0: raise AssertionError(msg or f"Expected 0 rows, but {num_rows} were returned from: '{selectStatement}'") @@ -110,6 +125,7 @@ def row_count_is_equal_to_x( sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ Check if the number of rows returned from ``selectStatement`` is equal to the value submitted. If not, then this @@ -129,7 +145,7 @@ def row_count_is_equal_to_x( | Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | sansTran=True | """ logger.info(f"Executing : Row Count Is Equal To X | {selectStatement} | {numRows}") - num_rows = self.row_count(selectStatement, sansTran, alias=alias) + 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}'" @@ -142,6 +158,7 @@ def row_count_is_greater_than_x( sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ Check if the number of rows returned from ``selectStatement`` is greater than the value submitted. If not, then @@ -161,7 +178,7 @@ def row_count_is_greater_than_x( | Row Count Is Greater Than X | SELECT id FROM person | 1 | sansTran=True | """ logger.info(f"Executing : Row Count Is Greater Than X | {selectStatement} | {numRows}") - num_rows = self.row_count(selectStatement, sansTran, alias=alias) + 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}'" @@ -174,6 +191,7 @@ def row_count_is_less_than_x( sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ Check if the number of rows returned from ``selectStatement`` is less than the value submitted. If not, then this @@ -194,7 +212,7 @@ def row_count_is_less_than_x( """ logger.info(f"Executing : Row Count Is Less Than X | {selectStatement} | {numRows}") - num_rows = self.row_count(selectStatement, sansTran, alias=alias) + 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}'" @@ -204,7 +222,7 @@ def table_must_exist( self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None ): """ - Check if the table given exists in the database. + Check if the given table exists in the database. Set optional input ``sansTran`` to True to run command without an explicit transaction commit or rollback. From 79560d7cd147ad562a518df8a58413c92ec26ddc Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 20 Nov 2023 18:01:29 +0100 Subject: [PATCH 178/266] Extend tests for queries with params and move them into a separe test suite --- test/tests/common_tests/basic_tests.robot | 13 ----- test/tests/common_tests/query_params.robot | 67 ++++++++++++++++++++++ 2 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 test/tests/common_tests/query_params.robot diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 4db398c..2fe17bc 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -16,19 +16,6 @@ SQL Statement Ending With Semicolon Works SQL Statement Ending Without Semicolon Works Query SELECT * FROM person; -SQL Statement With Parameters Works - @{params}= Create List 2 - - IF "${DB_MODULE}" in ["oracledb"] - ${output}= Query SELECT * FROM person WHERE id < :id parameters=${params} - ELSE IF "${DB_MODULE}" in ["sqlite3", "pyodbc"] - ${output}= Query SELECT * FROM person WHERE id < ? parameters=${params} - ELSE - ${output}= Query SELECT * FROM person WHERE id < %s parameters=${params} - END - - Length Should Be ${output} 1 - Create Person Table [Setup] Log No setup for this test ${output}= Create Person Table diff --git a/test/tests/common_tests/query_params.robot b/test/tests/common_tests/query_params.robot new file mode 100644 index 0000000..839059b --- /dev/null +++ b/test/tests/common_tests/query_params.robot @@ -0,0 +1,67 @@ +*** Settings *** +Documentation Keywords with query params as seperate 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 *** +@{PARAMS} Franz Allan + + +*** Keywords *** +Connect To DB And Build Query + Connect To DB + Build Query String With Params + +Build Query String With Params + ${sql}= Set Variable SELECT id FROM person WHERE FIRST_NAME= + IF "${DB_MODULE}" in ["oracledb", "cx_Oracle"] + ${sql}= Catenate ${sql} :id + ELSE IF "${DB_MODULE}" in ["sqlite3", "pyodbc"] + ${sql}= Catenate ${sql} ? + ELSE + ${sql}= Catenate ${sql} %s + END + Set Suite Variable ${QUERY} ${sql} + + +*** Test Cases *** +Query + ${out}= Query ${QUERY} parameters=${PARAMS} + Length Should Be ${out} 1 + +Row Count + ${out}= Row Count ${QUERY} parameters=${PARAMS} + Should Be Equal As Strings ${out} 1 + +Description + ${out}= Description ${QUERY} parameters=${PARAMS} + Length Should Be ${out} 1 + +Execute SQL String + Execute Sql String ${QUERY} parameters=${PARAMS} + +Check If Exists In DB + Check If Exists In Database ${QUERY} parameters=${PARAMS} + +Check If Not Exists In DB + @{Wrong params}= Create List Joe + Check If Not Exists In Database ${QUERY} parameters=${Wrong params} + +Row Count is 0 + @{Wrong params}= Create List Joe + Row Count is 0 ${QUERY} parameters=${Wrong params} + +Row Count is Equal to X + Row Count is Equal to X ${QUERY} 1 parameters=${PARAMS} + +Row Count is Less Than X + Row Count is Less Than X ${QUERY} 5 parameters=${PARAMS} + +Row Count is Greater Than X + Row Count is Greater Than X ${QUERY} 0 parameters=${PARAMS} From 8a35c1c585b1e60ca99230fbfd194922f55f6d8e Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 20 Nov 2023 18:58:15 +0100 Subject: [PATCH 179/266] Bump version to 1.4.1 --- src/DatabaseLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 5d4d63c..bababb5 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "1.4" +VERSION = "1.4.1" From a212ff0fd08c70abda9a15fdd19a46b55097124a Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 20 Nov 2023 19:07:11 +0100 Subject: [PATCH 180/266] Forgot new query params in some keyword docs --- src/DatabaseLibrary/assertion.py | 31 +++++++- src/DatabaseLibrary/query.py | 119 +++++++++++++++---------------- 2 files changed, 86 insertions(+), 64 deletions(-) diff --git a/src/DatabaseLibrary/assertion.py b/src/DatabaseLibrary/assertion.py index 7e0862f..c3ecb87 100644 --- a/src/DatabaseLibrary/assertion.py +++ b/src/DatabaseLibrary/assertion.py @@ -41,11 +41,16 @@ def check_if_exists_in_database( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). + 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' | sansTran=True | + | @{parameters} | Create List | John | + | Check If Exists In Database | SELECT id FROM person WHERE first_name = %s | parameters=${parameters} | """ logger.info(f"Executing : Check If Exists In Database | {selectStatement}") if not self.query(selectStatement, sansTran, alias=alias, parameters=parameters): @@ -74,11 +79,16 @@ def check_if_not_exists_in_database( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). + 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} | """ logger.info(f"Executing : Check If Not Exists In Database | {selectStatement}") query_results = self.query(selectStatement, sansTran, alias=alias, parameters=parameters) @@ -107,11 +117,16 @@ def row_count_is_0( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). + 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(f"Executing : Row Count Is 0 | {selectStatement}") num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters) @@ -138,11 +153,16 @@ def row_count_is_equal_to_x( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``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} | """ logger.info(f"Executing : Row Count Is Equal To X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters) @@ -171,11 +191,16 @@ def row_count_is_greater_than_x( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client). + 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} | """ logger.info(f"Executing : Row Count Is Greater Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters) @@ -204,12 +229,16 @@ def row_count_is_less_than_x( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``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} | """ logger.info(f"Executing : Row Count Is Less Than X | {selectStatement} | {numRows}") num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 01a4842..2be123f 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -25,7 +25,12 @@ class Query: """ def query( - self, selectStatement: str, sansTran: bool = False, returnAsDict: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None + self, + selectStatement: str, + sansTran: bool = False, + returnAsDict: bool = False, + alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ Uses the input ``selectStatement`` to query for the values that will be returned as a list of tuples. @@ -34,6 +39,14 @@ def query( Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different + depending on the database client): + | @{parameters} | Create List | person | + | Query | SELECT * FROM %s | parameters=${parameters} | + + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: + | @{queryResults} | Query | SELECT * FROM person | True | + 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 @@ -59,14 +72,6 @@ def query( And get the following See, Franz Allan - - Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different - depending on the database client): - | parameters | Create List | person | - | Query | SELECT * FROM %s | parameters=${parameters} | - - Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: - | @{queryResults} | Query | SELECT * FROM person | True | """ db_connection = self.connection_store.get_connection(alias) cur = None @@ -83,40 +88,30 @@ def query( if cur and not sansTran: db_connection.client.rollback() - def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None): + def row_count( + self, + selectStatement: str, + sansTran: bool = False, + alias: Optional[str] = None, + parameters: Optional[List] = None, + ): """ Uses the input ``selectStatement`` to query the database and returns the number of rows from the query. - For example, given we have a table `person` with the following data: - | id | first_name | last_name | - | 1 | Franz Allan | See | - | 2 | Jerry | Schneider | - - When you do the following: - | ${rowCount} | Row Count | SELECT * FROM person | - | ${rowCount} | Row Count | SELECT * FROM person | alias=my_alias | - | Log | ${rowCount} | - - You will get the following: - 2 - - Also, you can do something like this: - | ${rowCount} | Row Count | SELECT * FROM person WHERE id = 2 | - | Log | ${rowCount} | - - And get the following - 1 - Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: + Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client): - | parameters | Create List | person | - | ${rowCount} | Row Count | SELECT * FROM %s | parameters=${parameters} | - Use optional ``sansTran`` to run command without an explicit transaction commit or rollback: - | ${rowCount} | Row Count | SELECT * FROM person | True | + Examples: + | ${rowCount} | Row Count | SELECT * FROM person | + | ${rowCount} | Row Count | SELECT * FROM person | sansTran=True | + | ${rowCount} | Row Count | SELECT * FROM person | alias=my_alias | + | @{parameters} | Create List | person | + | ${rowCount} | Row Count | SELECT * FROM %s | parameters=${parameters} | """ db_connection = self.connection_store.get_connection(alias) cur = None @@ -132,7 +127,13 @@ def row_count(self, selectStatement: str, sansTran: bool = False, alias: Optiona if cur and not sansTran: db_connection.client.rollback() - def description(self, selectStatement: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None): + def description( + self, + selectStatement: str, + sansTran: bool = False, + alias: Optional[str] = None, + parameters: Optional[List] = None, + ): """ Uses the input ``selectStatement`` to query a table in the db which will be used to determine the description. @@ -155,7 +156,7 @@ def description(self, selectStatement: str, sansTran: bool = False, alias: Optio Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client): - | parameters | Create List | person | + | @{parameters} | Create List | person | | ${desc} | Description | SELECT * FROM %s | parameters=${parameters} | Using optional `sansTran` to run command without an explicit transaction commit or rollback: @@ -180,23 +181,15 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali """ Delete all the rows within a given table. - For example, given we have a table `person` in a database - - When you do the following: - | Delete All Rows From Table | person | - | Delete All Rows From Table | person | alias=my_alias | - - 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 | + Use optional `sansTran` to run command without an explicit transaction commit or rollback. Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. - Use 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 | alias=my_alias | + | Delete All Rows From Table | person | sansTran=True | """ db_connection = self.connection_store.get_connection(alias) cur = None @@ -341,29 +334,27 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali if cur and not sansTran: db_connection.client.rollback() - def execute_sql_string(self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None): + def execute_sql_string( + self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None + ): """ Executes the sqlString as SQL commands. Useful to pass arguments to your sql. - SQL commands are expected to be delimited by a semicolon (';'). - For example: - | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | - | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | alias=my_alias | - - For example with an argument: - | Execute Sql String | SELECT * FROM person WHERE first_name = ${FIRSTNAME} | + Use optional `sansTran` to run command without an explicit transaction commit or rollback: Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different - depending on the database client): - | parameters | Create List | person_employee_table | - | Execute Sql String | SELECT * FROM %s | parameters=${parameters} | + depending on the database client). - Use 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 | + For example: + | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | + | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | alias=my_alias | + | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | sansTran=True | + | @{parameters} | Create List | person_employee_table | + | Execute Sql String | SELECT * FROM %s | parameters=${parameters} | """ db_connection = self.connection_store.get_connection(alias) cur = None @@ -519,7 +510,9 @@ def call_stored_procedure( if cur and not sansTran: db_connection.client.rollback() - def __execute_sql(self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None, parameters: Optional[List] = None): + def __execute_sql( + self, cur, sql_statement: str, omit_trailing_semicolon: Optional[bool] = None, parameters: Optional[List] = None + ): """ Runs the `sql_statement` using `cur` as Cursor object. Use `omit_trailing_semicolon` parameter (bool) for explicit instruction, From 5ba748578cd46c56215df4bc7d59d6b195a3112b Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 20 Nov 2023 19:10:47 +0100 Subject: [PATCH 181/266] Update main docs in the lib source code --- src/DatabaseLibrary/__init__.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index cd70e0b..1bf24f0 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -37,7 +37,7 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): Don't forget to install the required Python database module! == Usage example == - + === Basic usage === | *** Settings *** | Library DatabaseLibrary | Test Setup Connect To My Oracle DB @@ -65,6 +65,30 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): | Check If Not Exists In Database ${sql} | + === Handling multiple database connections === + | *** 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 db_user pass 127.0.0.1 5432 + | ... alias=postgres + | Connect To Database pymysql db db_user pass 127.0.0.1 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 + | == Database modules compatibility == The library is basically compatible with any [https://peps.python.org/pep-0249|Python Database API Specification 2.0] module. From 7b0ddde10fe2d58e3bef16a285d872658a0e628e Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 20 Nov 2023 19:11:07 +0100 Subject: [PATCH 182/266] Update keyword docs --- doc/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.html b/doc/index.html index 6c6c8a6..baf9a8e 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1180,7 +1180,7 @@ jQuery.extend({highlight:function(e,t,n,r){if(e.nodeType===3){var i=e.data.match(t);if(i){var s=document.createElement(n||"span");s.className=r||"highlight";var o=e.splitText(i.index);o.splitText(i[0].length);var u=o.cloneNode(true);s.appendChild(u);o.parentNode.replaceChild(s,o);return 1}}else if(e.nodeType===1&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&!(e.tagName===n.toUpperCase()&&e.className===r)){for(var a=0;a From 336e1ca741a1ba677c4e68f57eb564d604feb714 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 8 Dec 2023 10:08:43 +0100 Subject: [PATCH 183/266] Fix #202 - no empty list if no sql params --- src/DatabaseLibrary/query.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 2be123f..20cb91d 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -525,6 +525,8 @@ def __execute_sql( if omit_trailing_semicolon: sql_statement = sql_statement.rstrip(";") if parameters is None: - parameters = [] - logger.debug(f"Executing sql: {sql_statement}") - return cur.execute(sql_statement, parameters) + logger.debug(f"Executing sql '{sql_statement}' without parameters") + return cur.execute(sql_statement) + else: + logger.debug(f"Executing sql '{sql_statement}' with parameters: {parameters}") + return cur.execute(sql_statement, parameters) From 718891fbacc4ac510606d01f9a6eb5a7b59e14ee Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 09:08:00 +0100 Subject: [PATCH 184/266] Tests for PL/SQL block with IF condition inside - Oracle and MSSQL --- test/resources/create_stored_procedure_mssql.sql | 14 ++++++++++++++ test/resources/create_stored_procedures_oracle.sql | 13 ++++++++++++- test/tests/common_tests/stored_procedures.robot | 3 +++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/test/resources/create_stored_procedure_mssql.sql b/test/resources/create_stored_procedure_mssql.sql index f7da94f..c392de7 100644 --- a/test/resources/create_stored_procedure_mssql.sql +++ b/test/resources/create_stored_procedure_mssql.sql @@ -33,4 +33,18 @@ BEGIN SELECT FIRST_NAME FROM person; SELECT LAST_NAME FROM person; RETURN; +END; + +DROP PROCEDURE IF EXISTS check_condition; +CREATE PROCEDURE check_condition +AS +DECLARE @v_condition BIT = 1; +IF @v_condition = 1 +BEGIN +PRINT 'Condition is true'; +END +ELSE +BEGIN +PRINT 'Condition is false'; +END; END; \ No newline at end of file diff --git a/test/resources/create_stored_procedures_oracle.sql b/test/resources/create_stored_procedures_oracle.sql index f56cd3a..5e155da 100644 --- a/test/resources/create_stored_procedures_oracle.sql +++ b/test/resources/create_stored_procedures_oracle.sql @@ -29,4 +29,15 @@ OPEN first_names_cursor for SELECT FIRST_NAME FROM person; OPEN second_names_cursor for SELECT LAST_NAME FROM person; -END; \ No newline at end of file +END; + +CREATE OR REPLACE PROCEDURE +check_condition AS +v_condition BOOLEAN := TRUE; +BEGIN +IF v_condition THEN +DBMS_OUTPUT.PUT_LINE('Condition is true'); +ELSE +DBMS_OUTPUT.PUT_LINE('Condition is false'); +END IF; +END check_condition; \ No newline at end of file diff --git a/test/tests/common_tests/stored_procedures.robot b/test/tests/common_tests/stored_procedures.robot index 32aa6e2..0e52fef 100644 --- a/test/tests/common_tests/stored_procedures.robot +++ b/test/tests/common_tests/stored_procedures.robot @@ -88,6 +88,9 @@ Procedure Returns Multiple Result Sets Should Be Equal ${second result set}[0][0] See Should Be Equal ${second result set}[1][0] Schneider +Procedure With IF/ELSE Block + Call Stored Procedure check_condition + *** Keywords *** Create And Fill Tables And Stored Procedures From a762ab4446092f48f4817f506ed8a7691d98d817 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 09:10:01 +0100 Subject: [PATCH 185/266] Fix docker run strings --- test/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/readme.md b/test/readme.md index c9f9fde..41e7175 100644 --- a/test/readme.md +++ b/test/readme.md @@ -40,7 +40,7 @@ See the folder `.github/workflows` ## Microsoft SQL Server - https://hub.docker.com/_/microsoft-mssql-server -- docker run --rm --name mssql -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=MyPass1234!" -p 1433:1433 -d mcr.microsoft.com/mssql/server +- docker run --rm --name mssql -e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD='MyPass1234!' -p 1433:1433 -d mcr.microsoft.com/mssql/server --> login and create DB: - docker exec -it mssql bash - /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'MyPass1234!' From dc5a2e143d5280305b8d026f9e7f0576784d9d62 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 09:12:11 +0100 Subject: [PATCH 186/266] Improve statemnt blocks recognition using regexp --- src/DatabaseLibrary/query.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 2be123f..a3bcf0a 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -13,6 +13,7 @@ # limitations under the License. import inspect +import re import sys from typing import List, Optional @@ -277,12 +278,17 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali logger.info(f"Executing : Execute SQL Script | {sqlScriptFileName}") current_statement = "" inside_statements_group = False - + proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?") + proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?") for line in sql_file: line = line.strip() if line.startswith("#") or line.startswith("--") or line == "/": continue - if line.lower().startswith("begin"): + + # check if the line matches the creating procedure regexp pattern + elif proc_start_pattern.match(line.lower()): + inside_statements_group = True + elif line.lower().startswith("begin"): inside_statements_group = True # semicolons inside the line? use them to separate statements @@ -297,12 +303,16 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali for sqlFragment in sqlFragments: if len(sqlFragment.strip()) == 0: continue + if inside_statements_group: # if statements inside a begin/end block have semicolns, # they must persist - even with oracle sqlFragment += "; " - if sqlFragment.lower() == "end; ": + + if proc_end_pattern.match(sqlFragment.lower()): inside_statements_group = False + elif proc_start_pattern.match(sqlFragment.lower()): + inside_statements_group = True elif sqlFragment.lower().startswith("begin"): inside_statements_group = True @@ -326,7 +336,8 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali for statement in statements_to_execute: logger.info(f"Executing statement from script file: {statement}") - omit_semicolon = not statement.lower().endswith("end;") + line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") + omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) self.__execute_sql(cur, statement, omit_semicolon) if not sansTran: db_connection.client.commit() From 0999fef1fbd6c96e161413948b72670151b4f252 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 17:10:05 +0100 Subject: [PATCH 187/266] Add scripts for create procedure tests for other databases --- .../create_stored_procedure_mssql.sql | 2 +- .../create_stored_procedure_mysql.sql | 13 ++++++++++++- .../create_stored_procedure_postgres.sql | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/test/resources/create_stored_procedure_mssql.sql b/test/resources/create_stored_procedure_mssql.sql index c392de7..9baf396 100644 --- a/test/resources/create_stored_procedure_mssql.sql +++ b/test/resources/create_stored_procedure_mssql.sql @@ -46,5 +46,5 @@ END ELSE BEGIN PRINT 'Condition is false'; -END; +END END; \ No newline at end of file diff --git a/test/resources/create_stored_procedure_mysql.sql b/test/resources/create_stored_procedure_mysql.sql index 2f69130..5da8b26 100644 --- a/test/resources/create_stored_procedure_mysql.sql +++ b/test/resources/create_stored_procedure_mysql.sql @@ -27,4 +27,15 @@ CREATE PROCEDURE get_all_first_and_second_names() BEGIN SELECT FIRST_NAME FROM person; SELECT LAST_NAME FROM person; -END; \ No newline at end of file +END; + +DROP PROCEDURE IF EXISTS check_condition; +CREATE PROCEDURE check_condition() +BEGIN + DECLARE v_condition BOOLEAN DEFAULT TRUE; + IF v_condition THEN + SELECT 'Condition is true' AS Result; + ELSE + SELECT 'Condition is false' AS Result; + END IF; +END \ No newline at end of file diff --git a/test/resources/create_stored_procedure_postgres.sql b/test/resources/create_stored_procedure_postgres.sql index d35e9d7..158f547 100644 --- a/test/resources/create_stored_procedure_postgres.sql +++ b/test/resources/create_stored_procedure_postgres.sql @@ -49,4 +49,23 @@ RETURN NEXT result1; OPEN result2 FOR SELECT LAST_NAME FROM person; RETURN NEXT result2; END +'; + +DROP ROUTINE IF EXISTS check_condition; +CREATE FUNCTION +check_condition() +RETURNS VOID +LANGUAGE plpgsql +AS +' +DECLARE + v_condition BOOLEAN := TRUE; + v_res BOOLEAN := TRUE; +BEGIN + IF v_condition THEN + v_res := TRUE; + ELSE + v_res := FALSE; + END IF; +END '; \ No newline at end of file From 352962d916023a6f26294c93e35aca12d0ba61d6 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 18 Dec 2023 17:25:14 +0100 Subject: [PATCH 188/266] Consistent file names --- ...ocedure_mssql.sql => create_stored_procedures_mssql.sql} | 0 ...ocedure_mysql.sql => create_stored_procedures_mysql.sql} | 0 ...e_postgres.sql => create_stored_procedures_postgres.sql} | 0 test/tests/common_tests/stored_procedures.robot | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename test/resources/{create_stored_procedure_mssql.sql => create_stored_procedures_mssql.sql} (100%) rename test/resources/{create_stored_procedure_mysql.sql => create_stored_procedures_mysql.sql} (100%) rename test/resources/{create_stored_procedure_postgres.sql => create_stored_procedures_postgres.sql} (100%) diff --git a/test/resources/create_stored_procedure_mssql.sql b/test/resources/create_stored_procedures_mssql.sql similarity index 100% rename from test/resources/create_stored_procedure_mssql.sql rename to test/resources/create_stored_procedures_mssql.sql diff --git a/test/resources/create_stored_procedure_mysql.sql b/test/resources/create_stored_procedures_mysql.sql similarity index 100% rename from test/resources/create_stored_procedure_mysql.sql rename to test/resources/create_stored_procedures_mysql.sql diff --git a/test/resources/create_stored_procedure_postgres.sql b/test/resources/create_stored_procedures_postgres.sql similarity index 100% rename from test/resources/create_stored_procedure_postgres.sql rename to test/resources/create_stored_procedures_postgres.sql diff --git a/test/tests/common_tests/stored_procedures.robot b/test/tests/common_tests/stored_procedures.robot index 0e52fef..e0cad5b 100644 --- a/test/tests/common_tests/stored_procedures.robot +++ b/test/tests/common_tests/stored_procedures.robot @@ -98,11 +98,11 @@ Create And Fill Tables And Stored Procedures IF "${DB_MODULE}" in ["oracledb", "cx_Oracle"] Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_oracle.sql ELSE IF "${DB_MODULE}" in ["pymysql"] - Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_mysql.sql + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_mysql.sql ELSE IF "${DB_MODULE}" in ["psycopg2", "psycopg3"] - Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_postgres.sql + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_postgres.sql ELSE IF "${DB_MODULE}" in ["pymssql"] - Execute SQL Script ${CURDIR}/../../resources/create_stored_procedure_mssql.sql + Execute SQL Script ${CURDIR}/../../resources/create_stored_procedures_mssql.sql ELSE Skip Don't know how to create stored procedures for '${DB_MODULE}' END From 038d9f63ed7f7d80c081355762b0c28fb66364af Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 19 Dec 2023 17:08:10 +0100 Subject: [PATCH 189/266] Allow disabling the statement splitting when running an SQL script file --- src/DatabaseLibrary/query.py | 132 ++++++++++-------- .../sql_script_split_commands.robot | 22 +++ 2 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 test/tests/custom_db_tests/sql_script_split_commands.robot diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index a3bcf0a..b38d826 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -209,11 +209,17 @@ def delete_all_rows_from_table(self, tableName: str, sansTran: bool = False, ali if cur and not sansTran: db_connection.client.rollback() - def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, alias: Optional[str] = None): + def execute_sql_script( + self, sqlScriptFileName: str, sansTran: bool = False, split: bool = True, alias: Optional[str] = None + ): """ Executes the content of the `sqlScriptFileName` as SQL commands. Useful for setting the database to a known state before running your tests, or clearing out your test data after running each a test. + SQL commands are expected to be delimited by a semicolon (';') - they will be split and executed separately. + You can disable this behaviour setting the parameter `split` to _False_ - + in this case the entire script content will be passed to the database module for execution. + Sample usage : | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-setup.sql | | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-setup.sql | @@ -222,7 +228,6 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali | Execute Sql Script | ${EXECDIR}${/}resources${/}DML-teardown.sql | | Execute Sql Script | ${EXECDIR}${/}resources${/}DDL-teardown.sql | - SQL commands are expected to be delimited by a semicolon (';') - they will be executed separately. For example: DELETE FROM person_employee_table; @@ -273,72 +278,77 @@ def execute_sql_script(self, sqlScriptFileName: str, sansTran: bool = False, ali with open(sqlScriptFileName, encoding="UTF-8") as sql_file: cur = None try: - statements_to_execute = [] cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL Script | {sqlScriptFileName}") - current_statement = "" - inside_statements_group = False - proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?") - proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?") - for line in sql_file: - line = line.strip() - if line.startswith("#") or line.startswith("--") or line == "/": - continue - - # check if the line matches the creating procedure regexp pattern - elif proc_start_pattern.match(line.lower()): - inside_statements_group = True - elif line.lower().startswith("begin"): - inside_statements_group = True - - # semicolons inside the line? use them to separate statements - # ... but not if they are inside a begin/end block (aka. statements group) - sqlFragments = line.split(";") - # no semicolons - if len(sqlFragments) == 1: - current_statement += line + " " - continue - quotes = 0 - # "select * from person;" -> ["select..", ""] - for sqlFragment in sqlFragments: - if len(sqlFragment.strip()) == 0: + if not split: + logger.info("Statements splitting disabled - pass entire script content to the database module") + self.__execute_sql(cur, sql_file.read()) + else: + logger.info("Splitting script file into statements...") + statements_to_execute = [] + current_statement = "" + inside_statements_group = False + proc_start_pattern = re.compile("create( or replace)? (procedure|function){1}( )?") + proc_end_pattern = re.compile("end(?!( if;| loop;| case;| while;| repeat;)).*;()?") + for line in sql_file: + line = line.strip() + if line.startswith("#") or line.startswith("--") or line == "/": continue - if inside_statements_group: - # if statements inside a begin/end block have semicolns, - # they must persist - even with oracle - sqlFragment += "; " - - if proc_end_pattern.match(sqlFragment.lower()): - inside_statements_group = False - elif proc_start_pattern.match(sqlFragment.lower()): + # check if the line matches the creating procedure regexp pattern + if proc_start_pattern.match(line.lower()): inside_statements_group = True - elif sqlFragment.lower().startswith("begin"): + elif line.lower().startswith("begin"): inside_statements_group = True - # check if the semicolon is a part of the value (quoted string) - quotes += sqlFragment.count("'") - quotes -= sqlFragment.count("\\'") - quotes -= sqlFragment.count("''") - inside_quoted_string = quotes % 2 != 0 - if inside_quoted_string: - sqlFragment += ";" # restore the semicolon - - current_statement += sqlFragment - if not inside_statements_group and not inside_quoted_string: - statements_to_execute.append(current_statement.strip()) - current_statement = "" - quotes = 0 - - current_statement = current_statement.strip() - if len(current_statement) != 0: - statements_to_execute.append(current_statement) - - for statement in statements_to_execute: - logger.info(f"Executing statement from script file: {statement}") - line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") - omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) - self.__execute_sql(cur, statement, omit_semicolon) + # semicolons inside the line? use them to separate statements + # ... but not if they are inside a begin/end block (aka. statements group) + sqlFragments = line.split(";") + # no semicolons + if len(sqlFragments) == 1: + current_statement += line + " " + continue + quotes = 0 + # "select * from person;" -> ["select..", ""] + for sqlFragment in sqlFragments: + if len(sqlFragment.strip()) == 0: + continue + + if inside_statements_group: + # if statements inside a begin/end block have semicolns, + # they must persist - even with oracle + sqlFragment += "; " + + if proc_end_pattern.match(sqlFragment.lower()): + inside_statements_group = False + elif proc_start_pattern.match(sqlFragment.lower()): + inside_statements_group = True + elif sqlFragment.lower().startswith("begin"): + inside_statements_group = True + + # check if the semicolon is a part of the value (quoted string) + quotes += sqlFragment.count("'") + quotes -= sqlFragment.count("\\'") + quotes -= sqlFragment.count("''") + inside_quoted_string = quotes % 2 != 0 + if inside_quoted_string: + sqlFragment += ";" # restore the semicolon + + current_statement += sqlFragment + if not inside_statements_group and not inside_quoted_string: + statements_to_execute.append(current_statement.strip()) + current_statement = "" + quotes = 0 + + current_statement = current_statement.strip() + if len(current_statement) != 0: + statements_to_execute.append(current_statement) + + for statement in statements_to_execute: + logger.info(f"Executing statement from script file: {statement}") + line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") + omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) + self.__execute_sql(cur, statement, omit_semicolon) if not sansTran: db_connection.client.commit() finally: diff --git a/test/tests/custom_db_tests/sql_script_split_commands.robot b/test/tests/custom_db_tests/sql_script_split_commands.robot new file mode 100644 index 0000000..e6223cc --- /dev/null +++ b/test/tests/custom_db_tests/sql_script_split_commands.robot @@ -0,0 +1,22 @@ +*** Settings *** +Documentation Tests for the parameter _split_ in the keyword +... _Execute SQL Script_ - special for the issue #184: +... https://github.com/MarketSquare/Robotframework-Database-Library/issues/184 + +Resource ../../resources/common.resource +Suite Setup Connect To DB +Suite Teardown Disconnect From Database +Test Setup Create Person Table +Test Teardown Drop Tables Person And Foobar + + +*** Test Cases *** +Split Commands + [Documentation] Such a simple script works always, + ... just check if the logs if the parameter value was processed properly + Execute Sql Script ${CURDIR}/../../resources/insert_data_in_person_table.sql split=True + +Don't Split Commands + [Documentation] Running such a script as a single statement works for PostgreSQL, + ... but fails in Oracle. Check in the logs if the splitting was disabled. + Execute Sql Script ${CURDIR}/../../resources/insert_data_in_person_table.sql split=False From 8292df26c8e2c69b81f6b8038094411dfa93adf7 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 19 Dec 2023 17:10:08 +0100 Subject: [PATCH 190/266] Parameter 'omitTrailingSemicolon' for explicit instruction, if the trailing semicolon (;) at the SQL string end should be removed or not --- src/DatabaseLibrary/query.py | 21 +++++++++++---- .../oracle_omit_semicolon.robot | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 test/tests/custom_db_tests/oracle_omit_semicolon.robot diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index b38d826..5cbdf52 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -356,13 +356,23 @@ def execute_sql_script( db_connection.client.rollback() def execute_sql_string( - self, sqlString: str, sansTran: bool = False, alias: Optional[str] = None, parameters: Optional[List] = None + self, + sqlString: str, + sansTran: bool = False, + omitTrailingSemicolon: Optional[bool] = None, + alias: Optional[str] = None, + parameters: Optional[List] = None, ): """ - Executes the sqlString as SQL commands. Useful to pass arguments to your sql. - SQL commands are expected to be delimited by a semicolon (';'). + Executes the ``sqlString`` as a single SQL command. - Use optional `sansTran` to run command without an explicit transaction commit or rollback: + Use optional ``sansTran`` to run command without an explicit transaction commit or rollback. + + Use optional ``omitTrailingSemicolon`` parameter for explicit instruction, + if the trailing semicolon (;) at the SQL string end should be removed or not: + - Some database modules (e.g. Oracle) throw an exception, if you leave a semicolon at the string end + - However, there are exceptional cases, when you need it even for Oracle - e.g. at the end of a PL/SQL block. + - If not specified, it's decided based on the current database module in use. For Oracle, the semicolon is removed by default. Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. @@ -374,6 +384,7 @@ def execute_sql_string( | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | alias=my_alias | | Execute Sql String | DELETE FROM person_employee_table; DELETE FROM person_table | sansTran=True | + | Execute Sql String | CREATE PROCEDURE proc AS BEGIN DBMS_OUTPUT.PUT_LINE('Hello!'); END; | omitTrailingSemicolon=False | | @{parameters} | Create List | person_employee_table | | Execute Sql String | SELECT * FROM %s | parameters=${parameters} | """ @@ -382,7 +393,7 @@ def execute_sql_string( try: cur = db_connection.client.cursor() logger.info(f"Executing : Execute SQL String | {sqlString}") - self.__execute_sql(cur, sqlString, parameters=parameters) + self.__execute_sql(cur, sqlString, omit_trailing_semicolon=omitTrailingSemicolon, parameters=parameters) if not sansTran: db_connection.client.commit() finally: diff --git a/test/tests/custom_db_tests/oracle_omit_semicolon.robot b/test/tests/custom_db_tests/oracle_omit_semicolon.robot new file mode 100644 index 0000000..de2db7d --- /dev/null +++ b/test/tests/custom_db_tests/oracle_omit_semicolon.robot @@ -0,0 +1,27 @@ +*** Settings *** +Documentation Tests for the parameter _omitTrailingSemicolon_ in the keyword +... _Execute SQL String_ - special for the issue #184: +... https://github.com/MarketSquare/Robotframework-Database-Library/issues/184 +... The _PLSQL BLOCK_ is most likely valid for Oracle DB only. + +Resource ../../resources/common.resource +Suite Setup Connect To DB +Suite Teardown Disconnect From Database +Test Setup Create Person Table And Insert Data +Test Teardown Drop Tables Person And Foobar + + +*** Variables *** +${NORMAL QUERY} SELECT * FROM person; +${PLSQL BLOCK} DECLARE ERRCODE NUMBER; ERRMSG VARCHAR2(200); BEGIN DBMS_OUTPUT.PUT_LINE('Hello!'); END; + + +*** Test Cases *** +Explicitely Omit Semicolon + [Documentation] Check if it works for Oracle - explicitely omitting the semicolon + ... is equal to the default behaviour, otherwise oracle_db throws an error + Execute Sql String ${NORMAL QUERY} omitTrailingSemicolon=True + +Explicitely Dont't Omit Semicolon + [Documentation] Check if it works for Oracle - it throws an error without a semicolon + Execute Sql String ${PLSQL BLOCK} omitTrailingSemicolon=False From 96db35936548c0f93d5085d66171b08efd159b05 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 19 Dec 2023 20:04:30 +0100 Subject: [PATCH 191/266] Update docs of the 'query' keyword - fix #201 --- src/DatabaseLibrary/query.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index ce7ebab..dc14d1e 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -34,8 +34,12 @@ def query( parameters: Optional[List] = None, ): """ - Uses the input ``selectStatement`` to query for the values that will be returned as a list of tuples. - Set optional input ``returnAsDict`` to True to return values as a list of dictionaries. + Runs a query with the ``selectStatement`` and returns the result as a list of rows. + The type of row values depends on the database module - + usually they are tuples or tuple-like objects. + + Set optional input ``returnAsDict`` to _True_ to explicitely convert the return values + into a list of dictionaries. Use optional ``alias`` parameter to specify what connection should be used for the query if you have more than one connection open. From 29197dd3b8b179286f62297797571caf711eafa4 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 19 Dec 2023 20:05:30 +0100 Subject: [PATCH 192/266] v1.4.2 --- src/DatabaseLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index bababb5..94dc080 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "1.4.1" +VERSION = "1.4.2" From f8ee5c1b561adefa5a168d6f15eb47d6767e380d Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 19 Dec 2023 20:17:17 +0100 Subject: [PATCH 193/266] Update keyword docs --- doc/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/index.html b/doc/index.html index baf9a8e..2bf32eb 100644 --- a/doc/index.html +++ b/doc/index.html @@ -6,7 +6,7 @@ - + - - - - - - - - + + + + + + + - - - - - - - - - -
-

Opening library documentation failed

-
    -
  • 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.
  • -
-
- - - - - modalBackground.classList.remove('visible'); - modal.classList.remove('visible'); - document.body.style.overflow = 'auto'; - if (window.location.hash.indexOf('#type-') == 0) - history.pushState("", document.title, window.location.pathname); - // modal is hidden with a fading transition, timeout prevents premature emptying of modal - setTimeout(() => { - modalContent.innerHTML = ''; - }, 200); - } + + + + - // http://stackoverflow.com/a/18484799 - var delay = (function () { - var timer = 0; - return function(callback, ms) { - clearTimeout(timer); - timer = setTimeout(callback, ms); - }; - })(); - - - - - + + + + + + - - - - - - - + + + + - - + + - - - - - - - - - - - - + {{#if usages.length}} +
+

{{t "usages"}}

+
    + {{#each usages}} +
  • {{this}}
  • + {{/each}} +
+
+ {{/if}} + + + + + + diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index a50d1a2..f618471 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -401,14 +401,17 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): ROBOT_LIBRARY_SCOPE = "GLOBAL" ROBOT_LIBRARY_VERSION = __version__ - def __init__(self, log_query_results=True, log_query_results_head=50): + 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 to disable `Logging query results` or setup the log head. + + 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) + 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/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index a796a88..b334e15 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -31,12 +31,13 @@ class Connection: class ConnectionStore: - def __init__(self): + 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): - if alias in self._connections: + if alias in self._connections and self.warn_on_overwrite: if alias == self.default_alias: logger.warn("Overwriting not closed connection.") else: @@ -140,9 +141,9 @@ class ConnectionManager: Connection Manager handles the connection & disconnection to the database. """ - def __init__(self): + def __init__(self, warn_on_connection_overwrite): self.omit_trailing_semicolon: bool = False - self.connection_store: ConnectionStore = ConnectionStore() + self.connection_store: ConnectionStore = ConnectionStore(warn_on_overwrite=warn_on_connection_overwrite) self.ibmdb_driver_already_added_to_path: bool = False @staticmethod @@ -220,6 +221,9 @@ def connect_to_database( - _thick_ - _thick,lib_dir=_ + 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. + === Some parameters were renamed in version 2.0 === The old parameters ``dbapiModuleName``, ``dbName``, ``dbUsername``, ``dbPassword``, ``dbHost``, ``dbPort``, ``dbCharset``, ``dbDriver``, diff --git a/test/tests/common_tests/import_params.robot b/test/tests/common_tests/import_params.robot index b699f0f..f080bd7 100644 --- a/test/tests/common_tests/import_params.robot +++ b/test/tests/common_tests/import_params.robot @@ -11,4 +11,32 @@ Log Query Results Params Cause No Crash 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 \ No newline at end of file + ... 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 From ed401034375e1d9c27f157995604265290ee5595 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 3 Feb 2025 08:54:57 +0100 Subject: [PATCH 234/266] Default value for Import parameter 'warn_on_connection_overwrite' --- src/DatabaseLibrary/connection_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index b334e15..5e1bf86 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -141,7 +141,7 @@ class ConnectionManager: Connection Manager handles the connection & disconnection to the database. """ - def __init__(self, warn_on_connection_overwrite): + def __init__(self, warn_on_connection_overwrite=True): self.omit_trailing_semicolon: bool = False self.connection_store: ConnectionStore = ConnectionStore(warn_on_overwrite=warn_on_connection_overwrite) self.ibmdb_driver_already_added_to_path: bool = False From 025ac2282b300d62b66cbb4e1f150e98cf93e8a6 Mon Sep 17 00:00:00 2001 From: Jelle Dekker Date: Mon, 3 Feb 2025 10:33:32 +0100 Subject: [PATCH 235/266] Use len() to determine row count when using JayDeBeApi (#154) Co-authored-by: Andre Mochinin <35140131+amochin@users.noreply.github.com> --- src/DatabaseLibrary/query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 29dd202..dcba5a3 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -133,7 +133,7 @@ def row_count( self._execute_sql(cur, select_statement, parameters=parameters) data = cur.fetchall() col_names = [c[0] for c in cur.description] - if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc"]: + if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc", "jaydebeapi"]: current_row_count = len(data) else: current_row_count = cur.rowcount From 6f92ad94a8f5413b443bba590deb3800fe84bba2 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 3 Feb 2025 14:43:28 +0100 Subject: [PATCH 236/266] Rollback in case of errors only --- src/DatabaseLibrary/query.py | 67 +++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index dcba5a3..ff2933c 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -20,6 +20,7 @@ from robot.api import logger +from .connection_manager import Connection from .params_decorator import renamed_args @@ -87,9 +88,8 @@ def query( if return_dict: return [dict(zip(col_names, row)) for row in all_rows] return all_rows - finally: - if cur and not no_transaction: - db_connection.client.rollback() + 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( @@ -140,9 +140,8 @@ def row_count( logger.info(f"Retrieved {current_row_count} rows") self._log_query_results(col_names, data) return current_row_count - finally: - if cur and not no_transaction: - db_connection.client.rollback() + except Exception as e: + self._rollback_and_raise(db_connection, no_transaction, e) @renamed_args(mapping={"selectStatement": "select_statement", "sansTran": "no_transaction"}) def description( @@ -189,9 +188,8 @@ def description( for row in range(0, len(description)): description[row] = (description[row][0].encode("utf-8"),) + description[row][1:] return description - finally: - if cur and not no_transaction: - db_connection.client.rollback() + 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( @@ -229,15 +227,11 @@ def delete_all_rows_from_table( try: 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 no_transaction: - db_connection.client.commit() return result - if not no_transaction: - db_connection.client.commit() - finally: - if cur and not no_transaction: - db_connection.client.rollback() + 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( @@ -348,11 +342,9 @@ def execute_sql_script( line_ends_with_proc_end = re.compile(r"(\s|;)" + proc_end_pattern.pattern + "$") omit_semicolon = not line_ends_with_proc_end.search(statement.lower()) self._execute_sql(cur, statement, omit_semicolon) - if not no_transaction: - db_connection.client.commit() - finally: - if cur and not no_transaction: - db_connection.client.rollback() + self._commit_if_needed(db_connection, no_transaction) + except Exception as e: + self._rollback_and_raise(db_connection, no_transaction, e) @renamed_args( mapping={ @@ -410,11 +402,9 @@ def execute_sql_string( try: cur = db_connection.client.cursor() self._execute_sql(cur, sql_string, omit_trailing_semicolon=omit_trailing_semicolon, parameters=parameters) - if not no_transaction: - db_connection.client.commit() - finally: - if cur and not no_transaction: - db_connection.client.rollback() + 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( @@ -717,13 +707,10 @@ def call_stored_procedure( else: result_sets_available = False - if not no_transaction: - db_connection.client.commit() - + self._commit_if_needed(db_connection, no_transaction) return param_values, result_sets - finally: - if cur and not no_transaction: - db_connection.client.rollback() + 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): """ @@ -771,6 +758,22 @@ def _execute_sql( ) 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. From 012ae6309c86dc974f6acfad9538b9305a6c48c6 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 3 Feb 2025 20:27:58 +0100 Subject: [PATCH 237/266] Support set autocmmit for jaydebeapi --- src/DatabaseLibrary/connection_manager.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 5e1bf86..1d16983 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -44,7 +44,7 @@ def register_connection(self, client: Any, module_name: str, alias: str): logger.warn(f"Overwriting not closed connection for alias = '{alias}'") self._connections[alias] = Connection(client, module_name) - def get_connection(self, alias: Optional[str]): + def get_connection(self, alias: Optional[str]) -> Connection: """ Return connection with given alias. @@ -61,7 +61,7 @@ def get_connection(self, alias: Optional[str]): raise ValueError(f"Alias '{alias}' not found in existing connections.") return self._connections[alias] - def pop_connection(self, alias: Optional[str]): + def pop_connection(self, alias: Optional[str]) -> Connection: if not self._connections: return None if not alias: @@ -639,7 +639,10 @@ def set_auto_commit( | Set Auto Commit | True | alias=postgres | """ db_connection = self.connection_store.get_connection(alias) - db_connection.client.autocommit = auto_commit + if db_connection.module_name == "jaydebeapi": + db_connection.client.jconn.setAutoCommit(auto_commit) + else: + db_connection.client.autocommit = auto_commit def switch_database(self, alias: str): """ From 7b8a3bc45e4dd95fcaae22c49333c65d02c16663 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 11:34:39 +0100 Subject: [PATCH 238/266] Add tests for Jaydebeapi --- .gitignore | 1 + .vscode/launch.json | 1 + test/resources/common.resource | 10 +++++++ test/tests/__init__.robot | 8 +++++ .../assertion_error_messages.robot | 7 ++++- test/tests/common_tests/basic_tests.robot | 4 +-- .../common_tests/connection_params.robot | 2 +- .../common_tests/custom_connection.robot | 2 ++ test/tests/common_tests/description.robot | 19 ++++++------ test/tests/common_tests/query_params.robot | 2 +- test/tests/custom_db_tests/jaydebe.robot | 30 ------------------- 11 files changed, 42 insertions(+), 44 deletions(-) delete mode 100644 test/tests/custom_db_tests/jaydebe.robot diff --git a/.gitignore b/.gitignore index 56a41d3..b00d1b3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ report.html venv .runNumber .DS_Store +test/resources/ojdbc17.jar diff --git a/.vscode/launch.json b/.vscode/launch.json index 868cdbf..d940042 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -93,6 +93,7 @@ "Teradata", "MySQL_pymysql", "MySQL_pyodbc", + "Oracle_JDBC", "MSSQL", "Excel", "Excel_RW", diff --git a/test/resources/common.resource b/test/resources/common.resource index 93971d7..a1bca6c 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -21,6 +21,12 @@ ${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 @@ -36,6 +42,10 @@ Connect To DB 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} ELSE ${Connection String}= Build Connection String Connect To Database Using Custom Connection String ${DB_MODULE} ${Connection String} &{DB_KWARGS} diff --git a/test/tests/__init__.robot b/test/tests/__init__.robot index 05b728e..e62e855 100644 --- a/test/tests/__init__.robot +++ b/test/tests/__init__.robot @@ -73,6 +73,14 @@ Set DB Variables 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 diff --git a/test/tests/common_tests/assertion_error_messages.robot b/test/tests/common_tests/assertion_error_messages.robot index 28ac504..2ab45a3 100644 --- a/test/tests/common_tests/assertion_error_messages.robot +++ b/test/tests/common_tests/assertion_error_messages.robot @@ -120,8 +120,13 @@ Check Row Count With Assertion Engine Fails With 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' (int) should be '${expected value}' (int) + ... 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} diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index d836b4d..6bcc44e 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -10,10 +10,10 @@ Test Teardown Drop Tables Person And Foobar *** Test Cases *** -SQL Statement Ending With Semicolon Works +SQL Statement Ending Without Semicolon Works Query SELECT * FROM person -SQL Statement Ending Without Semicolon Works +SQL Statement Ending With Semicolon Works Query SELECT * FROM person; Create Person Table diff --git a/test/tests/common_tests/connection_params.robot b/test/tests/common_tests/connection_params.robot index b68e280..7e88a13 100644 --- a/test/tests/common_tests/connection_params.robot +++ b/test/tests/common_tests/connection_params.robot @@ -4,7 +4,7 @@ Documentation Tests for the basic _Connect To Database_ keyword - with and Resource ../../resources/common.resource -Test Setup Skip If $DB_MODULE == "sqlite3" +Test Setup Skip If $DB_MODULE == "sqlite3" or $DB_MODULE == "jaydebeapi" Test Teardown Disconnect From Database *** Variables *** diff --git a/test/tests/common_tests/custom_connection.robot b/test/tests/common_tests/custom_connection.robot index 61cb8ff..1e27e8c 100644 --- a/test/tests/common_tests/custom_connection.robot +++ b/test/tests/common_tests/custom_connection.robot @@ -36,6 +36,8 @@ Connect Using Custom Params ${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}', diff --git a/test/tests/common_tests/description.robot b/test/tests/common_tests/description.robot index 06f7527..70b0dcf 100644 --- a/test/tests/common_tests/description.robot +++ b/test/tests/common_tests/description.robot @@ -16,12 +16,12 @@ Verify Person Description 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) + 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) @@ -68,9 +68,10 @@ Verify Foobar Description 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) + 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) diff --git a/test/tests/common_tests/query_params.robot b/test/tests/common_tests/query_params.robot index 0bdab73..4761b73 100644 --- a/test/tests/common_tests/query_params.robot +++ b/test/tests/common_tests/query_params.robot @@ -21,7 +21,7 @@ Connect To DB And Build Query Build Query Strings With Params ${placeholder}= Set Variable %s - IF "${DB_MODULE}" in ["oracledb", "cx_Oracle"] + IF "${DB_MODULE}" in ["oracledb", "cx_Oracle", "jaydebeapi"] ${placeholder}= Set Variable :id ELSE IF "${DB_MODULE}" in ["sqlite3", "pyodbc"] ${placeholder}= Set Variable ? diff --git a/test/tests/custom_db_tests/jaydebe.robot b/test/tests/custom_db_tests/jaydebe.robot deleted file mode 100644 index 5b6463f..0000000 --- a/test/tests/custom_db_tests/jaydebe.robot +++ /dev/null @@ -1,30 +0,0 @@ -*** Settings *** -Documentation Test connecting to an Oracle database using jaydebeapi. -... Requires Oracle JDBC driver installed and put to the `DRIVER_PATH`. - -Resource ../../resources/common.resource - - -*** Variables *** -${DRIVER_PATH} ${CURDIR}/ojdbc17.jar -${DRIVER_CLASSNAME} oracle.jdbc.driver.OracleDriver -${DB_MODULE} jaydebeapi -${DB_HOST} 127.0.0.1 -${DB_PORT} 1521 -${DB_NAME} db -${JDBC_URL} jdbc:oracle:thin:@${DB_HOST}:${DB_PORT}/${DB_NAME} -&{DRIVER_ARGS} user=db_user password=pass - - -*** Test Cases *** -Regular Connect - Connect To Database ${DB_MODULE} - ... jclassname=${DRIVER_CLASSNAME} - ... url=${JDBC_URL} - ... driver_args=${DRIVER_ARGS} - ... jars=${DRIVER_PATH} - -Deprecated Connect - Connect To Database Using Custom Params - ... ${DB_MODULE} - ... '${DRIVER_CLASSNAME}', '${JDBC_URL}', ['${DB_USER}', '${DB_PASS}'], '${DRIVER_PATH}' From c4e187296258460d46b7b6a4ef89e52eae3d4531 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 14:42:36 +0100 Subject: [PATCH 239/266] Allow different 'omit trailing semicolon' behavior pro connection --- src/DatabaseLibrary/connection_manager.py | 14 ++++---- src/DatabaseLibrary/query.py | 39 ++++++++++++++++------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 1d16983..ec57616 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -28,6 +28,7 @@ class Connection: client: Any module_name: str + omit_trailing_semicolon: bool class ConnectionStore: @@ -36,13 +37,13 @@ def __init__(self, warn_on_overwrite=True): self.default_alias: str = "default" self.warn_on_overwrite = warn_on_overwrite - def register_connection(self, client: Any, module_name: str, alias: str): + 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) + self._connections[alias] = Connection(client, module_name, omit_trailing_semicolon) def get_connection(self, alias: Optional[str]) -> Connection: """ @@ -142,7 +143,6 @@ class ConnectionManager: """ def __init__(self, warn_on_connection_overwrite=True): - self.omit_trailing_semicolon: bool = False self.connection_store: ConnectionStore = ConnectionStore(warn_on_overwrite=warn_on_connection_overwrite) self.ibmdb_driver_already_added_to_path: bool = False @@ -320,6 +320,8 @@ def _arg_or_config(arg_value, param_name, *, old_param_name=None, mandatory=Fals if other_config_file_params: logger.info(f"Other params from configuration file: {list(other_config_file_params.keys())}") + omit_trailing_semicolon = False + if db_module == "excel" or db_module == "excelrw": db_api_module_name = "pyodbc" else: @@ -444,7 +446,7 @@ def _arg_or_config(arg_value, param_name, *, old_param_name=None, mandatory=Fals 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) - self.omit_trailing_semicolon = True + omit_trailing_semicolon = True elif db_module in ["oracledb"]: db_port = db_port or 1521 @@ -473,7 +475,7 @@ def _arg_or_config(arg_value, param_name, *, old_param_name=None, mandatory=Fals f"Expected oracledb to run in thin mode: {oracle_thin_mode}, " f"but the connection has thin mode: {db_connection.thin}" ) - self.omit_trailing_semicolon = True + omit_trailing_semicolon = True elif db_module in ["teradata"]: db_port = db_port or 1025 @@ -505,7 +507,7 @@ def _arg_or_config(arg_value, param_name, *, old_param_name=None, mandatory=Fals _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) + self.connection_store.register_connection(db_connection, db_api_module_name, alias, omit_trailing_semicolon) @renamed_args(mapping={"dbapiModuleName": "db_module"}) def connect_to_database_using_custom_params( diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index ff2933c..43e146f 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -81,7 +81,12 @@ def query( cur = None try: cur = db_connection.client.cursor() - self._execute_sql(cur, select_statement, parameters=parameters) + self._execute_sql( + cur, + select_statement, + parameters=parameters, + omit_trailing_semicolon=db_connection.omit_trailing_semicolon, + ) all_rows = cur.fetchall() col_names = [c[0] for c in cur.description] self._log_query_results(col_names, all_rows) @@ -130,7 +135,12 @@ def row_count( cur = None try: cur = db_connection.client.cursor() - self._execute_sql(cur, select_statement, parameters=parameters) + self._execute_sql( + cur, + select_statement, + parameters=parameters, + omit_trailing_semicolon=db_connection.omit_trailing_semicolon, + ) data = cur.fetchall() col_names = [c[0] for c in cur.description] if db_connection.module_name in ["sqlite3", "ibm_db", "ibm_db_dbi", "pyodbc", "jaydebeapi"]: @@ -182,7 +192,12 @@ def description( cur = None try: cur = db_connection.client.cursor() - self._execute_sql(cur, select_statement, parameters=parameters) + self._execute_sql( + cur, + select_statement, + parameters=parameters, + omit_trailing_semicolon=db_connection.omit_trailing_semicolon, + ) description = list(cur.description) if sys.version_info[0] < 3: for row in range(0, len(description)): @@ -276,7 +291,11 @@ def execute_sql_script( 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()) + 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 = [] @@ -377,11 +396,7 @@ def execute_sql_string( Use ``parameters`` for query variable substitution (variable substitution syntax may be different depending on the database client). - Use ``omit_trailing_semicolon`` for explicit instruction, - if the trailing semicolon (;) at the SQL string end should be removed or not: - - Some database modules (e.g. Oracle) throw an exception, if you leave a semicolon at the string end - - However, there are exceptional cases, when you need it even for Oracle - e.g. at the end of a PL/SQL block - - If not explicitly specified, it's decided based on the current database module in use. For Oracle, the semicolon is removed by default. + Set the ``omit_trailing_semicolon`` to explicitly control the `Omitting trailing semicolon behavior` for the command. === Some parameters were renamed in version 2.0 === The old parameters ``sqlString``, ``sansTran`` and ``omitTrailingSemicolon`` are *deprecated*, @@ -401,6 +416,8 @@ def execute_sql_string( 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: @@ -734,7 +751,7 @@ def _execute_sql( self, cur, sql_statement: str, - omit_trailing_semicolon: Optional[bool] = None, + omit_trailing_semicolon: Optional[bool] = False, parameters: Optional[Tuple] = None, ): """ @@ -744,8 +761,6 @@ def _execute_sql( 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 is None: - omit_trailing_semicolon = self.omit_trailing_semicolon if omit_trailing_semicolon: sql_statement = sql_statement.rstrip(";") if parameters is None: From 9a8389b962d0616190d3cbab404c6e3feb1e5c57 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 14:43:03 +0100 Subject: [PATCH 240/266] New keyword 'Set Omit Trailing Semicolon' --- README.md | 17 ++++- doc/index.html | 2 +- src/DatabaseLibrary/__init__.py | 13 ++++ src/DatabaseLibrary/connection_manager.py | 13 ++++ .../oracle_omit_semicolon.robot | 62 +++++++++++++++---- 5 files changed, 93 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b48f799..5b8f42b 100644 --- a/README.md +++ b/README.md @@ -367,7 +367,7 @@ The retry mechanism is disabled by default - ``retry_timeout`` is set to ``0``. ${sql}= Catenate SELECT first_name FROM person Check Row Count ${sql} == 2 retry_timeout=10 seconds Check Query Result ${sql} contains Allan retry_timeout=5s retry_pause=1s -```` +``` # Logging query results Keywords, that fetch results of a SQL query, print the result rows as a table in RF log. @@ -389,7 +389,7 @@ 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 - @@ -408,6 +408,19 @@ It's also possible to explicitly set the _autocommit_ behavior on the Python DB 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 > Looking for [Connection examples for different DB modules](#connection-examples-for-different-db-modules)? diff --git a/doc/index.html b/doc/index.html index fc64366..02a5494 100644 --- a/doc/index.html +++ b/doc/index.html @@ -8,7 +8,7 @@ diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index f618471..5e66f4d 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -363,6 +363,19 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): 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. diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index ec57616..4e3bc32 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -655,3 +655,16 @@ def switch_database(self, alias: str): | Switch Database | alias=my_alias | """ self.connection_store.switch(alias) + + def set_omit_trailing_semicolon(self, omit=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 | + """ + db_connection = self.connection_store.get_connection(alias) + db_connection.omit_trailing_semicolon = omit diff --git a/test/tests/custom_db_tests/oracle_omit_semicolon.robot b/test/tests/custom_db_tests/oracle_omit_semicolon.robot index 2ba6bd2..cbc9ae8 100644 --- a/test/tests/custom_db_tests/oracle_omit_semicolon.robot +++ b/test/tests/custom_db_tests/oracle_omit_semicolon.robot @@ -1,10 +1,11 @@ *** 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. +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 @@ -12,16 +13,55 @@ 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; +${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 - [Documentation] Check if it works for Oracle - explicitely omitting the semicolon - ... is equal to the default behaviour, otherwise oracle_db throws an error +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 Dont't Omit Semicolon - [Documentation] Check if it works for Oracle - it throws an error without a semicolon +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} From 19012b9a008443ebab545d2ec5ca1e2d12384da2 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 14:45:08 +0100 Subject: [PATCH 241/266] omit_trailing_semicolon - better argument naming --- src/DatabaseLibrary/connection_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 4e3bc32..f09a403 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -656,7 +656,7 @@ def switch_database(self, alias: str): """ self.connection_store.switch(alias) - def set_omit_trailing_semicolon(self, omit=True, alias: Optional[str] = None): + 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. @@ -667,4 +667,4 @@ def set_omit_trailing_semicolon(self, omit=True, alias: Optional[str] = None): | Set Omit Trailing Semicolon | False | alias=my_alias | """ db_connection = self.connection_store.get_connection(alias) - db_connection.omit_trailing_semicolon = omit + db_connection.omit_trailing_semicolon = omit_trailing_semicolon From 527b94afe95a062c761c137a1b659c02c3fe7d0c Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 14:53:41 +0100 Subject: [PATCH 242/266] Update the docs --- doc/index.html | 2234 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 1850 insertions(+), 384 deletions(-) diff --git a/doc/index.html b/doc/index.html index 02a5494..c4c4354 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1,410 +1,1876 @@ - + - - - - - - - + + + + + + + + + + + + + - - - - - -
-

Opening library documentation failed

-
    -
  • 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. -
  • -
-
+ + + + + + + + - +
+

Opening library documentation failed

+
    +
  • 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.
  • +
+
- - - - + -
- + + - - + + - - - - - - {{#if type}} - - {{> typeInfo type}} - + - - + + + + + + + - - + + - - - + + {{if doc}} +
+

Documentation

+
{{html doc}}
+
+ {{/if}} + + + + + + + + + + + + + + + From b42452dfe52eaea7badc261b3d249ddcdfaca978 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 15:08:16 +0100 Subject: [PATCH 243/266] Docs on ibm_db incompatibility - use ibm_db_dbi --- README.md | 6 +++--- doc/index.html | 2 +- src/DatabaseLibrary/__init__.py | 6 +++--- src/DatabaseLibrary/connection_manager.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5b8f42b..4b541f7 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ Connect To Database
-IBM DB2 (ibm_db) +IBM DB2 (ibm_db_dbi) ```RobotFramework Connect To Database @@ -447,8 +447,8 @@ Therefore there are some modules, which are "natively" supported in the library ### Teradata - [teradata](https://github.com/teradata/PyTd) ### IBM DB2 -- [ibm_db](https://github.com/ibmdb/python-ibmdb) -- [ibm_db_dbi](https://github.com/ibmdb/python-ibmdb) +- The Python package to be installed is [ibm_db](https://github.com/ibmdb/python-ibmdb). 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 [official docs](https://www.ibm.com/docs/en/db2/12.1?topic=applications-python-sqlalchemy-django-framework). ### ODBC - [pyodbc](https://github.com/mkleehammer/pyodbc) - [pypyodbc](https://github.com/pypyodbc/pypyodbc) diff --git a/doc/index.html b/doc/index.html index c4c4354..27c8255 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1191,7 +1191,7 @@ jQuery.extend({highlight:function(e,t,n,r){if(e.nodeType===3){var i=e.data.match(t);if(i){var s=document.createElement(n||"span");s.className=r||"highlight";var o=e.splitText(i.index);o.splitText(i[0].length);var u=o.cloneNode(true);s.appendChild(u);o.parentNode.replaceChild(s,o);return 1}}else if(e.nodeType===1&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&!(e.tagName===n.toUpperCase()&&e.className===r)){for(var a=0;a diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index 5e66f4d..01cdd27 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -386,7 +386,7 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): === 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. + - 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] @@ -402,8 +402,8 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): === Teradata === - [https://github.com/teradata/PyTd|teradata] === IBM DB2 === - - [https://github.com/ibmdb/python-ibmdb|ibm_db] - - [https://github.com/ibmdb/python-ibmdb|ibm_db_dbi] + - 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] diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index f09a403..23a17f2 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -209,7 +209,7 @@ def connect_to_database( 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*, the connection is established using a *connection string* - + 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. Set ``alias`` for `Handling multiple database connections`. From 12d7d6dd6ee2e88b91289894303891b5873daf76 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 15:11:45 +0100 Subject: [PATCH 244/266] Throw an error when trying to set autocommit for ibm_db as it's not supported --- src/DatabaseLibrary/connection_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 23a17f2..5710253 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -643,6 +643,8 @@ def set_auto_commit( 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 From 2e49f9ffe015911b2345f32d564cda71c9c740c6 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 15:14:16 +0100 Subject: [PATCH 245/266] Bump version to 2.1.0 --- doc/index.html | 2 +- src/DatabaseLibrary/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/index.html b/doc/index.html index 27c8255..4b90938 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1191,7 +1191,7 @@ jQuery.extend({highlight:function(e,t,n,r){if(e.nodeType===3){var i=e.data.match(t);if(i){var s=document.createElement(n||"span");s.className=r||"highlight";var o=e.splitText(i.index);o.splitText(i[0].length);var u=o.cloneNode(true);s.appendChild(u);o.parentNode.replaceChild(s,o);return 1}}else if(e.nodeType===1&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&!(e.tagName===n.toUpperCase()&&e.className===r)){for(var a=0;a diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 664cb5f..127c148 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "2.0.5" +VERSION = "2.1.0" From de11778d8f8bc8a89e672485de69c3f374b25b1c Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 15:20:39 +0100 Subject: [PATCH 246/266] Set "omit trailing semicolon" in tests for oracle via jaydebeapi --- test/resources/common.resource | 1 + 1 file changed, 1 insertion(+) diff --git a/test/resources/common.resource b/test/resources/common.resource index a1bca6c..260002c 100644 --- a/test/resources/common.resource +++ b/test/resources/common.resource @@ -46,6 +46,7 @@ Connect To DB 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} From 594caff435405048a0291ab324dfad2aab112531 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 15:29:55 +0100 Subject: [PATCH 247/266] docs - extend examples for JDBC --- README.md | 6 ++++++ doc/index.html | 2 +- src/DatabaseLibrary/__init__.py | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b541f7..1ced8ee 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,12 @@ Connect To Database ... 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 ```
diff --git a/doc/index.html b/doc/index.html index 4b90938..af2ac91 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1191,7 +1191,7 @@ jQuery.extend({highlight:function(e,t,n,r){if(e.nodeType===3){var i=e.data.match(t);if(i){var s=document.createElement(n||"span");s.className=r||"highlight";var o=e.splitText(i.index);o.splitText(i[0].length);var u=o.cloneNode(true);s.appendChild(u);o.parentNode.replaceChild(s,o);return 1}}else if(e.nodeType===1&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&!(e.tagName===n.toUpperCase()&&e.className===r)){for(var a=0;a diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index 01cdd27..2741393 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -236,6 +236,12 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): | ... 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 From 485c7a1d7cf53e694b97882345bc49e6b91c37d8 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 15:38:56 +0100 Subject: [PATCH 248/266] [CI] Update pages upload workflow to 3 --- .github/workflows/static.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 6c3001b..723b40d 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Pages uses: actions/configure-pages@v3 - name: Upload artifact - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: # Upload entire repository path: 'doc' From 888b529eeb480f8d65d547d19b7b6e2ea0bd3bc7 Mon Sep 17 00:00:00 2001 From: amochin Date: Tue, 4 Feb 2025 15:41:46 +0100 Subject: [PATCH 249/266] [CI] Update pages deploy workflow to 4 --- .github/workflows/static.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 723b40d..a9d347e 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -40,4 +40,4 @@ jobs: path: 'doc' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 From 516fb354186a6caba545d2221514554ee62c5721 Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:48:38 +0100 Subject: [PATCH 250/266] Support SAP HANA via ODBC - user "SERVERNODE" parameter in connection string (#236) * Change server string in ODBC Driver for SAPHANA * Proper parameters for SAP HANA --------- Co-authored-by: Raffaele Della Valle <41875661+RaffaeleDV@users.noreply.github.com> --- .vscode/settings.json | 6 ++++++ src/DatabaseLibrary/connection_manager.py | 12 ++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 65332e6..3bae561 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,4 +9,10 @@ "--output", "${workspaceRoot}/logs/interactive_console.xml" ], "python.analysis.completeFunctionParens": true, + "cSpell.words": [ + "hana", + "hdbodbc", + "saphana", + "SERVERNODE" + ], } \ No newline at end of file diff --git a/src/DatabaseLibrary/connection_manager.py b/src/DatabaseLibrary/connection_manager.py index 5710253..87926ab 100644 --- a/src/DatabaseLibrary/connection_manager.py +++ b/src/DatabaseLibrary/connection_manager.py @@ -391,10 +391,14 @@ def _arg_or_config(arg_value, param_name, *, old_param_name=None, mandatory=Fals if db_charset: con_str += f"charset={db_charset};" if db_host and db_port: - if odbc_driver and "mysql" in odbc_driver.lower(): - con_str += f"SERVER={db_host}:{db_port};" - else: - con_str += f"SERVER={db_host},{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};" From f38e07789de53a47c3973c44950496331d3ba263 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 10 Feb 2025 21:51:44 +0200 Subject: [PATCH 251/266] Bump version to 2.1.1 --- doc/index.html | 2 +- src/DatabaseLibrary/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/index.html b/doc/index.html index af2ac91..ab6f1b8 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1191,7 +1191,7 @@ jQuery.extend({highlight:function(e,t,n,r){if(e.nodeType===3){var i=e.data.match(t);if(i){var s=document.createElement(n||"span");s.className=r||"highlight";var o=e.splitText(i.index);o.splitText(i[0].length);var u=o.cloneNode(true);s.appendChild(u);o.parentNode.replaceChild(s,o);return 1}}else if(e.nodeType===1&&e.childNodes&&!/(script|style)/i.test(e.tagName)&&!(e.tagName===n.toUpperCase()&&e.className===r)){for(var a=0;a diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 127c148..5b0431e 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "2.1.0" +VERSION = "2.1.1" From 714f1ed03eabef50da8b5d97f8f39e395a2671d4 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 15 Feb 2025 10:04:12 +0200 Subject: [PATCH 252/266] Test "For Loop" --- test/tests/common_tests/basic_tests.robot | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 6bcc44e..d09ff24 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -144,3 +144,11 @@ 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 + +For Loop + FOR ${i} IN RANGE 10 + ${results}= Query SELECT LAST_NAME FROM person ORDER BY id + Sleep 3s + IF '${results}[0][0]' == 'Musk' BREAK + END + Should Be Equal As Strings ${results}[0][0] Musk \ No newline at end of file From b077395683d6eb25a2d87ce18aa756ba25ff8d8e Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:32:21 +0200 Subject: [PATCH 253/266] Remove Excellib dependency as it's not needed for library usage (required for running tests only) (#240) --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7a3d83a..f678224 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ readme = "README.md" requires-python = ">=3.8.1" dependencies = [ "robotframework>=5.0.1", - "robotframework-excellib", "robotframework-assertion-engine" ] classifiers = [ From ff1645d184aa6655b5c52d30157b19921482053a Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:33:05 +0200 Subject: [PATCH 254/266] Support RF dotted dictionary access syntax when returning query results as a dictionary (#239) --- src/DatabaseLibrary/query.py | 3 ++- test/tests/common_tests/basic_tests.robot | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 43e146f..5b4f0c0 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -19,6 +19,7 @@ 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 @@ -91,7 +92,7 @@ def query( col_names = [c[0] for c in cur.description] self._log_query_results(col_names, all_rows) if return_dict: - return [dict(zip(col_names, row)) for row in all_rows] + 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) diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 6bcc44e..adbd93e 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -113,6 +113,19 @@ Verify Query - Get results as a list of dictionaries 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} From 619285a7d0b2adcea0b63d8989769dca2cb0956f Mon Sep 17 00:00:00 2001 From: Andre Mochinin <35140131+amochin@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:33:31 +0200 Subject: [PATCH 255/266] Better colors in logging query results when using dark mode (#238) --- src/DatabaseLibrary/query.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 5b4f0c0..70b2680 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -803,12 +803,13 @@ def _log_query_results(self, col_names, result_rows, log_head: Optional[int] = N 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_color = "#d6ecd4" - msg = f'
' + row_index_background_color = "#d6ecd4" + row_index_text_color = "black" + msg = '
' msg += f'' msg += f'' msg += "" - msg += f'' + msg += f'' for col in col_names: msg += f'' msg += "" @@ -819,9 +820,9 @@ def _log_query_results(self, col_names, result_rows, log_head: Optional[int] = N break row_style = "" if i % 2 == 0: - row_style = ' style="background-color: #eee;"' + row_style = ' style="background-color: var(--secondary-color, #eee)"' msg += f"" - msg += f'' + msg += f'' for cell in row: msg += f'' msg += "" From 1bfaec6f646b223571a512a72ac3ae9d30c708e4 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 15 Feb 2025 14:34:53 +0200 Subject: [PATCH 256/266] Bump version to 2.1.2 --- doc/index.html | 2234 ++++++-------------------------- src/DatabaseLibrary/version.py | 2 +- 2 files changed, 385 insertions(+), 1851 deletions(-) diff --git a/doc/index.html b/doc/index.html index ab6f1b8..54ea42e 100644 --- a/doc/index.html +++ b/doc/index.html @@ -1,1876 +1,410 @@ - + - - - - - - - - - - - - - + + + + + + + - - - - - - - - - -
-

Opening library documentation failed

-
    -
  • 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.
  • -
-
- - - - - modalBackground.classList.remove('visible'); - modal.classList.remove('visible'); - document.body.style.overflow = 'auto'; - if (window.location.hash.indexOf('#type-') == 0) - history.pushState("", document.title, window.location.pathname); - // modal is hidden with a fading transition, timeout prevents premature emptying of modal - setTimeout(() => { - modalContent.innerHTML = ''; - }, 200); - } + + + + - // http://stackoverflow.com/a/18484799 - var delay = (function () { - var timer = 0; - return function(callback, ms) { - clearTimeout(timer); - timer = setTimeout(callback, ms); - }; - })(); - - - - - + + + + + + - - - - - - - + + + + - - + + - - - - - - - - - - - - + {{#if usages.length}} +
+

{{t "usages"}}

+
    + {{#each usages}} +
  • {{this}}
  • + {{/each}} +
+
+ {{/if}} + + + + + + diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 5b0431e..b777579 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "2.1.1" +VERSION = "2.1.2" From 02dbb6125402c555a73c02e7df375a133c99f817 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 15 Feb 2025 19:00:28 +0200 Subject: [PATCH 257/266] Delete old tests --- test/tests/_old/DB2SQL_DB_Conf.txt | 6 - test/tests/_old/DB2SQL_DB_Tests.robot | 99 ------ test/tests/_old/MSSQL_DB_Tests.robot | 210 ------------ test/tests/_old/MySQL-tests.yml | 125 ------- test/tests/_old/MySQL_DB_Tests.robot | 179 ----------- test/tests/_old/Oracle-tests.yml | 61 ---- .../_old/Oracle_Custom_Params_Tests.robot | 274 ---------------- test/tests/_old/PostgreSQL-tests.yml | 71 ---- test/tests/_old/PostgreSQL_DB_Tests.robot | 147 --------- test/tests/_old/PyODBC_DB_Tests.robot | 185 ----------- test/tests/_old/SQLite-tests.yml | 38 --- test/tests/_old/SQLite3_DB_Tests.robot | 304 ------------------ test/tests/_old/Teradata_DB_Tests.robot | 159 --------- test/tests/_old/excel_old.robot | 294 ----------------- test/tests/_old/my_db_test_insertData.sql | 2 - 15 files changed, 2154 deletions(-) delete mode 100644 test/tests/_old/DB2SQL_DB_Conf.txt delete mode 100644 test/tests/_old/DB2SQL_DB_Tests.robot delete mode 100644 test/tests/_old/MSSQL_DB_Tests.robot delete mode 100644 test/tests/_old/MySQL-tests.yml delete mode 100644 test/tests/_old/MySQL_DB_Tests.robot delete mode 100644 test/tests/_old/Oracle-tests.yml delete mode 100644 test/tests/_old/Oracle_Custom_Params_Tests.robot delete mode 100644 test/tests/_old/PostgreSQL-tests.yml delete mode 100644 test/tests/_old/PostgreSQL_DB_Tests.robot delete mode 100644 test/tests/_old/PyODBC_DB_Tests.robot delete mode 100644 test/tests/_old/SQLite-tests.yml delete mode 100644 test/tests/_old/SQLite3_DB_Tests.robot delete mode 100644 test/tests/_old/Teradata_DB_Tests.robot delete mode 100644 test/tests/_old/excel_old.robot delete mode 100644 test/tests/_old/my_db_test_insertData.sql diff --git a/test/tests/_old/DB2SQL_DB_Conf.txt b/test/tests/_old/DB2SQL_DB_Conf.txt deleted file mode 100644 index 3a22bea..0000000 --- a/test/tests/_old/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/tests/_old/DB2SQL_DB_Tests.robot b/test/tests/_old/DB2SQL_DB_Tests.robot deleted file mode 100644 index a66bdaa..0000000 --- a/test/tests/_old/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; \ No newline at end of file diff --git a/test/tests/_old/MSSQL_DB_Tests.robot b/test/tests/_old/MSSQL_DB_Tests.robot deleted file mode 100644 index f5870d2..0000000 --- a/test/tests/_old/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/tests/_old/MySQL-tests.yml b/test/tests/_old/MySQL-tests.yml deleted file mode 100644 index 1568794..0000000 --- a/test/tests/_old/MySQL-tests.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: MySQL (pymysql, pyodbc) Tests - -on: [push, pull_request] - -env: - DB_DATABASE: my_db_test - DB_USER: root - DB_PASSWORD: root - DB_HOST: 127.0.0.1 - DB_PORT: 3306 - -jobs: - dbsetup: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - # py-db-module: ["pymysql", "pyodbc", "pymssql", "MySQLdb"] - include: - - db-name: PyMySQL - py-db-module: pymysql - pip-install: pymysql - - db-name: PyODBC - py-db-module: pyodbc - pip-install: pyodbc - - db-name: MySQLdb - py-db-module: MySQLdb - pip-install: mysqlclient - steps: - - uses: actions/checkout@v3 - - - name: Set up MySQL - run: | - sudo /etc/init.d/mysql start - mysql -e 'CREATE DATABASE ${{ matrix.py-db-module }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} - - - name: Verify MySQL Setup - run: | - mysql -e "SHOW DATABASES;" -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} - netstat -ano - - - name: Install ODBC driver for PostgreSQL - run: | - echo "*** apt-get install the driver" - sudo apt-get install --yes odbc-postgresql - echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' - ls -l /usr/lib/x86_64-linux-gnu/odbc || true - echo '*** add full paths to Postgres .so files in /etc/odbcinst.ini' - sudo sed -i 's|Driver=psqlodbca.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbca.so|g' /etc/odbcinst.ini - sudo sed -i 's|Driver=psqlodbcw.so|Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so|g' /etc/odbcinst.ini - sudo sed -i 's|Setup=libodbcpsqlS.so|Setup=/usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so|g' /etc/odbcinst.ini - - - name: Install ODBC driver for MySQL - run: | - cd "$RUNNER_TEMP" - echo "*** download driver zip file" - curl --silent --show-error --write-out "$CURL_OUTPUT_FORMAT" -O "https://www.mirrorservice.org/sites/ftp.mysql.com/Downloads/Connector-ODBC/8.0/${MYSQL_DRIVER}.tar.gz" - ls -l "${MYSQL_DRIVER}.tar.gz" - tar -xz -f "${MYSQL_DRIVER}.tar.gz" - echo "*** copy driver file to /usr/lib" - sudo cp -v "${MYSQL_DRIVER}/lib/libmyodbc8a.so" /usr/lib/x86_64-linux-gnu/odbc/ - sudo chmod a+r /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so - echo "*** create odbcinst.ini entry" - echo '[MySQL ODBC 8.0 ANSI Driver]' > mysql_odbcinst.ini - echo 'Driver = /usr/lib/x86_64-linux-gnu/odbc/libmyodbc8a.so' >> mysql_odbcinst.ini - echo 'UsageCount = 1' >> mysql_odbcinst.ini - echo 'Threading = 2' >> mysql_odbcinst.ini - sudo odbcinst -i -d -f mysql_odbcinst.ini - env: - CURL_OUTPUT_FORMAT: '%{http_code} %{filename_effective} %{size_download} %{time_total}\n' - MYSQL_DRIVER: mysql-connector-odbc-8.0.22-linux-glibc2.12-x86-64bit - - - name: Check ODBC setup - run: | - echo "*** odbcinst -j" - odbcinst -j - echo "*** cat /etc/odbcinst.ini" - cat /etc/odbcinst.ini - echo "*** cat /etc/odbc.ini" - cat /etc/odbc.ini - echo '*** ls -l /opt/microsoft/msodbcsql17/lib64' - ls -l /opt/microsoft/msodbcsql17/lib64 || true - echo '*** ls -l /usr/lib/x86_64-linux-gnu/odbc' - ls -l /usr/lib/x86_64-linux-gnu/odbc || true - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.16' - - - name: Setup Python Packaging and Pip - run: | - python -m pip install --upgrade pip - pip install wheel - - - name: Setup Robot Framework - run: | - pip install robotframework - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install ${{ github.workspace }}/. - - - name: Setup Python DB module - run: | - pip install ${{ matrix.pip-install }} - - - name: Run Robot Framework tests using PyMySQL - if: matrix.py-db-module == 'pymysql' - working-directory: ./test - run: | - robot -d pymysql/ -v DBModule:${{ matrix.py-db-module }} -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/MySQL_DB_Tests.robot - - - name: Run Robot Framework tests using PyODBC - if: matrix.py-db-module == 'pyodbc' - working-directory: ./test - run: | - python3 -c "import pyodbc; print(pyodbc.drivers())" - robot --loglevel TRACE -d pyodbc/ -v DBName:${{ matrix.py-db-module }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:localhost -v DBPort:${{ env.DB_PORT }} -v DBCharset:utf8mb4 -v dbDriver:"{MySQL ODBC 8.0 ANSI Driver}" ${{ github.workspace }}/test/PyODBC_DB_Tests.robot - - - name: Upload Robot Logs - if: always() - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/ \ No newline at end of file diff --git a/test/tests/_old/MySQL_DB_Tests.robot b/test/tests/_old/MySQL_DB_Tests.robot deleted file mode 100644 index 5960485..0000000 --- a/test/tests/_old/MySQL_DB_Tests.robot +++ /dev/null @@ -1,179 +0,0 @@ -*** Settings *** -Suite Setup Connect To Database ${DBModule} ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} -Suite Teardown Disconnect From Database -Library DatabaseLibrary -Library OperatingSystem -Test Tags main db smoke - - -*** Variables *** -${DBHost} 127.0.0.1 -${DBName} my_db_test -${DBPass} "" -${DBPort} 3306 -${DBUser} root - -*** 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 - Comment ${output} = Execute SQL Script ./my_db_test_insertData.sql - ${output} = Execute SQL Script ${CURDIR}/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 * FROM person LIMIT 1; - Log Many @{queryResults} - ${output} = Set Variable ${queryResults[0]} - Should Be Equal As Strings ${output} ('id', 3, None, 11, 11, 0, True) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('first_name', 253, None, 80, 80, 0, True) - ${output} = Set Variable ${queryResults[2]} - Should Be Equal As Strings ${output} ('last_name', 253, None, 80, 80, 0, True) - ${NumColumns} = Get Length ${queryResults} - Should Be Equal As Integers ${NumColumns} 3 - -Verify foobar Description - 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', 3, None, 11, 11, 0, False) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('firstname', 253, None, 80, 80, 0, True) - ${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; - Comment ${output} = Query SELECT COUNT(*) FROM foobar; - Comment Log ${output} - Comment Should Be Equal As Strings ${output} [(0,)] - -Begin first transaction - ${output} = Execute SQL String SAVEPOINT 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 SAVEPOINT 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 TO SAVEPOINT 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 TO SAVEPOINT 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,foobar; - Log ${output} - Should Be Equal As Strings ${output} None diff --git a/test/tests/_old/Oracle-tests.yml b/test/tests/_old/Oracle-tests.yml deleted file mode 100644 index 29cc791..0000000 --- a/test/tests/_old/Oracle-tests.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Oracle Tests - -on: [push, pull_request] - -env: - DB_DATABASE: FREEPDB1 - DB_USER: db_user - DB_PASSWORD: pass - DB_MODULE: oracledb - -jobs: - run-robotframework-tests-psycopg2: - runs-on: ubuntu-latest - strategy: - fail-fast: false - - services: - oracle: - image: gvenzl/oracle-free:latest - env: - ORACLE_PASSWORD: pass - APP_USER: db_user - APP_USER_PASSWORD: pass - ports: - - 1521:1521 - # Provide healthcheck script options for startup - options: >- - --health-cmd healthcheck.sh - --health-interval 10s - --health-timeout 5s - --health-retries 10 - - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install ${{ env.DB_MODULE }} - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Run Robot Framework tests using oracledb - working-directory: ./test - run: | - robot -T --xunit result.xml -d results/ -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} ${{ github.workspace }}/test/Oracle_Custom_Params_Tests.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ diff --git a/test/tests/_old/Oracle_Custom_Params_Tests.robot b/test/tests/_old/Oracle_Custom_Params_Tests.robot deleted file mode 100644 index 4de9c88..0000000 --- a/test/tests/_old/Oracle_Custom_Params_Tests.robot +++ /dev/null @@ -1,274 +0,0 @@ -*** Settings *** -Library DatabaseLibrary -Library Collections - -Suite Setup Connect To DB -Suite Teardown Disconnect From Database -Test Setup Create Person Table And Insert Data -Test Teardown Drop Tables - - -*** Variables *** -${DBHost} 127.0.0.1 -${DBName} db -${DBPass} pass -${DBPort} 1521 -${DBUser} db_user - - -*** Test Cases *** -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 person table - [Tags] db smoke - [Setup] Create Person Table - ${output} = Execute SQL Script ${CURDIR}/my_db_test_insertData.sql - Log ${output} - Should Be Equal As Strings ${output} None - -Execute SQL String - Create Table - [Tags] db smoke - [Setup] Log No setup for this test - ${output} = Create Foobar Table - 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 FETCH FIRST 1 ROWS ONLY - Log Many @{queryResults} - ${output} = Set Variable ${queryResults[0]} - Should Be Equal As Strings ${output} ('ID', , 39, None, 38, 0, False) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('FIRST_NAME', , 20, 20, None, None, False) - ${output} = Set Variable ${queryResults[2]} - Should Be Equal As Strings ${output} ('LAST_NAME', , 20, 20, None, None, False) - ${NumColumns} = Get Length ${queryResults} - Should Be Equal As Integers ${NumColumns} 3 - -Verify foobar Description - [Tags] db smoke - [Setup] Create Foobar Table - Comment Query db for table column descriptions - @{queryResults} = Description SELECT * FROM foobar FETCH FIRST 1 ROWS ONLY - Log Many @{queryResults} - ${output} = Set Variable ${queryResults[0]} - Should Be Equal As Strings ${output} ('ID', , 39, None, 38, 0, False) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('FIRST_NAME', , 20, 20, None, None, False) - ${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} - ${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 - [Tags] db smoke - [Setup] Create 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 - [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 - [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 - [Tags] db smoke - [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 - [Tags] db smoke - [Setup] Create Foobar Table And Insert Data - ${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 - [Tags] db smoke - [Setup] Create Foobar Table And Insert Data - Delete All Rows From Table foobar - Comment Sleep 2s - -Verify Query - Row Count foobar table 0 row - [Tags] db smoke - [Setup] Create Foobar Table And Insert Data - Delete All Rows From Table foobar - 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,)] - -Transaction - [Tags] db smoke - [Setup] Create Person Table - 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 *** -Connect To DB - ${con_str} = Catenate SEPARATOR=${EMPTY} - ... ${DBUser}/${DBPass}@ - ... ${DBHost}:${DBPort}/${DBName} - Connect To Database Using Custom Params oracledb "${con_str}" - -Create Person Table - # ${sql} = Catenate - # ... CREATE TABLE person - # ... (id INT GENERATED BY DEFAULT AS IDENTITY, - # ... first_name varchar2(20) NOT NULL, - # ... last_name varchar2(20) NOT NULL, - # ... PRIMARY KEY(id)) - ${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 - Execute SQL Script ${CURDIR}/my_db_test_insertData.sql - -Create Foobar Table - ${sql} = Catenate - ... CREATE TABLE foobar - ... (id INT GENERATED BY DEFAULT AS IDENTITY, - ... first_name varchar2(20) NOT NULL, - ... PRIMARY KEY(id)) - ${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') - -Begin first transaction - ${output} = Execute SQL String SAVEPOINT 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 SAVEPOINT 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 TO SAVEPOINT 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 TO SAVEPOINT 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 Tables - ${sql} = Catenate DROP TABLE IF EXISTS person - Execute SQL String ${sql} - ${sql} = Catenate DROP TABLE IF EXISTS foobar - Execute SQL String ${sql} diff --git a/test/tests/_old/PostgreSQL-tests.yml b/test/tests/_old/PostgreSQL-tests.yml deleted file mode 100644 index fe68cf4..0000000 --- a/test/tests/_old/PostgreSQL-tests.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: PostgreSQL (psycopg2) Tests - -on: [push, pull_request] - -env: - DB_DATABASE: db - DB_USER: postgres - DB_PASSWORD: postgres - DB_HOST: postgres - DB_PORT: 5432 - DB_MODULE: psycopg2 - -jobs: - run-robotframework-tests-psycopg2: - runs-on: ubuntu-latest - strategy: - fail-fast: false - - services: - postgres: - image: postgres:11 - env: - POSTGRES_DB: db - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - '5432' - # needed because the postgres container does not provide a healthcheck - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install ${{ env.DB_MODULE }} - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Set DB options if in ACT - if: ${{ env.ACT }} - run: | - echo "DB_HOST=${{ env.ACT_DB_HOST }}" >> $GITHUB_ENV - echo "DB_PORT=${{ env.ACT_POSTGRES_DB_PORT }}" >> $GITHUB_ENV - - - name: Set DB options if not in ACT - if: ${{ !env.ACT }} - run: | - echo "DB_HOST=localhost" >> $GITHUB_ENV - echo "DB_PORT=${{ job.services.postgres.ports[5432] }}" >> $GITHUB_ENV - - - name: Run Robot Framework tests using psycopg2 - working-directory: ./test - run: | - robot -T --xunit result.xml -d results/ -v DBModule:${{ env.DB_MODULE }} -v DBName:${{ env.DB_DATABASE }} -v DBUser:${{ env.DB_USER }} -v DBPass:${{ env.DB_PASSWORD }} -v DBHost:${{ env.DB_HOST }} -v DBPort:${{ env.DB_PORT }} ${{ github.workspace }}/test/PostgreSQL_DB_Tests.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ diff --git a/test/tests/_old/PostgreSQL_DB_Tests.robot b/test/tests/_old/PostgreSQL_DB_Tests.robot deleted file mode 100644 index 5cbc3f1..0000000 --- a/test/tests/_old/PostgreSQL_DB_Tests.robot +++ /dev/null @@ -1,147 +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 -Test Tags main db smoke - - -*** Variables *** -${DBHost} localhost -${DBName} db -${DBPass} pass -${DBPort} 5432 -${DBUser} db_user - -*** Test Cases *** -Create person table - ${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 ${CURDIR}/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 - 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) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} Column(name='first_name', type_code=1043) - ${output} = Set Variable ${queryResults[2]} - Should Be Equal As Strings ${output} Column(name='last_name', type_code=1043) - ${NumColumns} = Get Length ${queryResults} - Should Be Equal As Integers ${NumColumns} 3 - -Verify foobar Description - 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) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} Column(name='firstname', type_code=1043) - ${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 - ${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/tests/_old/PyODBC_DB_Tests.robot b/test/tests/_old/PyODBC_DB_Tests.robot deleted file mode 100644 index a28cec0..0000000 --- a/test/tests/_old/PyODBC_DB_Tests.robot +++ /dev/null @@ -1,185 +0,0 @@ -*** Settings *** -Suite Setup Connect To Database ${DBModule} ${DBName} ${DBUser} ${DBPass} dbHost=${DBHost} dbPort=${DBPort} dbCharset=${DBCharset} dbDriver=${dbDriver} -Suite Teardown Disconnect From Database -Library DatabaseLibrary -Library Collections -Library OperatingSystem -Test Tags main db smoke - - -*** Variables *** -${DBModule} pyodbc -${DBHost} ${EMPTY} -${DBName} ${EMPTY} -${DBPass} ${EMPTY} -${DBPort} ${EMPTY} -${DBUser} ${EMPTY} -${DBCharset} ${None} -#${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 * FROM person LIMIT 1; - Log Many @{queryResults} - ${output} = Set Variable ${queryResults[0]} - Should Be Equal As Strings ${output} ('id', , None, 10, 10, 0, True) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('first_name', , None, 20, 20, 0, True) - ${output} = Set Variable ${queryResults[2]} - Should Be Equal As Strings ${output} ('last_name', , None, 20, 20, 0, True) - ${NumColumns} = Get Length ${queryResults} - Should Be Equal As Integers ${NumColumns} 3 - -Verify foobar Description - 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, 10, 10, 0, False) - ${output} = Set Variable ${queryResults[1]} - Should Be Equal As Strings ${output} ('firstname', , None, 20, 20, 0, True) - ${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 SAVEPOINT 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 SAVEPOINT 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 TO 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 TO 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 - -Disconnect from all databases - Disconnect From All Databases diff --git a/test/tests/_old/SQLite-tests.yml b/test/tests/_old/SQLite-tests.yml deleted file mode 100644 index 1d0fa27..0000000 --- a/test/tests/_old/SQLite-tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: SQLite (sqlite3) Tests - -on: [push, pull_request] - -jobs: - run-robotframework-tests-sqlite: - runs-on: ubuntu-latest - strategy: - fail-fast: false - - steps: - - name: Check out repository code - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: '3.8.14' - - - name: Setup Python dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Install Development/Checked out version of DatabaseLibrary - run: | - pip install -e ${{ github.workspace }} - - - name: Run Robot Framework tests using SQLite3 - working-directory: ./test - run: | - robot -T --xunit result.xml -d results/ ${{ github.workspace }}/test/SQLite3_DB_Tests.robot - - - name: Upload Robot Logs - if: ${{ always() }} - uses: actions/upload-artifact@v3 - with: - name: log-files - path: ./test/results/ diff --git a/test/tests/_old/SQLite3_DB_Tests.robot b/test/tests/_old/SQLite3_DB_Tests.robot deleted file mode 100644 index 2580cb1..0000000 --- a/test/tests/_old/SQLite3_DB_Tests.robot +++ /dev/null @@ -1,304 +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 Exists In DB Fails - [Tags] db smoke - ${expected_error} = Catenate - ... Expected to have have at least one row from 'SELECT id FROM person WHERE first_name = 'Joe';' - ... but got 0 rows. - Run Keyword And Expect Error ${expected_error} Check If Exists In Database - ... SELECT id FROM person WHERE first_name = 'Joe'; - -Check If Exists In DB Fails With Message - [Tags] db smoke - Run Keyword And Expect Error my error message Check If Exists In Database - ... SELECT id FROM person WHERE first_name = 'Joe'; msg=my error message - -Check If Not Exists In DB - Joe - [Tags] db smoke - Check If Not Exists In Database SELECT id FROM person WHERE first_name = 'Joe'; - -Check If Not Exists In DB Fails - [Tags] db smoke - ${expected_error} = Catenate - ... Expected to have have no rows from 'SELECT id FROM person WHERE first_name = 'Franz Allan';' - ... but got some rows : [(1,)* - Run Keyword And Expect Error ${expected_error} Check If Not Exists In Database - ... SELECT id FROM person WHERE first_name = 'Franz Allan'; - -Check If Not Exists In DB Fails With Message - [Tags] db smoke - Run Keyword And Expect Error my error message Check If Not Exists In Database - ... SELECT id FROM person WHERE first_name = 'Franz Allan'; msg=my error message - -Table Must Exist - person - [Tags] db smoke - Table Must Exist person - -Table Must Exist Fails - [Tags] db smoke - Run Keyword And Expect Error Table 'nonexistent' does not exist in the db - ... Table Must Exist nonexistent - -Table Must Exist Fails With Message - [Tags] db smoke - Run Keyword And Expect Error my error message - ... Table Must Exist nonexistent msg=my error message - -Verify Row Count Is 0 - [Tags] db smoke - Row Count Is 0 SELECT * FROM person WHERE first_name = 'NotHere'; - -Verify Row Count Is 0 Fails - [Tags] db smoke - ${expected_error} = Catenate - ... Expected zero rows to be returned from 'SELECT * FROM person WHERE first_name = 'Franz Allan';' - ... but got rows back. Number of rows returned was 1 - Run Keyword And Expect Error ${expected_error} Row Count Is 0 - ... SELECT * FROM person WHERE first_name = 'Franz Allan'; - -Verify Row Count Is 0 Fails With Message - [Tags] db smoke - Run Keyword And Expect Error my error message Row Count Is 0 - ... SELECT * FROM person WHERE first_name = 'Franz Allan'; msg=my error message - -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 Equal To X Fails - [Tags] db smoke - ${expected_error} = Catenate - ... Expected same number of rows to be returned from 'SELECT id FROM person;' - ... than the returned rows of 2 - Run Keyword And Expect Error ${expected_error} - ... Row Count Is Equal To X SELECT id FROM person; 3 - -Verify Row Count Is Equal To X Fails With Message - [Tags] db smoke - Run Keyword And Expect Error my error message Row Count Is Equal To X - ... SELECT id FROM person; 3 msg=my error message - -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 Less Than X Fails - [Tags] db smoke - ${expected_error} = Catenate - ... Expected less rows to be returned from 'SELECT id FROM person;' - ... than the returned rows of 2 - Run Keyword And Expect Error ${expected_error} Row Count Is Less Than X - ... SELECT id FROM person; 2 - -Verify Row Count Is Less Than X Fails With Message - [Tags] db smoke - Run Keyword And Expect Error my error message Row Count Is Less Than X - ... SELECT id FROM person; 2 msg=my error message - -Verify Row Count Is Greater Than X - [Tags] db smoke - Row Count Is Greater Than X SELECT * FROM person; 1 - -Verify Row Count Is Greater Than X Fails - [Tags] db smoke - ${expected_error} = Catenate - ... Expected more rows to be returned from 'SELECT * FROM person;' - ... than the returned rows of 2 - Run Keyword And Expect Error ${expected_error} - ... Row Count Is Greater Than X SELECT * FROM person; 3 - -Verify Row Count Is Greater Than X Fails With Message - [Tags] db smoke - Run Keyword And Expect Error my error message Row Count Is Greater Than X - ... SELECT * FROM person; 3 msg=my error message - -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/tests/_old/Teradata_DB_Tests.robot b/test/tests/_old/Teradata_DB_Tests.robot deleted file mode 100644 index dab7795..0000000 --- a/test/tests/_old/Teradata_DB_Tests.robot +++ /dev/null @@ -1,159 +0,0 @@ -*** Settings *** -Library DatabaseLibrary -Library OperatingSystem -Library Collections - -Suite Setup Connect To Database teradata ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} -Suite Teardown Disconnect From Database - - -*** Variables *** -${DBHost} 192.168.0.231 -${DBName} db -${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 ${CURDIR}/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/tests/_old/excel_old.robot b/test/tests/_old/excel_old.robot deleted file mode 100644 index 85e41cc..0000000 --- a/test/tests/_old/excel_old.robot +++ /dev/null @@ -1,294 +0,0 @@ -*** Settings *** -Suite Setup Setup testing excel -Suite Teardown Cleanup testing excel -Library DatabaseLibrary -Library OperatingSystem -Library ExcelLibrary - -*** Variables *** -${DBHost} dummy -${DBName} ${EXECDIR}/test/Test_Excel.xls -${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)); - 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 ${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) - ${output} = Set Variable ${queryResults[1]} - 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) - ${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) - ${output} = Set Variable ${queryResults[1]} - 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, )] - -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, )] - - -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} - - -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) - ${output} = Set Variable ${queryResults[1]} - 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) - ${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) - ${output} = Set Variable ${queryResults[1]} - 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, )] - -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, )] - -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 - -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, )] - -Setup RW access to excel - Disconnect From Database - 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 Document Test_Excel - Connect To Database excelrw ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} - -Cleanup testing excel - Disconnect From Database - Remove File ${DBName} diff --git a/test/tests/_old/my_db_test_insertData.sql b/test/tests/_old/my_db_test_insertData.sql deleted file mode 100644 index 566fd1b..0000000 --- a/test/tests/_old/my_db_test_insertData.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO person VALUES(1,'Franz Allan','See'); -INSERT INTO person VALUES(2,'Jerry','Schneider'); From e324bf6d60c38d1a921514dac95da61b00ce7565 Mon Sep 17 00:00:00 2001 From: amochin Date: Sat, 15 Feb 2025 19:05:54 +0200 Subject: [PATCH 258/266] Put some more info in test readme --- test/readme.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/readme.md b/test/readme.md index 41e7175..96870c4 100644 --- a/test/readme.md +++ b/test/readme.md @@ -1,5 +1,17 @@ -# Some tests are run automatically in the pipeline after pushing in the repository -See the folder `.github/workflows` +# 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 From 9cbed65f48a62b0d52abc03387e45b0c23e4dcc9 Mon Sep 17 00:00:00 2001 From: amochin Date: Mon, 17 Feb 2025 11:25:25 +0100 Subject: [PATCH 259/266] Delete old readme about pipeline tests --- .github/workflows/README.rst | 71 ------------------------------------ 1 file changed, 71 deletions(-) delete mode 100644 .github/workflows/README.rst diff --git a/.github/workflows/README.rst b/.github/workflows/README.rst deleted file mode 100644 index 8db0654..0000000 --- a/.github/workflows/README.rst +++ /dev/null @@ -1,71 +0,0 @@ -Test Framework for the DatabaseLibrary using Github Actions -=========================================================== - -.. contents:: - -!! Work In Progress !! ----------------------- -Note at this time this GitHub Actions CI workflow is a work in progress. I've been trying -to build a "complete" set of database and databse module suites. This notes, which are also -a work in progress, are here to help guide you and remind us of where we are at. - -About this test framework -------------------------- -As I re-exmine using GitHub Actions and expanding the internal test suite, I am reminded -of why creating a test suite with GitHub Actions has been difficult. The number of modules -currently supported (by code) is between nine and twelve. The number of databases known to -be supported by Python is over thirty. So the possibly test matrix for the library is very, -very, large. - -Given the possibilities we will start with looking from the perspective of Python modules -(as compared to databases) and limit it to those nine currently supported by test code. - -Database Systems and Python Modules ----------------------------- - -There are a variety of database systems and Python modules that the DatabaseLibrary supports. This -chart is intended to keep track of those implemented and resources around them. - - -================================== =========== ========================== ======================================= - Database Systems module Status Workflow -================================== =========== ========================== ======================================= -MySQL pymysql Completed common_tests.yml -\ pyodbc Completed common_tests.yml -PostgreSQL psycopg2 Completed common_tests.yml -\ psycopg3 Not Yet Implemented -\ pyodbc Not Yet Implemented -SQLite sqlite3 Completed common_tests.yml -Oracle - "custom params" oracledb Workflow is done, common_tests.yml - but some tests are failing - bugs have to be fixed - in the library, - tests are to be checked - and probably extended -Teradata Teradata Can be tested locally only, local only - as it requires a VM -Excel pyodbc Currentyl local tests only, local only - as I wasn't able to install - the ODBC driver for Excel - in the container -IBM DB2 ibmdb Currently local tests only, local only - as I wasn't able to get - the container working in - the workflow -================================== =========== ========================== ======================================= - - -================================== =========== ========================== ======================================= - Database module ..something.. Comment -================================== =========== ========================== ======================================= -Sub-Etha h2g2 The Hitchhiker's Database to the Galaxy -================================== =========== ========================== ======================================= - - -References: - -`PEP 249 - Python Database API Specification v2.0`_ - -`Database interfaces available for Python`_ - -Docker container with Oracle DB: https://github.com/gvenzl/oci-oracle-free From e403ef40be58a5673beef561b1e65c2506df1b1c Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Feb 2025 10:44:25 +0100 Subject: [PATCH 260/266] Fix #237 - commit even after simple query --- README.md | 7 +++---- src/DatabaseLibrary/__init__.py | 7 +++---- src/DatabaseLibrary/query.py | 9 ++++++--- test/tests/common_tests/basic_tests.robot | 8 -------- test/tests/common_tests/connection_params.robot | 2 +- .../db_update_in_background_commit.robot | 17 +++++++++++++++++ 6 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 test/tests/custom_db_tests/db_update_in_background_commit.robot diff --git a/README.md b/README.md index 1ced8ee..6981910 100644 --- a/README.md +++ b/README.md @@ -401,11 +401,10 @@ Library DatabaseLibrary log_query_results_head=0 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 must contain a dedicated commit statement, if necessary. +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 `Execute SQL String` perform automatically a commit after running the query - or a rollback in case of error -- Keywords like `Query` don't perform a commit, but also do a rollback in case of error +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. diff --git a/src/DatabaseLibrary/__init__.py b/src/DatabaseLibrary/__init__.py index 2741393..8b1ef85 100644 --- a/src/DatabaseLibrary/__init__.py +++ b/src/DatabaseLibrary/__init__.py @@ -356,11 +356,10 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion): 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 must contain a dedicated commit statement, if necessary. + 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 `Execute SQL String` perform automatically a commit after running the query - or a rollback in case of error - - Keywords like `Query` don't perform a commit, but also do a rollback in case of error + 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. diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 70b2680..3649203 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -54,7 +54,7 @@ def query( The type of row values depends on the database module - usually they are tuples or tuple-like objects. - Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + Set ``no_transaction`` to _True_ to run command without explicit transaction commit or rollback in case of error. See `Commit behavior` for details. Set ``return_dict`` to _True_ to explicitly convert the return values into list of dictionaries. @@ -89,6 +89,7 @@ def query( 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: @@ -111,7 +112,7 @@ def row_count( """ Runs a query with the ``select_statement`` and returns the number of rows in the result. - Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + 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`. @@ -143,6 +144,7 @@ def row_count( omit_trailing_semicolon=db_connection.omit_trailing_semicolon, ) data = cur.fetchall() + 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) @@ -168,7 +170,7 @@ def description( """ Runs a query with the ``select_statement`` to determine the table description. - Set ``no_transaction`` to _True_ to run command without explicit transaction rollback in case of error. + 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`. @@ -199,6 +201,7 @@ def description( 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)): diff --git a/test/tests/common_tests/basic_tests.robot b/test/tests/common_tests/basic_tests.robot index 6004661..adbd93e 100644 --- a/test/tests/common_tests/basic_tests.robot +++ b/test/tests/common_tests/basic_tests.robot @@ -157,11 +157,3 @@ 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 - -For Loop - FOR ${i} IN RANGE 10 - ${results}= Query SELECT LAST_NAME FROM person ORDER BY id - Sleep 3s - IF '${results}[0][0]' == 'Musk' BREAK - END - Should Be Equal As Strings ${results}[0][0] Musk \ No newline at end of file diff --git a/test/tests/common_tests/connection_params.robot b/test/tests/common_tests/connection_params.robot index 7e88a13..7fb585d 100644 --- a/test/tests/common_tests/connection_params.robot +++ b/test/tests/common_tests/connection_params.robot @@ -19,7 +19,7 @@ Test Teardown Disconnect From Database ... invalid custom param=TypeError: connect() got an unexpected keyword argument 'blah' &{Errors pymysql} ... missing basic params=OperationalError: (1045, "Access denied* -... invalid custom param=TypeError: __init__() got an unexpected keyword argument 'blah' +... invalid custom param=TypeError: Connection.__init__() got an unexpected keyword argument 'blah' &{Errors pyodbc} ... missing basic params=REGEXP: InterfaceError.*Data source name not found and no default driver specified.* 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 0000000..28ff999 --- /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 From 2b9b7c038b03fad1bfca4ab03d8f481cf72627b9 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Feb 2025 10:46:27 +0100 Subject: [PATCH 261/266] Bump version to 2.1.3 --- src/DatabaseLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index b777579..4260069 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "2.1.2" +VERSION = "2.1.3" From ba5a8013e80684258f4afa8a37d57e2badfbcac7 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Feb 2025 10:47:47 +0100 Subject: [PATCH 262/266] 2.1.3 - update docs --- doc/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.html b/doc/index.html index 54ea42e..266ef9c 100644 --- a/doc/index.html +++ b/doc/index.html @@ -8,7 +8,7 @@ From 9aa14a4f194ab9bcb26e4787c643e471f9ac9bc7 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 20 Feb 2025 11:22:30 +0100 Subject: [PATCH 263/266] Fix pymysql tests --- test/tests/common_tests/connection_params.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/common_tests/connection_params.robot b/test/tests/common_tests/connection_params.robot index 7fb585d..75f59ab 100644 --- a/test/tests/common_tests/connection_params.robot +++ b/test/tests/common_tests/connection_params.robot @@ -19,7 +19,7 @@ Test Teardown Disconnect From Database ... invalid custom param=TypeError: connect() got an unexpected keyword argument 'blah' &{Errors pymysql} ... missing basic params=OperationalError: (1045, "Access denied* -... invalid custom param=TypeError: Connection.__init__() got an unexpected keyword argument 'blah' +... 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.* From 1cb38a5699afb80be91a0a1c7965bd31009eb15d Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 26 Jun 2025 14:10:52 +0200 Subject: [PATCH 264/266] fix #244 - error when querying BLOB data --- src/DatabaseLibrary/query.py | 6 +++- test/tests/custom_db_tests/oracle_blob.robot | 29 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 test/tests/custom_db_tests/oracle_blob.robot diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 3649203..39a1222 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -827,7 +827,11 @@ def _log_query_results(self, col_names, result_rows, log_head: Optional[int] = N msg += f"" msg += f'
' for cell in row: - msg += f'' + try: + cell_string = str(cell) + except TypeError as e: + cell_string = f"Unable printing the value: {e}" + msg += f'' msg += "" msg += "
Query returned {len(result_rows)} rows
RowRow{col}
{i}{i}{cell}
{i}{cell}{cell_string}
" if table_truncated: diff --git a/test/tests/custom_db_tests/oracle_blob.robot b/test/tests/custom_db_tests/oracle_blob.robot new file mode 100644 index 0000000..aae973c --- /dev/null +++ b/test/tests/custom_db_tests/oracle_blob.robot @@ -0,0 +1,29 @@ +*** Settings *** +Documentation Tests for querying a table with BLOB data type. +... The data type naming is DB specific, these tests are designed for Oracle DB only. + +Resource ../../resources/common.resource + +Suite Setup Connect To DB +Suite Teardown Disconnect From Database +Test Setup Execute Sql String +... CREATE TABLE blob_table (id integer not null unique, data blob) +Test Teardown Execute Sql String DROP TABLE blob_table + + +*** 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 *** +Blob Data Type - Logging Results Causes No Error + [Documentation] See https://github.com/MarketSquare/Robotframework-Database-Library/issues/244 + ${binary_data}= Evaluate b'abc' + Execute Sql String INSERT INTO blob_table VALUES(1, '${binary_data}') + ${result}= Query SELECT data FROM blob_table WHERE id=1 From 8f475a8aca6800f73e79bf3ba84f39042039c2c7 Mon Sep 17 00:00:00 2001 From: amochin Date: Thu, 26 Jun 2025 14:11:44 +0200 Subject: [PATCH 265/266] Bump version to 2.1.4 --- src/DatabaseLibrary/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseLibrary/version.py b/src/DatabaseLibrary/version.py index 4260069..5dfae46 100644 --- a/src/DatabaseLibrary/version.py +++ b/src/DatabaseLibrary/version.py @@ -1 +1 @@ -VERSION = "2.1.3" +VERSION = "2.1.4" From afdeb6de01e896d1c7917286aae243b9d5d2bbb9 Mon Sep 17 00:00:00 2001 From: amochin Date: Fri, 27 Jun 2025 11:09:20 +0200 Subject: [PATCH 266/266] fix #243 - parsing SQL scripts with semicolons and escaped single quotes --- src/DatabaseLibrary/query.py | 1 - .../script_file_tests/semicolons_and_quotes_in_values.sql | 3 ++- test/tests/common_tests/script_files.robot | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/DatabaseLibrary/query.py b/src/DatabaseLibrary/query.py index 3649203..0c68f99 100644 --- a/src/DatabaseLibrary/query.py +++ b/src/DatabaseLibrary/query.py @@ -346,7 +346,6 @@ def execute_sql_script( # 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 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 index d5a846a..1a9813b 100644 --- a/test/resources/script_file_tests/semicolons_and_quotes_in_values.sql +++ b/test/resources/script_file_tests/semicolons_and_quotes_in_values.sql @@ -1 +1,2 @@ -INSERT INTO person VALUES(5, 'Miles', 'O''Brian'); \ No newline at end of file +INSERT INTO person VALUES(5, 'Miles', 'O''Brian'); +INSERT INTO person VALUES(6, 'Keiko', 'O''Brian'); diff --git a/test/tests/common_tests/script_files.robot b/test/tests/common_tests/script_files.robot index 2d067ec..09d3ae0 100644 --- a/test/tests/common_tests/script_files.robot +++ b/test/tests/common_tests/script_files.robot @@ -29,10 +29,11 @@ Semicolons In Values Semicolons And Quotes In Values Run SQL Script File semicolons_and_quotes_in_values ${sql}= Catenate select * from person - ... where id=5 + ... where LAST_NAME='O''Brian' ${results}= Query ${sql} - Length Should Be ${results} 1 + Length Should Be ${results} 2 Should Be Equal As Strings ${results}[0] (5, 'Miles', "O'Brian") + Should Be Equal As Strings ${results}[1] (6, 'Keiko', "O'Brian") *** Keywords ***