From bd9f806fff862beb25f48fd1ab1288ee5ea0218d Mon Sep 17 00:00:00 2001 From: Earamma K Date: Thu, 7 May 2026 19:23:40 +0530 Subject: [PATCH] Add asyncio support, cursor enhancements, test runner refactor and documentation Signed-off-by: Earamma K --- ASYNC_APIs_WIKI.md | 1136 +++++++++++++++++ README.md | 33 +- asyncio_testsuite/test_01_async_connect.py | 50 + asyncio_testsuite/test_02_async_pconnect.py | 46 + .../test_03_async_connection_class.py | 44 + .../test_04_async_context_manager.py | 59 + asyncio_testsuite/test_05_async_fetch.py | 59 + asyncio_testsuite/test_06_async_iteration.py | 65 + .../test_07_async_execute_params.py | 63 + .../test_08_async_prepare_bind_execute.py | 49 + asyncio_testsuite/test_09_async_callproc.py | 53 + .../test_10_async_bind_array_callproc.py | 69 + .../test_11_async_executemany.py | 62 + .../test_12_async_commit_rollback.py | 71 ++ asyncio_testsuite/test_13_async_autocommit.py | 47 + .../test_14_async_set_get_option.py | 49 + asyncio_testsuite/test_15_async_schema.py | 55 + .../test_16_async_server_info.py | 38 + asyncio_testsuite/test_17_async_metadata.py | 107 ++ .../test_18_async_fix_return_type.py | 67 + asyncio_testsuite/test_19_async_dml.py | 80 ++ asyncio_testsuite/test_20_async_nextset.py | 107 ++ .../test_21_async_error_handling.py | 91 ++ .../test_22_async_scalar_bind_callproc.py | 64 + .../test_23_async_concurrent_queries.py | 63 + .../test_24_async_error_functions.py | 54 + .../test_25_async_createdb_dropdb.py | 104 ++ .../test_26_async_inout_param.py | 62 + .../test_27_async_bind_datatypes.py | 83 ++ .../test_28_async_cursor_description.py | 71 ++ asyncio_testsuite/test_29_async_reprepare.py | 62 + asyncio_testsuite/test_30_sync_vs_async.py | 97 ++ .../test_31_async_sparray_cardinalities.py | 274 ++++ .../test_32_async_sparray_computations.py | 274 ++++ ...est_33_async_sparray_inout_computations.py | 354 +++++ ...async_sparray_input_output_computations.py | 355 ++++++ .../test_35_async_scalarsp_execute.py | 195 +++ .../test_async_concurrent_mixed.py | 101 ++ ibm_db_ctx.py | 2 +- ibm_db_dbi.py | 414 +++++- ibmdb_tests.py | 77 +- run_all_tests | 4 +- run_individual_tests | 2 +- testfunctions.py | 84 +- 44 files changed, 5209 insertions(+), 87 deletions(-) create mode 100644 ASYNC_APIs_WIKI.md create mode 100644 asyncio_testsuite/test_01_async_connect.py create mode 100644 asyncio_testsuite/test_02_async_pconnect.py create mode 100644 asyncio_testsuite/test_03_async_connection_class.py create mode 100644 asyncio_testsuite/test_04_async_context_manager.py create mode 100644 asyncio_testsuite/test_05_async_fetch.py create mode 100644 asyncio_testsuite/test_06_async_iteration.py create mode 100644 asyncio_testsuite/test_07_async_execute_params.py create mode 100644 asyncio_testsuite/test_08_async_prepare_bind_execute.py create mode 100644 asyncio_testsuite/test_09_async_callproc.py create mode 100644 asyncio_testsuite/test_10_async_bind_array_callproc.py create mode 100644 asyncio_testsuite/test_11_async_executemany.py create mode 100644 asyncio_testsuite/test_12_async_commit_rollback.py create mode 100644 asyncio_testsuite/test_13_async_autocommit.py create mode 100644 asyncio_testsuite/test_14_async_set_get_option.py create mode 100644 asyncio_testsuite/test_15_async_schema.py create mode 100644 asyncio_testsuite/test_16_async_server_info.py create mode 100644 asyncio_testsuite/test_17_async_metadata.py create mode 100644 asyncio_testsuite/test_18_async_fix_return_type.py create mode 100644 asyncio_testsuite/test_19_async_dml.py create mode 100644 asyncio_testsuite/test_20_async_nextset.py create mode 100644 asyncio_testsuite/test_21_async_error_handling.py create mode 100644 asyncio_testsuite/test_22_async_scalar_bind_callproc.py create mode 100644 asyncio_testsuite/test_23_async_concurrent_queries.py create mode 100644 asyncio_testsuite/test_24_async_error_functions.py create mode 100644 asyncio_testsuite/test_25_async_createdb_dropdb.py create mode 100644 asyncio_testsuite/test_26_async_inout_param.py create mode 100644 asyncio_testsuite/test_27_async_bind_datatypes.py create mode 100644 asyncio_testsuite/test_28_async_cursor_description.py create mode 100644 asyncio_testsuite/test_29_async_reprepare.py create mode 100644 asyncio_testsuite/test_30_sync_vs_async.py create mode 100644 asyncio_testsuite/test_31_async_sparray_cardinalities.py create mode 100644 asyncio_testsuite/test_32_async_sparray_computations.py create mode 100644 asyncio_testsuite/test_33_async_sparray_inout_computations.py create mode 100644 asyncio_testsuite/test_34_async_sparray_input_output_computations.py create mode 100644 asyncio_testsuite/test_35_async_scalarsp_execute.py create mode 100644 asyncio_testsuite/test_async_concurrent_mixed.py diff --git a/ASYNC_APIs_WIKI.md b/ASYNC_APIs_WIKI.md new file mode 100644 index 00000000..6090d5e9 --- /dev/null +++ b/ASYNC_APIs_WIKI.md @@ -0,0 +1,1136 @@ +# Async (asyncio) APIs for ibm_db_dbi + +The `ibm_db_dbi` module provides full `asyncio` support through `AsyncConnection`, `AsyncCursor`, and module-level async functions. All async operations use `asyncio.to_thread()` internally, making the synchronous IBM CLI calls non-blocking in an async event loop. + +> **Note:** All examples use `await` at the top level for brevity. In a script, wrap your code in `async def main()` and call `asyncio.run(main())`. For interactive testing, use `python -m asyncio` to start the asyncio REPL, which supports top-level `await`. The regular `python` REPL does not support `await` outside a function. + +## Table of Contents + +- [Module-Level Async Functions](#module-level-async-functions) + - [connect_async](#connect_async) + - [pconnect_async](#pconnect_async) + - [conn_errormsg_async](#conn_errormsg_async) + - [conn_error_async](#conn_error_async) + - [get_sqlcode_async](#get_sqlcode_async) + - [createdb_async](#createdb_async) + - [dropdb_async](#dropdb_async) + - [recreatedb_async](#recreatedb_async) + - [createdbNX_async](#createdbnx_async) +- [AsyncConnection](#asyncconnection) + - [AsyncConnection.connect](#asyncconnectionconnect) + - [AsyncConnection.close](#asyncconnectionclose) + - [AsyncConnection.commit](#asyncconnectioncommit) + - [AsyncConnection.rollback](#asyncconnectionrollback) + - [AsyncConnection.cursor](#asyncconnectioncursor) + - [AsyncConnection.set_autocommit](#asyncconnectionset_autocommit) + - [AsyncConnection.set_option / get_option](#asyncconnectionset_option--get_option) + - [AsyncConnection.set_current_schema / get_current_schema](#asyncconnectionset_current_schema--get_current_schema) + - [AsyncConnection.server_info](#asyncconnectionserver_info) + - [AsyncConnection.set_fix_return_type](#asyncconnectionset_fix_return_type) + - [AsyncConnection metadata methods](#asyncconnection-metadata-methods) + - [AsyncConnection context manager](#asyncconnection-context-manager) +- [AsyncCursor](#asynccursor) + - [AsyncCursor.execute](#asynccursorexecute) + - [AsyncCursor.executemany](#asynccursorexecutemany) + - [AsyncCursor.prepare](#asynccursorprepare) + - [AsyncCursor.bind_param](#asynccursorbind_param) + - [AsyncCursor.fetchone](#asynccursorfetchone) + - [AsyncCursor.fetchmany](#asynccursorfetchmany) + - [AsyncCursor.fetchall](#asynccursorfetchall) + - [AsyncCursor.fetch_tuple](#asynccursorfetch_tuple) + - [AsyncCursor.callproc](#asynccursorcallproc) + - [AsyncCursor.fetch_callproc](#asynccursorfetch_callproc) + - [AsyncCursor.nextset](#asynccursornextset) + - [AsyncCursor.close](#asynccursorclose) + - [AsyncCursor.stmt_errormsg / stmt_error](#asynccursorstmt_errormsg--stmt_error) + - [AsyncCursor.description / rowcount](#asynccursordescription--rowcount) + - [AsyncCursor context manager](#asynccursor-context-manager) +- [Stored Procedure Patterns](#stored-procedure-patterns) +- [Concurrent Queries with asyncio.gather](#concurrent-queries-with-asynciogather) + +## Module-Level Async Functions + +### connect_async + +`Connection await ibm_db_dbi.connect_async(string dsn, string user, string password [, string host [, string database [, dict conn_options]]])` + +**Description** + +Async wrapper around `ibm_db_dbi.connect()`. Creates a connection asynchronously using `asyncio.to_thread()` and returns a **sync** `Connection` object. + +> **Note:** Use `AsyncConnection.connect()` instead if you want a fully async connection and cursor lifecycle. + +**Parameters** + +- `dsn` - A connection string (e.g., `"DATABASE=sample;HOSTNAME=host;PORT=50000;PROTOCOL=TCPIP"`) +- `user` - The username for authentication +- `password` - The password for authentication +- `host` - (optional) Hostname +- `database` - (optional) Database name +- `conn_options` - (optional) A dict of connection options + +> For full details on connection parameters, DSN formats, JWT access tokens, and connection options (`SQL_ATTR_*`), refer to [ibm_db.connect](https://github.com/ibmdb/python-ibmdb/wiki/APIs#ibm_dbconnect). + +**Return Values** + +- On success, a sync `Connection` object +- On failure, raises an exception + +**Example** + +```python +import asyncio +import ibm_db_dbi + +async def main(): + dsn = "DATABASE=sample;HOSTNAME=host.example.com;PORT=50000;PROTOCOL=TCPIP" + conn = await ibm_db_dbi.connect_async(dsn, "user", "password") + # conn is a sync Connection object + cursor = conn.cursor() + cursor.execute("SELECT 1 FROM SYSIBM.SYSDUMMY1") + print(cursor.fetchone()) + conn.close() + +asyncio.run(main()) +``` + +### pconnect_async + +`Connection await ibm_db_dbi.pconnect_async(string dsn, string user, string password [, string host [, string database [, dict conn_options]]])` + +**Description** + +Async wrapper around `ibm_db_dbi.pconnect()`. Returns a persistent **sync** `Connection`. Persistent connections are reused from a process-wide connection pool when `ibm_db.close()` is called. + +**Parameters** + +Same as [`connect_async`](#connect_async). For full details on connection parameters, refer to [ibm_db.pconnect](https://github.com/ibmdb/python-ibmdb/wiki/APIs#ibm_dbpconnect). + +**Return Values** + +- On success, a sync `Connection` object (persistent) +- On failure, raises an exception + +**Example** + +```python +import asyncio +import ibm_db_dbi + +async def main(): + dsn = "DATABASE=sample;HOSTNAME=host.example.com;PORT=50000;PROTOCOL=TCPIP" + conn = await ibm_db_dbi.pconnect_async(dsn, "user", "password") + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) FROM STAFF") + print(cursor.fetchone()) + conn.close() # returns to pool, not actually closed + +asyncio.run(main()) +``` + +### conn_errormsg_async + +`string await ibm_db_dbi.conn_errormsg_async([Connection connection])` + +**Description** + +Async wrapper around `ibm_db.conn_errormsg()`. Returns the SQLCODE and error message for the last failed connection operation. + +**Parameters** + +- `connection` - (optional) A valid connection handler + +**Return Values** + +Returns a string containing the error message or an empty string if there was no error. + +**Example** + +```python +import asyncio +import ibm_db_dbi + +async def main(): + try: + conn = await ibm_db_dbi.connect_async( + "DATABASE=sample;HOSTNAME=host;PORT=50000;PROTOCOL=TCPIP", + "invalid_user", "invalid_pwd") + except Exception: + msg = await ibm_db_dbi.conn_errormsg_async() + print("Connection error:", msg) + +asyncio.run(main()) +``` + +### conn_error_async + +`string await ibm_db_dbi.conn_error_async([Connection connection])` + +**Description** + +Async wrapper around `ibm_db.conn_error()`. Returns the SQLSTATE (5-character string) for the last failed connection operation. + +**Parameters** + +- `connection` - (optional) A valid connection handler + +**Return Values** + +Returns a string containing the SQLSTATE or an empty string if there was no error. + +**Example** + +```python +import asyncio +import ibm_db_dbi + +async def main(): + try: + conn = await ibm_db_dbi.connect_async( + "DATABASE=sample;HOSTNAME=host;PORT=50000;PROTOCOL=TCPIP", + "invalid_user", "invalid_pwd") + except Exception: + sqlstate = await ibm_db_dbi.conn_error_async() + print("SQLSTATE:", sqlstate) + +asyncio.run(main()) +``` + +### get_sqlcode_async + +`string await ibm_db_dbi.get_sqlcode_async([Connection connection] / [Cursor cursor])` + +**Description** + +Async wrapper around `ibm_db.get_sqlcode()`. Returns the SQLCODE for the last failed operation. + +**Parameters** + +- `handle` - (optional) A valid connection or statement handler + +**Return Values** + +Returns a string containing the SQLCODE or an empty string if there was no error. + +**Example** + +```python +import asyncio +import ibm_db_dbi + +async def main(): + try: + conn = await ibm_db_dbi.connect_async( + "DATABASE=sample;HOSTNAME=host;PORT=50000;PROTOCOL=TCPIP", + "invalid_user", "invalid_pwd") + except Exception: + sqlcode = await ibm_db_dbi.get_sqlcode_async() + print("SQLCODE:", sqlcode) + +asyncio.run(main()) +``` + +### createdb_async + +`bool await ibm_db_dbi.createdb_async(string database, string dsn, string user, string password [, string host [, string codeset [, string mode]]])` + +**Description** + +Async wrapper around `ibm_db_dbi.createdb()`. Creates a database asynchronously. + +**Parameters** + +- `database` - Name of the database to create +- `dsn` - Connection string with `ATTACH=true` +- `user` - Username +- `password` - Password +- `host` - Hostname +- `codeset` - (optional) Database code set +- `mode` - (optional) Database logging mode + +**Return Values** + +Returns `True` on success, `None` on failure. + +**Example** + +```python +import asyncio +import ibm_db_dbi + +async def main(): + dsn = "ATTACH=true;HOSTNAME=host.example.com;PORT=50000;PROTOCOL=TCPIP" + rc = await ibm_db_dbi.createdb_async("TESTDB", dsn, "user", "password") + print("Created:", rc) + +asyncio.run(main()) +``` + +### dropdb_async + +`bool await ibm_db_dbi.dropdb_async(string database, string dsn, string user, string password [, string host])` + +**Description** + +Async wrapper around `ibm_db_dbi.dropdb()`. Drops a database asynchronously. + +**Parameters** + +- `database` - Name of the database to drop +- `dsn` - Connection string with `ATTACH=true` +- `user` - Username +- `password` - Password +- `host` - Hostname + +**Return Values** + +Returns `True` on success, `None` on failure. + +**Example** + +```python +import asyncio +import ibm_db_dbi + +async def main(): + dsn = "ATTACH=true;HOSTNAME=host.example.com;PORT=50000;PROTOCOL=TCPIP" + rc = await ibm_db_dbi.dropdb_async("TESTDB", dsn, "user", "password") + print("Dropped:", rc) + +asyncio.run(main()) +``` + +### recreatedb_async + +`bool await ibm_db_dbi.recreatedb_async(string database, string dsn, string user, string password [, string host [, string codeset [, string mode]]])` + +**Description** + +Async wrapper around `ibm_db_dbi.recreatedb()`. Drops and then recreates a database asynchronously. + +**Parameters** + +Same as `createdb_async`. + +**Return Values** + +Returns `True` on success, `None` on failure. + +### createdbNX_async + +`bool await ibm_db_dbi.createdbNX_async(string database, string dsn, string user, string password [, string host [, string codeset [, string mode]]])` + +**Description** + +Async wrapper around `ibm_db_dbi.createdbNX()`. Creates a database only if it does not already exist. + +**Parameters** + +Same as `createdb_async`. + +**Return Values** + +Returns `True` if database already exists or is created successfully, `None` on failure. + +## AsyncConnection + +`AsyncConnection` is a fully async connection class. All methods are coroutines. Obtain an instance via the `connect()` class method. + +### AsyncConnection.connect + +`AsyncConnection await AsyncConnection.connect(string dsn, string user, string password [, string host [, string database [, dict conn_options]]])` + +**Description** + +Class method that creates and returns an `AsyncConnection`. This is the recommended way to establish an async connection. + +**Parameters** + +- `dsn` - A connection string +- `user` - Username +- `password` - Password +- `host` - (optional) Hostname +- `database` - (optional) Database name +- `conn_options` - (optional) A dict of connection options + +**Return Values** + +Returns an `AsyncConnection` object. + +**Example** + +```python +import asyncio +from ibm_db_dbi import AsyncConnection + +async def main(): + dsn = "DATABASE=sample;HOSTNAME=host.example.com;PORT=50000;PROTOCOL=TCPIP" + conn = await AsyncConnection.connect(dsn, "user", "password") + + cursor = await conn.cursor() + await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 3 ROWS ONLY") + rows = await cursor.fetchall() + for row in rows: + print(row) + + await cursor.close() + await conn.close() + +asyncio.run(main()) +``` + +### AsyncConnection.close + +`await conn.close()` + +**Description** + +Closes the async connection. Rolls back any uncommitted transactions before closing. + +**Return Values** + +None. + +### AsyncConnection.commit + +`await conn.commit()` + +**Description** + +Commits the current transaction. + +**Return Values** + +None. + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +async def main(): + conn = await AsyncConnection.connect(dsn, user, password) + await conn.set_autocommit(False) + + cursor = await conn.cursor() + await cursor.execute("INSERT INTO STAFF (ID, NAME) VALUES (999, 'Test')") + await conn.commit() + + await cursor.close() + await conn.close() +``` + +### AsyncConnection.rollback + +`await conn.rollback()` + +**Description** + +Rolls back an in-progress transaction on the `AsyncConnection` and begins a new transaction. Python applications normally default to AUTOCOMMIT mode, so `rollback()` normally has no effect unless AUTOCOMMIT has been turned off for the connection. + +**Note:** If the `AsyncConnection` wraps a persistent connection (created via `pconnect_async`), all transactions in progress for all applications using that persistent connection will be rolled back. For this reason, persistent connections are not recommended for use in applications that require transactions. + +**Parameters** + +**Parameters** + +None. + +**Return Values** + +None. + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +async def main(): + conn = await AsyncConnection.connect(dsn, user, password) + await conn.set_autocommit(False) + + cursor = await conn.cursor() + await cursor.execute("DELETE FROM STAFF WHERE ID = 10") + await conn.rollback() # undo the delete + + await cursor.close() + await conn.close() +``` + +### AsyncConnection.cursor + +`AsyncCursor await conn.cursor()` + +**Description** + +Creates and returns an `AsyncCursor` bound to this connection. + +**Return Values** + +Returns an `AsyncCursor` object. + +### AsyncConnection.set_autocommit + +`await conn.set_autocommit(bool flag)` + +**Description** + +Enables or disables autocommit on the connection. + +**Parameters** + +- `flag` - `True` to enable autocommit, `False` to disable + +### AsyncConnection.set_option / get_option + +`await conn.set_option(dict options_dict)` +`mixed await conn.get_option(int option_key)` + +**Description** + +Sets or gets connection-level attributes. + +**Parameters** + +- `options_dict` - A dict of `{ibm_db_dbi.SQL_ATTR_*: value}` +- `option_key` - An `ibm_db_dbi.SQL_ATTR_*` constant + +**Example** + +```python +import ibm_db_dbi +from ibm_db_dbi import AsyncConnection + +conn = await AsyncConnection.connect(dsn, user, password) +await conn.set_option({ibm_db_dbi.SQL_ATTR_AUTOCOMMIT: ibm_db_dbi.SQL_AUTOCOMMIT_OFF}) +val = await conn.get_option(ibm_db_dbi.SQL_ATTR_AUTOCOMMIT) +print("Autocommit:", val) +``` + +### AsyncConnection.set_current_schema / get_current_schema + +`await conn.set_current_schema(string schema_name)` +`string await conn.get_current_schema()` + +**Description** + +Sets or gets the current schema for the connection. + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +conn = await AsyncConnection.connect(dsn, user, password) +await conn.set_current_schema("MYSCHEMA") +schema = await conn.get_current_schema() +print("Current schema:", schema) +``` + +### AsyncConnection.server_info + +`tuple await conn.server_info()` + +**Description** + +Returns a tuple of `(DBMS_NAME, DBMS_VER)` for the connected server. + +**Return Values** + +A tuple: `(dbms_name_string, dbms_ver_string)` + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +conn = await AsyncConnection.connect(dsn, user, password) +info = await conn.server_info() +print("Server:", info[0], "Version:", info[1]) +``` + +### AsyncConnection.set_fix_return_type + +`await conn.set_fix_return_type(bool flag)` + +**Description** + +Enables or disables `FIX_RETURN_TYPE`. When enabled, numeric columns return `Decimal` instead of string. + +**Parameters** + +- `flag` - `True` to enable, `False` to disable + +### AsyncConnection metadata methods + +`list await conn.tables([string schema_name [, string table_name]])` +`list await conn.columns([string schema_name [, string table_name [, string column_names]]])` +`list await conn.primary_keys([bool unique [, string schema_name [, string table_name]]])` +`list await conn.indexes([bool unique [, string schema_name [, string table_name]]])` +`list await conn.foreign_keys([bool unique [, string schema_name [, string table_name]]])` + +**Description** + +Query metadata about tables, columns, primary keys, indexes, and foreign keys. Each returns the result as fetched rows. + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +conn = await AsyncConnection.connect(dsn, user, password) + +# List tables +tables = await conn.tables(schema_name="DB2ADMIN", table_name="STAFF") +print(tables) + +# List columns +columns = await conn.columns(schema_name="DB2ADMIN", table_name="STAFF") +for col in columns: + print(col) + +# Primary keys +pkeys = await conn.primary_keys(schema_name="DB2ADMIN", table_name="STAFF") +print(pkeys) + +await conn.close() +``` + +### AsyncConnection context manager + +**Description** + +`AsyncConnection` supports `async with`. The connection is automatically closed when the block exits. + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +async with await AsyncConnection.connect(dsn, user, password) as conn: + cursor = await conn.cursor() + await cursor.execute("SELECT 1 FROM SYSIBM.SYSDUMMY1") + print(await cursor.fetchone()) +# connection auto-closed on exit +``` + +## AsyncCursor + +`AsyncCursor` is returned by `await conn.cursor()` on an `AsyncConnection`. All I/O methods are coroutines. + +### AsyncCursor.execute + +`await cursor.execute([string operation [, tuple parameters]])` + +**Description** + +Executes an SQL statement. Supports three calling patterns: +- `await cursor.execute(sql)` — execute a SQL string +- `await cursor.execute(sql, params)` — execute with parameter markers +- `await cursor.execute()` — execute a previously prepared statement (after `prepare` + `bind_param`) + +**Parameters** + +- `operation` - SQL statement string, or `None` to execute a prepared statement +- `parameters` - (optional) A tuple of parameter values for parameter markers (`?`) + +**Return Values** + +None. + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +conn = await AsyncConnection.connect(dsn, user, password) +cursor = await conn.cursor() + +# Simple query +await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 5 ROWS ONLY") +rows = await cursor.fetchall() +print(rows) + +# With parameter markers +await cursor.execute("SELECT ID, NAME FROM STAFF WHERE ID = ?", (10,)) +row = await cursor.fetchone() +print(row) + +await cursor.close() +await conn.close() +``` + +### AsyncCursor.executemany + +`await cursor.executemany(string operation, tuple seq_of_parameters)` + +**Description** + +Executes an SQL statement once for each set of parameters in the sequence. + +**Parameters** + +- `operation` - SQL statement string with parameter markers +- `seq_parameters` - A sequence of tuples, each containing parameter values + +**Example** + +```python +cursor = await conn.cursor() +await cursor.executemany( + "INSERT INTO STAFF (ID, NAME) VALUES (?, ?)", + ((901, 'Alice'), (902, 'Bob'), (903, 'Carol')) +) +print("Rows inserted:", cursor.rowcount) +``` + +### AsyncCursor.prepare + +`await cursor.prepare(string operation)` + +**Description** + +Prepares an SQL statement for later execution with `execute()`. Use with `bind_param()` for the prepare/bind/execute workflow. + +**Parameters** + +- `operation` - SQL statement string, optionally containing parameter markers (`?`) + +**Example** + +```python +cursor = await conn.cursor() +await cursor.prepare("SELECT ID, NAME, SALARY FROM STAFF WHERE ID = ?") +await cursor.bind_param(1, 20) +await cursor.execute() +row = await cursor.fetchone() +print(row) +``` + +### AsyncCursor.bind_param + +`await cursor.bind_param(int param_no, mixed value [, int param_type [, int data_type]])` + +**Description** + +Binds a parameter value to a prepared statement. + +**Parameters** + +- `index` - 1-based parameter position +- `value` - The Python value to bind (scalar or list for array parameters) +- `param_type` - (optional) `ibm_db.SQL_PARAM_INPUT`, `SQL_PARAM_OUTPUT`, or `SQL_PARAM_INPUT_OUTPUT` +- `data_type` - (optional) SQL data type constant (e.g. `ibm_db.SQL_INTEGER`, `ibm_db.SQL_VARCHAR`) + +**Example** + +```python +import ibm_db + +cursor = await conn.cursor() +await cursor.prepare("INSERT INTO STAFF (ID, NAME) VALUES (?, ?)") +await cursor.bind_param(1, 999) +await cursor.bind_param(2, "TestName") +await cursor.execute() +``` + +For output parameters: + +```python +await cursor.prepare("CALL MY_DOUBLE_PROC(?, ?)") +await cursor.bind_param(1, 42, ibm_db.SQL_PARAM_INPUT) +await cursor.bind_param(2, 0, ibm_db.SQL_PARAM_OUTPUT) +await cursor.execute() +result = await cursor.fetch_callproc() +print(result) # (stmt_handler, 42, 84) +``` + +### AsyncCursor.fetchone + +`tuple await cursor.fetchone()` + +**Description** + +Fetches the next row from the result set as a tuple, or `None` if no more rows. + +**Return Values** + +- A tuple containing the column values, or `None` + +**Example** + +```python +await cursor.execute("SELECT ID, NAME FROM STAFF") +row = await cursor.fetchone() +while row: + print(row) + row = await cursor.fetchone() +``` + +### AsyncCursor.fetchmany + +`list await cursor.fetchmany([int size])` + +**Description** + +Fetches up to `size` rows from the result set as a list of tuples. + +**Parameters** + +- `size` - (optional) Number of rows to fetch. Defaults to `cursor.arraysize`. + +**Return Values** + +A list of tuples. Empty list if no rows remain. + +**Example** + +```python +await cursor.execute("SELECT ID, NAME FROM STAFF") +rows = await cursor.fetchmany(3) +print(rows) +``` + +### AsyncCursor.fetchall + +`list await cursor.fetchall()` + +**Description** + +Fetches all remaining rows from the result set as a list of tuples. + +**Return Values** + +A list of tuples. Empty list if no rows remain. + +**Example** + +```python +await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 5 ROWS ONLY") +rows = await cursor.fetchall() +for row in rows: + print(row) +``` + +### AsyncCursor.fetch_tuple + +`tuple await cursor.fetch_tuple()` + +**Description** + +Fetches one row as a tuple from the current result set. This is an async equivalent of `ibm_db.fetch_tuple()`. + +**Return Values** + +- A tuple containing the column values for the next row +- `None` if there are no more rows + +**Example** + +```python +cursor = await conn.cursor() +await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 3 ROWS ONLY") +row = await cursor.fetch_tuple() +while row: + print(row) + row = await cursor.fetch_tuple() +await cursor.close() +``` + +### AsyncCursor.callproc + +`tuple await cursor.callproc(string procname [, tuple parameters])` + +**Description** + +Calls a stored procedure. Returns a tuple of the (possibly modified) parameters. OUT and INOUT parameters are updated with values returned by the database. + +**Parameters** + +- `procname` - Name of the stored procedure +- `parameters` - (optional) A tuple of IN/OUT/INOUT parameter values + +**Return Values** + +A tuple of parameter values (IN parameters unchanged, OUT/INOUT parameters updated). + +**Example** + +```python +cursor = await conn.cursor() + +# Suppose MY_DOUBLE_PROC takes IN int, OUT int and sets OUT = IN * 2 +result = await cursor.callproc("MY_DOUBLE_PROC", (42, 0)) +print(result) # (42, 84) +``` + +### AsyncCursor.fetch_callproc + +`tuple await cursor.fetch_callproc()` + +**Description** + +Fetches the output parameters returned by a stored procedure that was executed via the prepare/bind/execute path (not `callproc`). + +**Return Values** + +A tuple containing the statement handler followed by the parameter values. + +**Example** + +```python +import ibm_db + +cursor = await conn.cursor() +await cursor.prepare("CALL MY_DOUBLE_PROC(?, ?)") +await cursor.bind_param(1, 42, ibm_db.SQL_PARAM_INPUT) +await cursor.bind_param(2, 0, ibm_db.SQL_PARAM_OUTPUT) +await cursor.execute() + +result = await cursor.fetch_callproc() +print(result) # (stmt_handler, 42, 84) +``` + +For array parameters: + +```python +await cursor.prepare("CALL ARRAY_SUM_PROC(?, ?, ?)") +await cursor.bind_param(1, [10, 20, 30, 40, 50], ibm_db.SQL_PARAM_INPUT) +await cursor.bind_param(2, 0, ibm_db.SQL_PARAM_OUTPUT) +await cursor.bind_param(3, 5, ibm_db.SQL_PARAM_INPUT) +await cursor.execute() + +result = await cursor.fetch_callproc() +print(result) +``` + +### AsyncCursor.nextset + +`bool await cursor.nextset()` + +**Description** + +Advances to the next result set when a stored procedure returns multiple result sets. + +**Return Values** + +- `True` if there is another result set +- `None` if there are no more result sets + +**Example** + +```python +await cursor.callproc("MULTI_RESULTSET_PROC") + +# First result set +rows1 = await cursor.fetchall() +print("Result set 1:", rows1) + +# Advance to next +has_next = await cursor.nextset() +if has_next: + rows2 = await cursor.fetchall() + print("Result set 2:", rows2) +``` + +### AsyncCursor.close + +`await cursor.close()` + +**Description** + +Closes the cursor and releases associated resources. + +### AsyncCursor.stmt_errormsg / stmt_error + +`string await cursor.stmt_errormsg()` +`string await cursor.stmt_error()` + +**Description** + +Returns the error message or SQLSTATE for the last failed statement operation on this cursor. + +**Example** + +```python +cursor = await conn.cursor() +try: + await cursor.execute("SELECT * FROM NONEXISTENT_TABLE") +except Exception: + print("Error:", await cursor.stmt_errormsg()) + print("SQLSTATE:", await cursor.stmt_error()) +``` + +### AsyncCursor.description / rowcount + +**Description** + +These are **sync properties** (no `await` needed): + +- `cursor.description` — Column metadata after execute (list of 7-tuples per DB-API spec), or `None` +- `cursor.rowcount` — Number of rows affected by the last DML statement, or `-1` + +**Example** + +```python +await cursor.execute("SELECT ID, NAME, SALARY FROM STAFF FETCH FIRST 1 ROW ONLY") +for col in cursor.description: + print(col[0], col[1]) # column name, type object + +await cursor.execute("UPDATE STAFF SET SALARY = SALARY + 100 WHERE ID = 10") +print("Rows updated:", cursor.rowcount) +``` + +### AsyncCursor context manager + +**Description** + +`AsyncCursor` supports `async with`. The cursor is automatically closed when the block exits. + +**Example** + +```python +from ibm_db_dbi import AsyncConnection + +conn = await AsyncConnection.connect(dsn, user, password) +cursor = await conn.cursor() +async with cursor: + await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 3 ROWS ONLY") + rows = await cursor.fetchall() + for row in rows: + print(row) +# cursor auto-closed on exit +``` + +## Stored Procedure Patterns + +### callproc (simple IN/OUT) + +```python +cursor = await conn.cursor() +result = await cursor.callproc("MY_DOUBLE_PROC", (42, 0)) +print(result) # (42, 84) +``` + +### prepare + bind_param + fetch_callproc (scalar) + +```python +import ibm_db + +cursor = await conn.cursor() +await cursor.prepare("CALL MY_DOUBLE_PROC(?, ?)") +await cursor.bind_param(1, 42, ibm_db.SQL_PARAM_INPUT) +await cursor.bind_param(2, 0, ibm_db.SQL_PARAM_OUTPUT) +await cursor.execute() +result = await cursor.fetch_callproc() +print(result) # (stmt_handler, 42, 84) +``` + +### Array bind + fetch_callproc + +```python +await cursor.prepare("CALL ARRAY_SUM_PROC(?, ?, ?)") +await cursor.bind_param(1, [10, 20, 30, 40, 50], ibm_db.SQL_PARAM_INPUT) +await cursor.bind_param(2, 0, ibm_db.SQL_PARAM_OUTPUT) +await cursor.bind_param(3, 5, ibm_db.SQL_PARAM_INPUT) +await cursor.execute() +result = await cursor.fetch_callproc() +``` + +### INOUT parameter + +```python +await cursor.prepare("CALL ADD_100(?)") +await cursor.bind_param(1, 42, ibm_db.SQL_PARAM_INPUT_OUTPUT) +await cursor.execute() +result = await cursor.fetch_callproc() +print(result) # (stmt_handler, 142) +``` + +### Multiple result sets (nextset) + +```python +await cursor.callproc("MULTI_RESULTSET_PROC") + +# First result set +rows1 = await cursor.fetchall() + +# Advance to next +has_next = await cursor.nextset() +if has_next: + rows2 = await cursor.fetchall() +``` + +## Concurrent Queries with asyncio.gather + +Since all async methods use `asyncio.to_thread()`, multiple queries can run concurrently using `asyncio.gather()`. + +> **Note:** Each concurrent query should use its own cursor. + +**Example** + +```python +import asyncio +from ibm_db_dbi import AsyncConnection + +async def query(conn, staff_id): + cur = await conn.cursor() + await cur.execute("SELECT ID, NAME FROM STAFF WHERE ID = ?", (staff_id,)) + row = await cur.fetchone() + await cur.close() + return row + +async def main(): + dsn = "DATABASE=sample;HOSTNAME=host.example.com;PORT=50000;PROTOCOL=TCPIP" + conn = await AsyncConnection.connect(dsn, "user", "password") + + results = await asyncio.gather( + query(conn, 10), + query(conn, 20), + query(conn, 30), + ) + for r in results: + print(r) + + await conn.close() + +asyncio.run(main()) +``` + +## Complete Example Script + +```python +import asyncio +from ibm_db_dbi import AsyncConnection + +async def main(): + dsn = "DATABASE=sample;HOSTNAME=host.example.com;PORT=50000;PROTOCOL=TCPIP" + + async with await AsyncConnection.connect(dsn, "user", "password") as conn: + # Server info + info = await conn.server_info() + print("Connected to:", info[0], info[1]) + + # Query + cursor = await conn.cursor() + await cursor.execute("SELECT ID, NAME, SALARY FROM STAFF FETCH FIRST 5 ROWS ONLY") + rows = await cursor.fetchall() + for row in rows: + print(row) + + # Prepare + bind + execute + await cursor.prepare("SELECT NAME FROM STAFF WHERE ID = ?") + await cursor.bind_param(1, 10) + await cursor.execute() + print("Staff 10:", await cursor.fetchone()) + + # DML with commit + await conn.set_autocommit(False) + await cursor.execute("UPDATE STAFF SET SALARY = SALARY + 1 WHERE ID = 10") + print("Updated rows:", cursor.rowcount) + await conn.rollback() + + await cursor.close() + +asyncio.run(main()) +``` diff --git a/README.md b/README.md index bee09f02..47d5cc56 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ For more information on the APIs supported by ibm_db, please refer to below link https://github.com/ibmdb/python-ibmdb/wiki/APIs +For the asyncio APIs, please refer to [ASYNC_APIs_WIKI.md](ASYNC_APIs_WIKI.md). + ## Pre-requisites @@ -744,29 +746,38 @@ later of Db2. While Db2 v8.x is fully supported, two of the tests (test_195.py and test_52949.py) utilize XML functionality. These tests will fail on version 8.x of Db2. -## Running the driver testsuite on Linux +## Running the driver testsuite -In order to run the entire python driver testsuite on Linux, run this -command at the command prompt: +Run the entire testsuite (sync + async): ``` python ibmdb_tests.py ``` -To run a single test, set the environment variable, **SINGLE_PYTHON_TEST**, to -the test filename you would like to run, followed by the previous command. +Run only sync or async tests, or a single test file: -## Running the driver testsuite on Windows +``` + python ibmdb_tests.py --async # Run only async tests + python ibmdb_tests.py --sync # Run only sync tests + python ibmdb_tests.py --async --test test_05_async_fetch.py # Run a single async test + python ibmdb_tests.py --sync --test test_006_ConnPassingOpts.py # Run a single sync test +``` -In order to run the entire python driver testsuite on Windows, run this -command at the command prompt: +Legacy: To run a single test, set the environment variable **SINGLE_PYTHON_TEST**: ``` - python ibmdb_tests.py + Windows: + set SINGLE_PYTHON_TEST=test_006_ConnPassingOpts.py + + Other platforms: + export SINGLE_PYTHON_TEST=test_006_ConnPassingOpts.py ``` -To run a single test, set the environment variable, **SINGLE_PYTHON_TEST**, to -the test filename you would like to run, followed by the previous command. +Then run: + +``` + python ibmdb_tests.py +``` ## Known Limitations for the Python driver diff --git a/asyncio_testsuite/test_01_async_connect.py b/asyncio_testsuite/test_01_async_connect.py new file mode 100644 index 00000000..5036cbb2 --- /dev/null +++ b/asyncio_testsuite/test_01_async_connect.py @@ -0,0 +1,50 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db_dbi +import config +from testfunctions import IbmDbTestFunctions + +class IbmDbTestCase(unittest.TestCase): + + def test_01_async_connect(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_01) + + def run_test_01(self): + async def main(): + # Cataloged connection + conn = await ibm_db_dbi.connect_async(config.database, config.user, config.password) + if conn: + print("Cataloged connection succeeded.") + conn.close() + else: + print("Cataloged connection failed.") + + # Uncataloged connection + conn = await ibm_db_dbi.connect_async( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + if conn: + print("Uncataloged connection succeeded.") + conn.close() + else: + print("Uncataloged connection failed.") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Cataloged connection succeeded. +#Uncataloged connection succeeded. +#__ZOS_EXPECTED__ +#Cataloged connection succeeded. +#Uncataloged connection succeeded. +#__SYSTEMI_EXPECTED__ +#Cataloged connection succeeded. +#Uncataloged connection succeeded. +#__IDS_EXPECTED__ +#Cataloged connection succeeded. +#Uncataloged connection succeeded. diff --git a/asyncio_testsuite/test_02_async_pconnect.py b/asyncio_testsuite/test_02_async_pconnect.py new file mode 100644 index 00000000..c5bd93d5 --- /dev/null +++ b/asyncio_testsuite/test_02_async_pconnect.py @@ -0,0 +1,46 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db_dbi +import config +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_02_async_pconnect(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_02) + + def run_test_02(self): + async def main(): + conn = await ibm_db_dbi.pconnect_async( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + print("Persistent connection:", conn) + info = conn.server_info() + print("Server info:", info) + conn.close() + print("Connection closed.") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Persistent connection: +#Server info: (%s) +#Connection closed. +#__ZOS_EXPECTED__ +#Persistent connection: +#Server info: (%s) +#Connection closed. +#__SYSTEMI_EXPECTED__ +#Persistent connection: +#Server info: (%s) +#Connection closed. +#__IDS_EXPECTED__ +#Persistent connection: +#Server info: (%s) +#Connection closed. diff --git a/asyncio_testsuite/test_03_async_connection_class.py b/asyncio_testsuite/test_03_async_connection_class.py new file mode 100644 index 00000000..f6c3f663 --- /dev/null +++ b/asyncio_testsuite/test_03_async_connection_class.py @@ -0,0 +1,44 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_03_async_connection_class(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_03) + + def run_test_03(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + print("Connected via AsyncConnection.connect():", conn) + + cursor = await conn.cursor() + print("Cursor created:", cursor) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Connected via AsyncConnection.connect(): +#Cursor created: +#__ZOS_EXPECTED__ +#Connected via AsyncConnection.connect(): +#Cursor created: +#__SYSTEMI_EXPECTED__ +#Connected via AsyncConnection.connect(): +#Cursor created: +#__IDS_EXPECTED__ +#Connected via AsyncConnection.connect(): +#Cursor created: diff --git a/asyncio_testsuite/test_04_async_context_manager.py b/asyncio_testsuite/test_04_async_context_manager.py new file mode 100644 index 00000000..19eca74f --- /dev/null +++ b/asyncio_testsuite/test_04_async_context_manager.py @@ -0,0 +1,59 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_04_async_context_manager(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_04) + + def run_test_04(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + async with conn: + cursor = await conn.cursor() + async with cursor: + await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 3 ROWS ONLY") + rows = await cursor.fetchall() + for row in rows: + print(row) + print("Cursor auto-closed via __aexit__") + print("Connection auto-closed via __aexit__") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#Cursor auto-closed via __aexit__ +#Connection auto-closed via __aexit__ +#__ZOS_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#Cursor auto-closed via __aexit__ +#Connection auto-closed via __aexit__ +#__SYSTEMI_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#Cursor auto-closed via __aexit__ +#Connection auto-closed via __aexit__ +#__IDS_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#Cursor auto-closed via __aexit__ +#Connection auto-closed via __aexit__ diff --git a/asyncio_testsuite/test_05_async_fetch.py b/asyncio_testsuite/test_05_async_fetch.py new file mode 100644 index 00000000..08e3b084 --- /dev/null +++ b/asyncio_testsuite/test_05_async_fetch.py @@ -0,0 +1,59 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_05_async_fetch(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_05) + + def run_test_05(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + await cursor.execute("SELECT ID, NAME, JOB FROM STAFF FETCH FIRST 10 ROWS ONLY") + + # fetchone + row = await cursor.fetchone() + print("fetchone:", row) + + # fetchmany + rows = await cursor.fetchmany(size=3) + print("fetchmany(3):", rows) + + # fetchall (remaining) + remaining = await cursor.fetchall() + print("fetchall remaining:", remaining) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#fetchone: (10, 'Sanders', 'Mgr ') +#fetchmany(3): [(20, 'Pernal', 'Sales'), (30, 'Marenghi', 'Mgr '), (40, 'OBrien', 'Sales')] +#fetchall remaining: [(50, 'Hanes', 'Mgr '), (60, 'Quigley', 'Sales'), (70, 'Rothman', 'Sales'), (80, 'James', 'Clerk'), (90, 'Koonitz', 'Sales'), (100, 'Plotz', 'Mgr ')] +#__ZOS_EXPECTED__ +#fetchone: (10, 'Sanders', 'Mgr ') +#fetchmany(3): [(20, 'Pernal', 'Sales'), (30, 'Marenghi', 'Mgr '), (40, 'OBrien', 'Sales')] +#fetchall remaining: [(50, 'Hanes', 'Mgr '), (60, 'Quigley', 'Sales'), (70, 'Rothman', 'Sales'), (80, 'James', 'Clerk'), (90, 'Koonitz', 'Sales'), (100, 'Plotz', 'Mgr ')] +#__SYSTEMI_EXPECTED__ +#fetchone: (10, 'Sanders', 'Mgr ') +#fetchmany(3): [(20, 'Pernal', 'Sales'), (30, 'Marenghi', 'Mgr '), (40, 'OBrien', 'Sales')] +#fetchall remaining: [(50, 'Hanes', 'Mgr '), (60, 'Quigley', 'Sales'), (70, 'Rothman', 'Sales'), (80, 'James', 'Clerk'), (90, 'Koonitz', 'Sales'), (100, 'Plotz', 'Mgr ')] +#__IDS_EXPECTED__ +#fetchone: (10, 'Sanders', 'Mgr ') +#fetchmany(3): [(20, 'Pernal', 'Sales'), (30, 'Marenghi', 'Mgr '), (40, 'OBrien', 'Sales')] +#fetchall remaining: [(50, 'Hanes', 'Mgr '), (60, 'Quigley', 'Sales'), (70, 'Rothman', 'Sales'), (80, 'James', 'Clerk'), (90, 'Koonitz', 'Sales'), (100, 'Plotz', 'Mgr ')] diff --git a/asyncio_testsuite/test_06_async_iteration.py b/asyncio_testsuite/test_06_async_iteration.py new file mode 100644 index 00000000..330a91f2 --- /dev/null +++ b/asyncio_testsuite/test_06_async_iteration.py @@ -0,0 +1,65 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_06_async_iteration(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_06) + + def run_test_06(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 5 ROWS ONLY") + + count = 0 + async for row in cursor: + print(row) + count += 1 + print("Iterated over %d rows" % count) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#(40, 'OBrien') +#(50, 'Hanes') +#Iterated over 5 rows +#__ZOS_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#(40, 'OBrien') +#(50, 'Hanes') +#Iterated over 5 rows +#__SYSTEMI_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#(40, 'OBrien') +#(50, 'Hanes') +#Iterated over 5 rows +#__IDS_EXPECTED__ +#(10, 'Sanders') +#(20, 'Pernal') +#(30, 'Marenghi') +#(40, 'OBrien') +#(50, 'Hanes') +#Iterated over 5 rows diff --git a/asyncio_testsuite/test_07_async_execute_params.py b/asyncio_testsuite/test_07_async_execute_params.py new file mode 100644 index 00000000..94431b11 --- /dev/null +++ b/asyncio_testsuite/test_07_async_execute_params.py @@ -0,0 +1,63 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_07_async_execute_params(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_07) + + def run_test_07(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # SELECT with positional parameter + await cursor.execute( + "SELECT ID, NAME, JOB, SALARY FROM STAFF WHERE ID = ?", + (20,) + ) + row = await cursor.fetchone() + print("Staff ID 20:", row) + + # SELECT with multiple parameters + await cursor.execute( + "SELECT ID, NAME, JOB FROM STAFF WHERE DEPT = ? AND JOB = ?", + (20, 'Sales') + ) + rows = await cursor.fetchall() + print("Dept 20 Sales (%d rows):" % len(rows)) + for r in rows: + print(" ", r) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) +#Dept 20 Sales (1 rows): +# (20, 'Pernal', 'Sales') +#__ZOS_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) +#Dept 20 Sales (1 rows): +# (20, 'Pernal', 'Sales') +#__SYSTEMI_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) +#Dept 20 Sales (1 rows): +# (20, 'Pernal', 'Sales') +#__IDS_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) +#Dept 20 Sales (1 rows): +# (20, 'Pernal', 'Sales') diff --git a/asyncio_testsuite/test_08_async_prepare_bind_execute.py b/asyncio_testsuite/test_08_async_prepare_bind_execute.py new file mode 100644 index 00000000..ecf615f2 --- /dev/null +++ b/asyncio_testsuite/test_08_async_prepare_bind_execute.py @@ -0,0 +1,49 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_08_async_prepare_bind_execute(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_08) + + def run_test_08(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + await cursor.prepare("SELECT ID, NAME, JOB, SALARY FROM STAFF WHERE ID = ?") + await cursor.bind_param(1, 20) + await cursor.execute() + + row = await cursor.fetchone() + print("Staff ID 20:", row) + + while row: + row = await cursor.fetchone() + if row: + print(row) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) +#__ZOS_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) +#__SYSTEMI_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) +#__IDS_EXPECTED__ +#Staff ID 20: (20, 'Pernal', 'Sales', Decimal('18171.25')) diff --git a/asyncio_testsuite/test_09_async_callproc.py b/asyncio_testsuite/test_09_async_callproc.py new file mode 100644 index 00000000..801b67a4 --- /dev/null +++ b/asyncio_testsuite/test_09_async_callproc.py @@ -0,0 +1,53 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_09_async_callproc(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_09) + + def run_test_09(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # Create stored procedure (ignore error if already exists) + try: + await cursor.execute(""" + CREATE PROCEDURE ASYNC_TEST_DOUBLE(IN val BIGINT, OUT result BIGINT) + LANGUAGE SQL + BEGIN + SET result = val * 2; + END + """) + except Exception: + pass + + # Call procedure via callproc + result = await cursor.callproc("ASYNC_TEST_DOUBLE", (42, 0)) + print("callproc result:", result) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#callproc result: (42, 84) +#__ZOS_EXPECTED__ +#callproc result: (42, 84) +#__SYSTEMI_EXPECTED__ +#callproc result: (42, 84) +#__IDS_EXPECTED__ +#callproc result: (42, 84) diff --git a/asyncio_testsuite/test_10_async_bind_array_callproc.py b/asyncio_testsuite/test_10_async_bind_array_callproc.py new file mode 100644 index 00000000..86cb5535 --- /dev/null +++ b/asyncio_testsuite/test_10_async_bind_array_callproc.py @@ -0,0 +1,69 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_10_async_bind_array_callproc(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_10) + + def run_test_10(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # Create stored procedure (ignore error if already exists) + try: + await cursor.execute(""" + CREATE PROCEDURE CLIARRAY.ARRAY_BINT12(IN var1 bint_array, OUT var2 BIGINT) + LANGUAGE SQL + BEGIN + SET var2 = CARDINALITY(var1); + END + """) + except Exception: + pass + + # Prepare, bind array IN + scalar OUT, execute + await cursor.prepare("CALL CLIARRAY.ARRAY_BINT12(?,?)") + + input_array = [10, 20, 30, 40, 50, 60, 70] + output = 0 + + await cursor.bind_param(1, input_array, ibm_db.SQL_PARAM_INPUT) + await cursor.bind_param(2, output, ibm_db.SQL_PARAM_OUTPUT) + + await cursor.execute() + + result = await cursor.fetch_callproc() + print("Input array:", input_array) + print("fetch_callproc result (cardinality):", result) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Input array: [10, 20, 30, 40, 50, 60, 70] +#fetch_callproc result (cardinality): (, [10, 20, 30, 40, 50, 60, 70], 7) +#__ZOS_EXPECTED__ +#Input array: [10, 20, 30, 40, 50, 60, 70] +#fetch_callproc result (cardinality): (, [10, 20, 30, 40, 50, 60, 70], 7) +#__SYSTEMI_EXPECTED__ +#Input array: [10, 20, 30, 40, 50, 60, 70] +#fetch_callproc result (cardinality): (, [10, 20, 30, 40, 50, 60, 70], 7) +#__IDS_EXPECTED__ +#Input array: [10, 20, 30, 40, 50, 60, 70] +#fetch_callproc result (cardinality): (, [10, 20, 30, 40, 50, 60, 70], 7) diff --git a/asyncio_testsuite/test_11_async_executemany.py b/asyncio_testsuite/test_11_async_executemany.py new file mode 100644 index 00000000..6bf72694 --- /dev/null +++ b/asyncio_testsuite/test_11_async_executemany.py @@ -0,0 +1,62 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_11_async_executemany(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_11) + + def run_test_11(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + await conn.set_autocommit(True) + cursor = await conn.cursor() + + # Create temp table + try: + await cursor.execute("DROP TABLE ASYNC_TEST_BATCH") + except Exception: + pass + await cursor.execute("CREATE TABLE ASYNC_TEST_BATCH (ID INT, NAME VARCHAR(30))") + + # Insert multiple rows + data = [(1, 'Alice'), (2, 'Bob'), (3, 'Charlie'), (4, 'Diana')] + await cursor.executemany("INSERT INTO ASYNC_TEST_BATCH (ID, NAME) VALUES (?, ?)", data) + print("Inserted %d rows" % len(data)) + + # Verify + await cursor.execute("SELECT * FROM ASYNC_TEST_BATCH ORDER BY ID") + rows = await cursor.fetchall() + print("Rows:", rows) + + # Cleanup + await cursor.execute("DROP TABLE ASYNC_TEST_BATCH") + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Inserted 4 rows +#Rows: [(1, 'Alice'), (2, 'Bob'), (3, 'Charlie'), (4, 'Diana')] +#__ZOS_EXPECTED__ +#Inserted 4 rows +#Rows: [(1, 'Alice'), (2, 'Bob'), (3, 'Charlie'), (4, 'Diana')] +#__SYSTEMI_EXPECTED__ +#Inserted 4 rows +#Rows: [(1, 'Alice'), (2, 'Bob'), (3, 'Charlie'), (4, 'Diana')] +#__IDS_EXPECTED__ +#Inserted 4 rows +#Rows: [(1, 'Alice'), (2, 'Bob'), (3, 'Charlie'), (4, 'Diana')] diff --git a/asyncio_testsuite/test_12_async_commit_rollback.py b/asyncio_testsuite/test_12_async_commit_rollback.py new file mode 100644 index 00000000..e5ea2f3a --- /dev/null +++ b/asyncio_testsuite/test_12_async_commit_rollback.py @@ -0,0 +1,71 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_12_async_commit_rollback(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_12) + + def run_test_12(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + await conn.set_autocommit(False) + cursor = await conn.cursor() + + # Create temp table + try: + await cursor.execute("DROP TABLE ASYNC_TEST_TXN") + except Exception: + pass + await conn.commit() + await cursor.execute("CREATE TABLE ASYNC_TEST_TXN (ID INT, VAL VARCHAR(20))") + await conn.commit() + + # Insert and rollback + await cursor.execute("INSERT INTO ASYNC_TEST_TXN VALUES (1, 'should_disappear')") + await conn.rollback() + await cursor.execute("SELECT COUNT(*) FROM ASYNC_TEST_TXN") + row = await cursor.fetchone() + print("After rollback, count:", row[0]) + assert row[0] == 0, "Rollback failed" + + # Insert and commit + await cursor.execute("INSERT INTO ASYNC_TEST_TXN VALUES (2, 'should_stay')") + await conn.commit() + await cursor.execute("SELECT * FROM ASYNC_TEST_TXN") + row = await cursor.fetchone() + print("After commit:", row) + assert row is not None, "Commit failed" + + # Cleanup + await cursor.execute("DROP TABLE ASYNC_TEST_TXN") + await conn.commit() + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#After rollback, count: 0 +#After commit: (2, 'should_stay') +#__ZOS_EXPECTED__ +#After rollback, count: 0 +#After commit: (2, 'should_stay') +#__SYSTEMI_EXPECTED__ +#After rollback, count: 0 +#After commit: (2, 'should_stay') +#__IDS_EXPECTED__ +#After rollback, count: 0 +#After commit: (2, 'should_stay') diff --git a/asyncio_testsuite/test_13_async_autocommit.py b/asyncio_testsuite/test_13_async_autocommit.py new file mode 100644 index 00000000..5ef132a7 --- /dev/null +++ b/asyncio_testsuite/test_13_async_autocommit.py @@ -0,0 +1,47 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_13_async_autocommit(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_13) + + def run_test_13(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + # Turn autocommit ON + result = await conn.set_autocommit(True) + print("set_autocommit(True):", result) + + # Turn autocommit OFF + result = await conn.set_autocommit(False) + print("set_autocommit(False):", result) + + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#set_autocommit(True): True +#set_autocommit(False): True +#__ZOS_EXPECTED__ +#set_autocommit(True): True +#set_autocommit(False): True +#__SYSTEMI_EXPECTED__ +#set_autocommit(True): True +#set_autocommit(False): True +#__IDS_EXPECTED__ +#set_autocommit(True): True +#set_autocommit(False): True diff --git a/asyncio_testsuite/test_14_async_set_get_option.py b/asyncio_testsuite/test_14_async_set_get_option.py new file mode 100644 index 00000000..abb79df3 --- /dev/null +++ b/asyncio_testsuite/test_14_async_set_get_option.py @@ -0,0 +1,49 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_14_async_set_get_option(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_14) + + def run_test_14(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + # Get current autocommit setting + val = await conn.get_option(ibm_db.SQL_ATTR_AUTOCOMMIT) + print("SQL_ATTR_AUTOCOMMIT:", val) + + # Set autocommit via set_option + await conn.set_option({ibm_db.SQL_ATTR_AUTOCOMMIT: ibm_db.SQL_AUTOCOMMIT_ON}) + val = await conn.get_option(ibm_db.SQL_ATTR_AUTOCOMMIT) + print("After set ON, SQL_ATTR_AUTOCOMMIT:", val) + + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#SQL_ATTR_AUTOCOMMIT: 0 +#After set ON, SQL_ATTR_AUTOCOMMIT: 1 +#__ZOS_EXPECTED__ +#SQL_ATTR_AUTOCOMMIT: 0 +#After set ON, SQL_ATTR_AUTOCOMMIT: 1 +#__SYSTEMI_EXPECTED__ +#SQL_ATTR_AUTOCOMMIT: 0 +#After set ON, SQL_ATTR_AUTOCOMMIT: 1 +#__IDS_EXPECTED__ +#SQL_ATTR_AUTOCOMMIT: 0 +#After set ON, SQL_ATTR_AUTOCOMMIT: 1 diff --git a/asyncio_testsuite/test_15_async_schema.py b/asyncio_testsuite/test_15_async_schema.py new file mode 100644 index 00000000..c4ae637b --- /dev/null +++ b/asyncio_testsuite/test_15_async_schema.py @@ -0,0 +1,55 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_15_async_schema(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_15) + + def run_test_15(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + schema = await conn.get_current_schema() + print("Current schema:", schema.upper()) + + await conn.set_current_schema("TESTSCHEMA") + schema = await conn.get_current_schema() + print("After set schema:", schema) + + # Reset back to original + await conn.set_current_schema(config.user.upper()) + schema = await conn.get_current_schema() + print("After reset schema:", schema.upper()) + + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Current schema: %s +#After set schema: TESTSCHEMA +#After reset schema: %s +#__ZOS_EXPECTED__ +#Current schema: %s +#After set schema: TESTSCHEMA +#After reset schema: %s +#__SYSTEMI_EXPECTED__ +#Current schema: %s +#After set schema: TESTSCHEMA +#After reset schema: %s +#__IDS_EXPECTED__ +#Current schema: %s +#After set schema: TESTSCHEMA +#After reset schema: %s diff --git a/asyncio_testsuite/test_16_async_server_info.py b/asyncio_testsuite/test_16_async_server_info.py new file mode 100644 index 00000000..70776b6d --- /dev/null +++ b/asyncio_testsuite/test_16_async_server_info.py @@ -0,0 +1,38 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_16_async_server_info(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_16) + + def run_test_16(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + info = await conn.server_info() + print("Server info (DBMS_NAME, DBMS_VER):", info) + + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Server info (DBMS_NAME, DBMS_VER): (%s) +#__ZOS_EXPECTED__ +#Server info (DBMS_NAME, DBMS_VER): (%s) +#__SYSTEMI_EXPECTED__ +#Server info (DBMS_NAME, DBMS_VER): (%s) +#__IDS_EXPECTED__ +#Server info (DBMS_NAME, DBMS_VER): (%s) diff --git a/asyncio_testsuite/test_17_async_metadata.py b/asyncio_testsuite/test_17_async_metadata.py new file mode 100644 index 00000000..5813909f --- /dev/null +++ b/asyncio_testsuite/test_17_async_metadata.py @@ -0,0 +1,107 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_17_async_metadata(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_17) + + def run_test_17(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + schema = config.user.upper() + + # Tables + tables = await conn.tables(schema_name=schema, table_name="STAFF") + print("Tables (%d found):" % len(tables)) + for t in tables[:3]: + print(" ", t.get("TABLE_NAME", t)) + + # Columns + columns = await conn.columns(schema_name=schema, table_name="STAFF") + print("Columns (%d found):" % len(columns)) + for c in columns[:5]: + print(" ", c.get("COLUMN_NAME", c), "-", c.get("TYPE_NAME", "")) + + # Primary keys + pks = await conn.primary_keys(schema_name=schema, table_name="STAFF") + print("Primary keys (%d found):" % len(pks)) + for pk in pks: + print(" ", pk.get("COLUMN_NAME", pk)) + + # Indexes + idxs = await conn.indexes(schema_name=schema, table_name="STAFF") + print("Indexes (%d found):" % len(idxs)) + for idx in idxs[:5]: + print(" ", idx.get("INDEX_NAME", idx)) + + # Foreign keys + fks = await conn.foreign_keys(schema_name=schema, table_name="STAFF") + print("Foreign keys (%d found):" % len(fks)) + for fk in fks[:3]: + print(" ", fk.get("FK_NAME", fk)) + + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Tables (%d found): +# STAFF +#Columns (%d found): +# ID - SMALLINT +# NAME - VARCHAR +# DEPT - SMALLINT +# JOB - CHAR +# YEARS - SMALLINT +#Primary keys (%d found): +#Indexes (%d found): +#Foreign keys (%d found): +#__ZOS_EXPECTED__ +#Tables (%d found): +# STAFF +#Columns (%d found): +# ID - SMALLINT +# NAME - VARCHAR +# DEPT - SMALLINT +# JOB - CHAR +# YEARS - SMALLINT +#Primary keys (%d found): +#Indexes (%d found): +#Foreign keys (%d found): +#__SYSTEMI_EXPECTED__ +#Tables (%d found): +# STAFF +#Columns (%d found): +# ID - SMALLINT +# NAME - VARCHAR +# DEPT - SMALLINT +# JOB - CHAR +# YEARS - SMALLINT +#Primary keys (%d found): +#Indexes (%d found): +#Foreign keys (%d found): +#__IDS_EXPECTED__ +#Tables (%d found): +# STAFF +#Columns (%d found): +# ID - SMALLINT +# NAME - VARCHAR +# DEPT - SMALLINT +# JOB - CHAR +# YEARS - SMALLINT +#Primary keys (%d found): +#Indexes (%d found): +#Foreign keys (%d found): diff --git a/asyncio_testsuite/test_18_async_fix_return_type.py b/asyncio_testsuite/test_18_async_fix_return_type.py new file mode 100644 index 00000000..263448f9 --- /dev/null +++ b/asyncio_testsuite/test_18_async_fix_return_type.py @@ -0,0 +1,67 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_18_async_fix_return_type(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_18) + + def run_test_18(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + await conn.set_autocommit(True) + + # Enable FIX_RETURN_TYPE + await conn.set_fix_return_type(True) + cursor = await conn.cursor() + + await cursor.execute("SELECT SALARY, COMM FROM STAFF WHERE ID = 20") + row = await cursor.fetchone() + print("With FIX_RETURN_TYPE=True:", row) + print(" SALARY type:", type(row[0])) + + # Disable FIX_RETURN_TYPE + await conn.set_fix_return_type(False) + cursor2 = await conn.cursor() + await cursor2.execute("SELECT SALARY, COMM FROM STAFF WHERE ID = 20") + row2 = await cursor2.fetchone() + print("With FIX_RETURN_TYPE=False:", row2) + print(" SALARY type:", type(row2[0])) + + await cursor.close() + await cursor2.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#With FIX_RETURN_TYPE=True: (Decimal('18171.25'), Decimal('612.45')) +# SALARY type: +#With FIX_RETURN_TYPE=False: ('18171.25', '612.45') +# SALARY type: +#__ZOS_EXPECTED__ +#With FIX_RETURN_TYPE=True: (Decimal('18171.25'), Decimal('612.45')) +# SALARY type: +#With FIX_RETURN_TYPE=False: ('18171.25', '612.45') +# SALARY type: +#__SYSTEMI_EXPECTED__ +#With FIX_RETURN_TYPE=True: (Decimal('18171.25'), Decimal('612.45')) +# SALARY type: +#With FIX_RETURN_TYPE=False: ('18171.25', '612.45') +# SALARY type: +#__IDS_EXPECTED__ +#With FIX_RETURN_TYPE=True: (Decimal('18171.25'), Decimal('612.45')) +# SALARY type: +#With FIX_RETURN_TYPE=False: ('18171.25', '612.45') +# SALARY type: diff --git a/asyncio_testsuite/test_19_async_dml.py b/asyncio_testsuite/test_19_async_dml.py new file mode 100644 index 00000000..db16613e --- /dev/null +++ b/asyncio_testsuite/test_19_async_dml.py @@ -0,0 +1,80 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_19_async_dml(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_19) + + def run_test_19(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + await conn.set_autocommit(True) + cursor = await conn.cursor() + + # Setup + try: + await cursor.execute("DROP TABLE ASYNC_TEST_DML") + except Exception: + pass + await cursor.execute("CREATE TABLE ASYNC_TEST_DML (ID INT, NAME VARCHAR(30))") + + # INSERT + await cursor.execute("INSERT INTO ASYNC_TEST_DML VALUES (1, 'Alice')") + print("Insert rowcount:", cursor.rowcount) + + await cursor.execute("INSERT INTO ASYNC_TEST_DML VALUES (2, 'Bob')") + await cursor.execute("INSERT INTO ASYNC_TEST_DML VALUES (3, 'Charlie')") + + # UPDATE + await cursor.execute("UPDATE ASYNC_TEST_DML SET NAME = 'ALICE_UPDATED' WHERE ID = 1") + print("Update rowcount:", cursor.rowcount) + + # DELETE + await cursor.execute("DELETE FROM ASYNC_TEST_DML WHERE ID = 2") + print("Delete rowcount:", cursor.rowcount) + + # Verify + await cursor.execute("SELECT * FROM ASYNC_TEST_DML ORDER BY ID") + rows = await cursor.fetchall() + print("Remaining rows:", rows) + + # Cleanup + await cursor.execute("DROP TABLE ASYNC_TEST_DML") + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Insert rowcount: 1 +#Update rowcount: 1 +#Delete rowcount: 1 +#Remaining rows: [(1, 'ALICE_UPDATED'), (3, 'Charlie')] +#__ZOS_EXPECTED__ +#Insert rowcount: 1 +#Update rowcount: 1 +#Delete rowcount: 1 +#Remaining rows: [(1, 'ALICE_UPDATED'), (3, 'Charlie')] +#__SYSTEMI_EXPECTED__ +#Insert rowcount: 1 +#Update rowcount: 1 +#Delete rowcount: 1 +#Remaining rows: [(1, 'ALICE_UPDATED'), (3, 'Charlie')] +#__IDS_EXPECTED__ +#Insert rowcount: 1 +#Update rowcount: 1 +#Delete rowcount: 1 +#Remaining rows: [(1, 'ALICE_UPDATED'), (3, 'Charlie')] diff --git a/asyncio_testsuite/test_20_async_nextset.py b/asyncio_testsuite/test_20_async_nextset.py new file mode 100644 index 00000000..cc571fb0 --- /dev/null +++ b/asyncio_testsuite/test_20_async_nextset.py @@ -0,0 +1,107 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_20_async_nextset(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_20) + + def run_test_20(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # Create a procedure that returns two result sets + try: + await cursor.execute("DROP PROCEDURE ASYNC_TEST_MULTI_RS") + except Exception: + pass + await cursor.execute(""" + CREATE PROCEDURE ASYNC_TEST_MULTI_RS() + LANGUAGE SQL + DYNAMIC RESULT SETS 2 + BEGIN + DECLARE c1 CURSOR WITH RETURN FOR + SELECT ID, NAME FROM STAFF FETCH FIRST 3 ROWS ONLY; + DECLARE c2 CURSOR WITH RETURN FOR + SELECT DEPTNO, DEPTNAME FROM DEPARTMENT FETCH FIRST 3 ROWS ONLY; + OPEN c1; + OPEN c2; + END + """) + + # Call the stored procedure + await cursor.callproc("ASYNC_TEST_MULTI_RS") + + # First result set + print("Result set 1:") + rows1 = await cursor.fetchall() + for r in rows1: + print(" ", r) + + # Move to next result set + has_next = await cursor.nextset() + print("nextset returned:", has_next) + + if has_next: + print("Result set 2:") + rows2 = await cursor.fetchall() + for r in rows2: + print(" ", r) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Result set 1: +# (10, 'Sanders') +# (20, 'Pernal') +# (30, 'Marenghi') +#nextset returned: True +#Result set 2: +# ('A00', 'SPIFFY COMPUTER SERVICE DIV.') +# ('B01', 'PLANNING') +# ('C01', 'INFORMATION CENTER') +#__ZOS_EXPECTED__ +#Result set 1: +# (10, 'Sanders') +# (20, 'Pernal') +# (30, 'Marenghi') +#nextset returned: True +#Result set 2: +# ('A00', 'SPIFFY COMPUTER SERVICE DIV.') +# ('B01', 'PLANNING') +# ('C01', 'INFORMATION CENTER') +#__SYSTEMI_EXPECTED__ +#Result set 1: +# (10, 'Sanders') +# (20, 'Pernal') +# (30, 'Marenghi') +#nextset returned: True +#Result set 2: +# ('A00', 'SPIFFY COMPUTER SERVICE DIV.') +# ('B01', 'PLANNING') +# ('C01', 'INFORMATION CENTER') +#__IDS_EXPECTED__ +#Result set 1: +# (10, 'Sanders') +# (20, 'Pernal') +# (30, 'Marenghi') +#nextset returned: True +#Result set 2: +# ('A00', 'SPIFFY COMPUTER SERVICE DIV.') +# ('B01', 'PLANNING') +# ('C01', 'INFORMATION CENTER') diff --git a/asyncio_testsuite/test_21_async_error_handling.py b/asyncio_testsuite/test_21_async_error_handling.py new file mode 100644 index 00000000..0b79b68c --- /dev/null +++ b/asyncio_testsuite/test_21_async_error_handling.py @@ -0,0 +1,91 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db_dbi +import config +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_21_async_error_handling(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_21) + + def run_test_21(self): + async def main(): + conn = None + try: + # Attempt connection + conn = await ibm_db_dbi.connect_async( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + print("Connected:", conn) + + # Run error APIs on a good connection + err_msg = await ibm_db_dbi.conn_errormsg_async(conn.conn_handler) + print("conn_errormsg_async (good conn):", repr(err_msg)) + + err_code = await ibm_db_dbi.conn_error_async(conn.conn_handler) + print("conn_error_async (good conn):", repr(err_code)) + + sqlcode = await ibm_db_dbi.get_sqlcode_async() + print("get_sqlcode_async:", repr(sqlcode)) + + except Exception as e: + # Connection itself failed + print("Connection failed:", e) + + # Run error APIs without a valid conn_handler + try: + err_msg = await ibm_db_dbi.conn_errormsg_async(None) + print("conn_errormsg_async (failed conn):", repr(err_msg)) + except Exception as e2: + print("conn_errormsg_async raised:", e2) + + try: + err_code = await ibm_db_dbi.conn_error_async(None) + print("conn_error_async (failed conn):", repr(err_code)) + except Exception as e2: + print("conn_error_async raised:", e2) + + try: + sqlcode = await ibm_db_dbi.get_sqlcode_async() + print("get_sqlcode_async (failed conn):", repr(sqlcode)) + except Exception as e2: + print("get_sqlcode_async raised:", e2) + + finally: + if conn: + conn.close() + print("Connection closed.") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Connected: +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' +#Connection closed. +#__ZOS_EXPECTED__ +#Connected: +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' +#Connection closed. +#__SYSTEMI_EXPECTED__ +#Connected: +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' +#Connection closed. +#__IDS_EXPECTED__ +#Connected: +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' +#Connection closed. diff --git a/asyncio_testsuite/test_22_async_scalar_bind_callproc.py b/asyncio_testsuite/test_22_async_scalar_bind_callproc.py new file mode 100644 index 00000000..3a5df6d7 --- /dev/null +++ b/asyncio_testsuite/test_22_async_scalar_bind_callproc.py @@ -0,0 +1,64 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_22_async_scalar_bind_callproc(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_22) + + def run_test_22(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # Create stored procedure + try: + await cursor.execute(""" + CREATE PROCEDURE ASYNC_TEST_DOUBLE(IN val BIGINT, OUT result BIGINT) + LANGUAGE SQL + BEGIN + SET result = val * 2; + END + """) + except Exception: + pass + + # Prepare + bind + execute + await cursor.prepare("CALL ASYNC_TEST_DOUBLE(?,?)") + await cursor.bind_param(1, 123456, ibm_db.SQL_PARAM_INPUT) + await cursor.bind_param(2, 0, ibm_db.SQL_PARAM_OUTPUT) + await cursor.execute() + + result = await cursor.fetch_callproc() + print("Input: 123456") + print("fetch_callproc result:", result) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Input: 123456 +#fetch_callproc result: (, 123456, 246912) +#__ZOS_EXPECTED__ +#Input: 123456 +#fetch_callproc result: (, 123456, 246912) +#__SYSTEMI_EXPECTED__ +#Input: 123456 +#fetch_callproc result: (, 123456, 246912) +#__IDS_EXPECTED__ +#Input: 123456 +#fetch_callproc result: (, 123456, 246912) diff --git a/asyncio_testsuite/test_23_async_concurrent_queries.py b/asyncio_testsuite/test_23_async_concurrent_queries.py new file mode 100644 index 00000000..15bc857e --- /dev/null +++ b/asyncio_testsuite/test_23_async_concurrent_queries.py @@ -0,0 +1,63 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_23_async_concurrent_queries(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_23) + + def run_test_23(self): + async def query_staff(conn, staff_id): + cursor = await conn.cursor() + await cursor.execute( + "SELECT ID, NAME, JOB FROM STAFF WHERE ID = ?", + (staff_id,) + ) + row = await cursor.fetchone() + await cursor.close() + return row + + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + # Run 3 queries concurrently + results = await asyncio.gather( + query_staff(conn, 10), + query_staff(conn, 20), + query_staff(conn, 30), + ) + + for r in results: + print(r) + + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#(10, 'Sanders', 'Mgr ') +#(20, 'Pernal', 'Sales') +#(30, 'Marenghi', 'Mgr ') +#__ZOS_EXPECTED__ +#(10, 'Sanders', 'Mgr ') +#(20, 'Pernal', 'Sales') +#(30, 'Marenghi', 'Mgr ') +#__SYSTEMI_EXPECTED__ +#(10, 'Sanders', 'Mgr ') +#(20, 'Pernal', 'Sales') +#(30, 'Marenghi', 'Mgr ') +#__IDS_EXPECTED__ +#(10, 'Sanders', 'Mgr ') +#(20, 'Pernal', 'Sales') +#(30, 'Marenghi', 'Mgr ') diff --git a/asyncio_testsuite/test_24_async_error_functions.py b/asyncio_testsuite/test_24_async_error_functions.py new file mode 100644 index 00000000..3b61694b --- /dev/null +++ b/asyncio_testsuite/test_24_async_error_functions.py @@ -0,0 +1,54 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db_dbi +import config +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_24_async_error_functions(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_24) + + def run_test_24(self): + async def main(): + # Good connection first + conn = await ibm_db_dbi.connect_async( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + # Check error functions on a good connection (pass raw conn_handler) + err_msg = await ibm_db_dbi.conn_errormsg_async(conn.conn_handler) + print("conn_errormsg_async (good conn):", repr(err_msg)) + + err_code = await ibm_db_dbi.conn_error_async(conn.conn_handler) + print("conn_error_async (good conn):", repr(err_code)) + + sqlcode = await ibm_db_dbi.get_sqlcode_async() + print("get_sqlcode_async:", repr(sqlcode)) + + conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' +#__ZOS_EXPECTED__ +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' +#__SYSTEMI_EXPECTED__ +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' +#__IDS_EXPECTED__ +#conn_errormsg_async (good conn): '' +#conn_error_async (good conn): '' +#get_sqlcode_async: '' diff --git a/asyncio_testsuite/test_25_async_createdb_dropdb.py b/asyncio_testsuite/test_25_async_createdb_dropdb.py new file mode 100644 index 00000000..1410b4c4 --- /dev/null +++ b/asyncio_testsuite/test_25_async_createdb_dropdb.py @@ -0,0 +1,104 @@ +from __future__ import print_function +import asyncio +import sys +import os +import unittest +import ibm_db +import ibm_db_dbi +import config +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + @unittest.skipIf(os.environ.get("CI", False), "Test fails in CI") + def test_25_async_createdb_dropdb(self): + obj = IbmDbTestFunctions() + if ((obj.server.DBMS_NAME == "DB2") or (obj.server.DBMS_NAME[0:3] != "DB2")): + raise unittest.SkipTest("createdb, dropdb not Supported") + obj.assert_expect(self.run_test_25) + + def run_test_25(self): + async def main(): + database = 'test001' + conn_str = "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % (database, config.hostname, config.port, config.user, config.password) + dsn_attach = "attach=true;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;" % (config.hostname, config.port) + + # Clean up if test database already exists + conn = None + try: + conn = ibm_db.connect(conn_str, '', '') + except: + pass + if conn: + ibm_db.close(conn) + conn = None + try: + await ibm_db_dbi.dropdb_async(database, dsn_attach, config.user, config.password) + except: + print('Errors occurred during drop database') + + try: + # create database + rc = await ibm_db_dbi.createdb_async(database, dsn_attach, config.user, config.password) + if rc: + conn = ibm_db.connect(conn_str, '', '') + if conn: + print('database created sucessfully') + ibm_db.close(conn) + conn = None + else: + print('database is not created') + else: + print('Errors occurred during create database') + + # drop database + rc = await ibm_db_dbi.dropdb_async(database, dsn_attach, config.user, config.password) + if rc: + try: + conn = ibm_db.connect(conn_str, '', '') + except: + print('datbase droped sucessfully') + if conn: + print('Errors occurred during drop database') + ibm_db.close(conn) + conn = None + else: + print('Errors occurred during delete database') + + # create database with codeset option + rc = await ibm_db_dbi.createdb_async(database, dsn_attach, config.user, config.password, codeset='iso88591') + if rc: + conn = ibm_db.connect(conn_str, '', '') + server_info = ibm_db.server_info(conn) + if conn and (server_info.DB_CODEPAGE == 819): + print('database with codeset created sucessfully') + ibm_db.close(conn) + conn = None + else: + print('database is not created') + else: + print('Errors occurred during create database') + + # drop database + rc = await ibm_db_dbi.dropdb_async(database, dsn_attach, config.user, config.password) + if rc: + try: + conn = ibm_db.connect(conn_str, '', '') + except: + print('datbase droped sucessfully') + if conn: + print('Errors occurred during drop database') + ibm_db.close(conn) + conn = None + else: + print('Errors occurred during drop database') + except: + print(ibm_db.conn_errormsg()) + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#database created sucessfully +#datbase droped sucessfully +#database with codeset created sucessfully +#datbase droped sucessfully diff --git a/asyncio_testsuite/test_26_async_inout_param.py b/asyncio_testsuite/test_26_async_inout_param.py new file mode 100644 index 00000000..090b124f --- /dev/null +++ b/asyncio_testsuite/test_26_async_inout_param.py @@ -0,0 +1,62 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_26_async_inout_param(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_26) + + def run_test_26(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # Create stored procedure with INOUT + try: + await cursor.execute(""" + CREATE PROCEDURE ASYNC_TEST_INOUT(INOUT val INTEGER) + LANGUAGE SQL + BEGIN + SET val = val + 100; + END + """) + except Exception: + pass + + await cursor.prepare("CALL ASYNC_TEST_INOUT(?)") + await cursor.bind_param(1, 42, ibm_db.SQL_PARAM_INPUT_OUTPUT) + await cursor.execute() + + result = await cursor.fetch_callproc() + print("Input: 42, Expected output: 142") + print("fetch_callproc result:", result) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Input: 42, Expected output: 142 +#fetch_callproc result: (, 142) +#__ZOS_EXPECTED__ +#Input: 42, Expected output: 142 +#fetch_callproc result: (, 142) +#__SYSTEMI_EXPECTED__ +#Input: 42, Expected output: 142 +#fetch_callproc result: (, 142) +#__IDS_EXPECTED__ +#Input: 42, Expected output: 142 +#fetch_callproc result: (, 142) diff --git a/asyncio_testsuite/test_27_async_bind_datatypes.py b/asyncio_testsuite/test_27_async_bind_datatypes.py new file mode 100644 index 00000000..fae314c2 --- /dev/null +++ b/asyncio_testsuite/test_27_async_bind_datatypes.py @@ -0,0 +1,83 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_27_async_bind_datatypes(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_27) + + def run_test_27(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + await conn.set_autocommit(True) + cursor = await conn.cursor() + + # Create temp table with multiple types + try: + await cursor.execute("DROP TABLE ASYNC_TEST_TYPES") + except Exception: + pass + await cursor.execute(""" + CREATE TABLE ASYNC_TEST_TYPES ( + ID INTEGER, + NAME VARCHAR(50), + AMOUNT DECIMAL(10,2), + ACTIVE SMALLINT + ) + """) + + # Prepare and bind different types + await cursor.prepare("INSERT INTO ASYNC_TEST_TYPES VALUES (?, ?, ?, ?)") + await cursor.bind_param(1, 1) + await cursor.bind_param(2, 'Test Name') + await cursor.bind_param(3, '99.95') + await cursor.bind_param(4, 1) + await cursor.execute() + + # Verify + await cursor.execute("SELECT * FROM ASYNC_TEST_TYPES") + row = await cursor.fetchone() + print("Inserted row:", row) + print(" ID type:", type(row[0])) + print(" NAME type:", type(row[1])) + print(" AMOUNT type:", type(row[2])) + + # Cleanup + await cursor.execute("DROP TABLE ASYNC_TEST_TYPES") + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Inserted row: (1, 'Test Name', Decimal('99.95'), 1) +# ID type: +# NAME type: +# AMOUNT type: +#__ZOS_EXPECTED__ +#Inserted row: (1, 'Test Name', Decimal('99.95'), 1) +# ID type: +# NAME type: +# AMOUNT type: +#__SYSTEMI_EXPECTED__ +#Inserted row: (1, 'Test Name', Decimal('99.95'), 1) +# ID type: +# NAME type: +# AMOUNT type: +#__IDS_EXPECTED__ +#Inserted row: (1, 'Test Name', Decimal('99.95'), 1) +# ID type: +# NAME type: +# AMOUNT type: diff --git a/asyncio_testsuite/test_28_async_cursor_description.py b/asyncio_testsuite/test_28_async_cursor_description.py new file mode 100644 index 00000000..5ec01e61 --- /dev/null +++ b/asyncio_testsuite/test_28_async_cursor_description.py @@ -0,0 +1,71 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_28_async_cursor_description(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_28) + + def run_test_28(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + await cursor.execute("SELECT ID, NAME, JOB, SALARY FROM STAFF FETCH FIRST 1 ROWS ONLY") + + # Description is populated after execute + desc = cursor.description + print("cursor.description:") + if desc: + for col in desc: + print(" %s" % (col,)) + else: + print(" description is None (may need async fetch of description)") + + row = await cursor.fetchone() + print("First row:", row) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#cursor.description: +# ['ID', DBAPITypeObject(%s), 6, 6, 5, 0, False] +# ['NAME', DBAPITypeObject(%s), 9, 9, 9, 0, True] +# ['JOB', DBAPITypeObject(%s), 5, 5, 5, 0, True] +# ['SALARY', DBAPITypeObject(%s), 9, 9, 7, 2, True] +#First row: (10, 'Sanders', 'Mgr ', Decimal('18357.50')) +#__ZOS_EXPECTED__ +#cursor.description: +# ['ID', DBAPITypeObject(%s), 6, 6, 5, 0, False] +# ['NAME', DBAPITypeObject(%s), 9, 9, 9, 0, True] +# ['JOB', DBAPITypeObject(%s), 5, 5, 5, 0, True] +# ['SALARY', DBAPITypeObject(%s), 9, 9, 7, 2, True] +#First row: (10, 'Sanders', 'Mgr ', Decimal('18357.50')) +#__SYSTEMI_EXPECTED__ +#cursor.description: +# ['ID', DBAPITypeObject(%s), 6, 6, 5, 0, False] +# ['NAME', DBAPITypeObject(%s), 9, 9, 9, 0, True] +# ['JOB', DBAPITypeObject(%s), 5, 5, 5, 0, True] +# ['SALARY', DBAPITypeObject(%s), 9, 9, 7, 2, True] +#First row: (10, 'Sanders', 'Mgr ', Decimal('18357.50')) +#__IDS_EXPECTED__ +#cursor.description: +# ['ID', DBAPITypeObject(%s), 6, 6, 5, 0, False] +# ['NAME', DBAPITypeObject(%s), 9, 9, 9, 0, True] +# ['JOB', DBAPITypeObject(%s), 5, 5, 5, 0, True] +# ['SALARY', DBAPITypeObject(%s), 9, 9, 7, 2, True] +#First row: (10, 'Sanders', 'Mgr ', Decimal('18357.50')) diff --git a/asyncio_testsuite/test_29_async_reprepare.py b/asyncio_testsuite/test_29_async_reprepare.py new file mode 100644 index 00000000..e12b05c5 --- /dev/null +++ b/asyncio_testsuite/test_29_async_reprepare.py @@ -0,0 +1,62 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_29_async_reprepare(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_29) + + def run_test_29(self): + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # First query + await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 2 ROWS ONLY") + rows1 = await cursor.fetchall() + print("Query 1:", rows1) + + # Re-execute with different SQL on same cursor + await cursor.execute("SELECT DEPTNO, DEPTNAME FROM DEPARTMENT FETCH FIRST 2 ROWS ONLY") + rows2 = await cursor.fetchall() + print("Query 2:", rows2) + + # Re-prepare with bind_param + await cursor.prepare("SELECT ID, NAME FROM STAFF WHERE ID = ?") + await cursor.bind_param(1, 30) + await cursor.execute() + row = await cursor.fetchone() + print("Query 3 (prepared):", row) + + await cursor.close() + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#Query 1: [(10, 'Sanders'), (20, 'Pernal')] +#Query 2: [('A00', 'SPIFFY COMPUTER SERVICE DIV.'), ('B01', 'PLANNING')] +#Query 3 (prepared): (30, 'Marenghi') +#__ZOS_EXPECTED__ +#Query 1: [(10, 'Sanders'), (20, 'Pernal')] +#Query 2: [('A00', 'SPIFFY COMPUTER SERVICE DIV.'), ('B01', 'PLANNING')] +#Query 3 (prepared): (30, 'Marenghi') +#__SYSTEMI_EXPECTED__ +#Query 1: [(10, 'Sanders'), (20, 'Pernal')] +#Query 2: [('A00', 'SPIFFY COMPUTER SERVICE DIV.'), ('B01', 'PLANNING')] +#Query 3 (prepared): (30, 'Marenghi') +#__IDS_EXPECTED__ +#Query 1: [(10, 'Sanders'), (20, 'Pernal')] +#Query 2: [('A00', 'SPIFFY COMPUTER SERVICE DIV.'), ('B01', 'PLANNING')] +#Query 3 (prepared): (30, 'Marenghi') diff --git a/asyncio_testsuite/test_30_sync_vs_async.py b/asyncio_testsuite/test_30_sync_vs_async.py new file mode 100644 index 00000000..e7917ca0 --- /dev/null +++ b/asyncio_testsuite/test_30_sync_vs_async.py @@ -0,0 +1,97 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db_dbi +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_30_sync_vs_async(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_30) + + def run_test_30(self): + def run_sync(): + print("--- Sync ---") + conn = ibm_db_dbi.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = conn.cursor() + + # Simple query + cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 3 ROWS ONLY") + rows = cursor.fetchall() + print("Sync fetchall:", rows) + + # Prepare + bind_param + execute + cursor.prepare("SELECT ID, NAME FROM STAFF WHERE ID = ?") + cursor.bind_param(1, 20) + cursor.execute() + row = cursor.fetchone() + print("Sync bind_param result:", row) + + cursor.close() + conn.close() + + async def run_async(): + print("--- Async ---") + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + cursor = await conn.cursor() + + # Simple query + await cursor.execute("SELECT ID, NAME FROM STAFF FETCH FIRST 3 ROWS ONLY") + rows = await cursor.fetchall() + print("Async fetchall:", rows) + + # Prepare + bind_param + execute + await cursor.prepare("SELECT ID, NAME FROM STAFF WHERE ID = ?") + await cursor.bind_param(1, 20) + await cursor.execute() + row = await cursor.fetchone() + print("Async bind_param result:", row) + + await cursor.close() + await conn.close() + + run_sync() + asyncio.run(run_async()) + +#__END__ +#__LUW_EXPECTED__ +#--- Sync --- +#Sync fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Sync bind_param result: (20, 'Pernal') +#--- Async --- +#Async fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Async bind_param result: (20, 'Pernal') +#__ZOS_EXPECTED__ +#--- Sync --- +#Sync fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Sync bind_param result: (20, 'Pernal') +#--- Async --- +#Async fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Async bind_param result: (20, 'Pernal') +#__SYSTEMI_EXPECTED__ +#--- Sync --- +#Sync fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Sync bind_param result: (20, 'Pernal') +#--- Async --- +#Async fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Async bind_param result: (20, 'Pernal') +#__IDS_EXPECTED__ +#--- Sync --- +#Sync fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Sync bind_param result: (20, 'Pernal') +#--- Async --- +#Async fetchall: [(10, 'Sanders'), (20, 'Pernal'), (30, 'Marenghi')] +#Async bind_param result: (20, 'Pernal') diff --git a/asyncio_testsuite/test_31_async_sparray_cardinalities.py b/asyncio_testsuite/test_31_async_sparray_cardinalities.py new file mode 100644 index 00000000..43a8d957 --- /dev/null +++ b/asyncio_testsuite/test_31_async_sparray_cardinalities.py @@ -0,0 +1,274 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from datetime import date, time, datetime +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_31_async_sparray_cardinalities(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_31) + + def run_test_31(self): + ARRAY_PROCS = [ + ("int_array", "array_int12", [1, 2, 3, 4, 5, None]), + ("sint_array", "array_sint12", [7, 512, -29000, 32000]), + ("bint_array", "array_bint12", [1234567890123, None, -9876543210]), + ("float_array", "array_float12", [1.1, 2.2, 3.3]), + ("double_array", "array_double12", [10.5, 20.25, 30.75]), + ("real_array", "array_real12", [0.5, 1.5, 2.5]), + ("decfloat16_array", "array_decfloat1612", [1.23, None, 4.56, None]), + ("decfloat34_array", "array_decfloat3412", [1234567890.1234, None]), + ("dec_array", "array_dec12", [12.34, None, 56.78]), + ("time_array", "array_time12", [time(12, 20, 30), time(13, 30, 45)]), + ("date_array", "array_date12", [date(2025, 1, 1), date(2025, 12, 31)]), + ("ts_array", "array_ts12", [b'1981-07-08 10:42:34.000010', None, + b'1982-07-08 10:42:34.000010']), + ("ts_array", "array_ts12", [datetime(1989, 2, 12, 23, 55, 59, 342380), + datetime(1990, 2, 12, 23, 55, 59, 342380)]), + ("char_array", "array_char12", ["abc", "defg", "jkl"]), + ("char_array", "array_char12", [b'abc', b'defg']), + ("vc_array", "array_vc12", ["hello", "world"]), + ("vc_array", "array_vc12", [b'hello', b'world']), + ("vcfbd_array", "array_vcfbd12", [b'abc', b'dog', b'deadbeef', None, b'foobar']), + ("clob_array", "array_clob12", [b'long text here', b'another clob']), + ("blob_array", "array_blob12", [b"binarydata", b"morebytes", None, b'abc']), + ] + + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + passed = 0 + failed = 0 + skipped = 0 + + for type_name, proc_name, input_array in ARRAY_PROCS: + cursor = await conn.cursor() + try: + sql = "CALL %s.%s(?,?)" % (config.user.upper(), proc_name.upper()) + await cursor.prepare(sql) + + await cursor.bind_param(1, input_array, ibm_db.SQL_PARAM_INPUT) + await cursor.bind_param(2, 0, ibm_db.SQL_PARAM_OUTPUT, ibm_db.SQL_INTEGER) + + await cursor.execute() + result = await cursor.fetch_callproc() + + # result = (stmt, array_out, cardinality_out) + cardinality = result[2] + non_null = len([x for x in input_array if x is not None]) + print("%-25s %-20s input=%r" % (proc_name, type_name, input_array)) + print(" -> array_out=%r cardinality=%s" % (result[1], cardinality)) + + if cardinality == non_null or cardinality == len(input_array): + passed += 1 + else: + print("*** UNEXPECTED cardinality: got %s, expected ~%s" % (cardinality, non_null)) + failed += 1 + except Exception as e: + err_str = str(e) + if 'SQL0440N' in err_str or 'CLI0102E' in err_str: + print(" %-25s %-20s SKIP: procedure not available" % (proc_name, type_name)) + skipped += 1 + else: + print(" %-25s %-20s ERROR: %s" % (proc_name, type_name, e)) + failed += 1 + finally: + await cursor.close() + + await conn.close() + total = len(ARRAY_PROCS) + print("\nResults: %d passed, %d skipped, %d failed out of %d" % (passed, skipped, failed, total)) + if failed == 0: + print("PASSED" if passed > 0 else "ALL SKIPPED") + else: + print("FAILED") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#array_int12 int_array input=[1, 2, 3, 4, 5, None] +# -> array_out=[1, 2, 3, 4, 5, None] cardinality=6 +#array_sint12 sint_array input=[7, 512, -29000, 32000] +# -> array_out=[7, 512, -29000, 32000] cardinality=4 +#array_bint12 bint_array input=[1234567890123, None, -9876543210] +# -> array_out=[1234567890123, None, -9876543210] cardinality=3 +#array_float12 float_array input=[1.1, 2.2, 3.3] +# -> array_out=[1.1, 2.2, 3.3] cardinality=3 +#array_double12 double_array input=[10.5, 20.25, 30.75] +# -> array_out=[10.5, 20.25, 30.75] cardinality=3 +#array_real12 real_array input=[0.5, 1.5, 2.5] +# -> array_out=[0.5, 1.5, 2.5] cardinality=3 +#array_decfloat1612 decfloat16_array input=[1.23, None, 4.56, None] +# -> array_out=[1.23, None, 4.56, None] cardinality=4 +#array_decfloat3412 decfloat34_array input=[1234567890.1234, None] +# -> array_out=[1234567890.1234, None] cardinality=2 +#array_dec12 dec_array input=[12.34, None, 56.78] +# -> array_out=[12.34, None, 56.78] cardinality=3 +#array_time12 time_array input=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +# -> array_out=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] cardinality=2 +#array_date12 date_array input=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +# -> array_out=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] cardinality=2 +#array_ts12 ts_array input=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] +# -> array_out=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] cardinality=3 +#array_ts12 ts_array input=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +# -> array_out=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] cardinality=2 +#array_char12 char_array input=['abc', 'defg', 'jkl'] +# -> array_out=['abc', 'defg', 'jkl'] cardinality=3 +#array_char12 char_array input=[b'abc', b'defg'] +# -> array_out=[b'abc', b'defg'] cardinality=2 +#array_vc12 vc_array input=['hello', 'world'] +# -> array_out=['hello', 'world'] cardinality=2 +#array_vc12 vc_array input=[b'hello', b'world'] +# -> array_out=[b'hello', b'world'] cardinality=2 +#array_vcfbd12 vcfbd_array input=[b'abc', b'dog', b'deadbeef', None, b'foobar'] +# -> array_out=[b'abc', b'dog', b'deadbeef', None, b'foobar'] cardinality=5 +#array_clob12 clob_array input=[b'long text here', b'another clob'] +# -> array_out=[b'long text here', b'another clob'] cardinality=2 +#array_blob12 blob_array input=[b'binarydata', b'morebytes', None, b'abc'] +# -> array_out=[b'binarydata', b'morebytes', None, b'abc'] cardinality=4 +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__ZOS_EXPECTED__ +#array_int12 int_array input=[1, 2, 3, 4, 5, None] +# -> array_out=[1, 2, 3, 4, 5, None] cardinality=6 +#array_sint12 sint_array input=[7, 512, -29000, 32000] +# -> array_out=[7, 512, -29000, 32000] cardinality=4 +#array_bint12 bint_array input=[1234567890123, None, -9876543210] +# -> array_out=[1234567890123, None, -9876543210] cardinality=3 +#array_float12 float_array input=[1.1, 2.2, 3.3] +# -> array_out=[1.1, 2.2, 3.3] cardinality=3 +#array_double12 double_array input=[10.5, 20.25, 30.75] +# -> array_out=[10.5, 20.25, 30.75] cardinality=3 +#array_real12 real_array input=[0.5, 1.5, 2.5] +# -> array_out=[0.5, 1.5, 2.5] cardinality=3 +#array_decfloat1612 decfloat16_array input=[1.23, None, 4.56, None] +# -> array_out=[1.23, None, 4.56, None] cardinality=4 +#array_decfloat3412 decfloat34_array input=[1234567890.1234, None] +# -> array_out=[1234567890.1234, None] cardinality=2 +#array_dec12 dec_array input=[12.34, None, 56.78] +# -> array_out=[12.34, None, 56.78] cardinality=3 +#array_time12 time_array input=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +# -> array_out=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] cardinality=2 +#array_date12 date_array input=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +# -> array_out=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] cardinality=2 +#array_ts12 ts_array input=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] +# -> array_out=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] cardinality=3 +#array_ts12 ts_array input=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +# -> array_out=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] cardinality=2 +#array_char12 char_array input=['abc', 'defg', 'jkl'] +# -> array_out=['abc', 'defg', 'jkl'] cardinality=3 +#array_char12 char_array input=[b'abc', b'defg'] +# -> array_out=[b'abc', b'defg'] cardinality=2 +#array_vc12 vc_array input=['hello', 'world'] +# -> array_out=['hello', 'world'] cardinality=2 +#array_vc12 vc_array input=[b'hello', b'world'] +# -> array_out=[b'hello', b'world'] cardinality=2 +#array_vcfbd12 vcfbd_array input=[b'abc', b'dog', b'deadbeef', None, b'foobar'] +# -> array_out=[b'abc', b'dog', b'deadbeef', None, b'foobar'] cardinality=5 +#array_clob12 clob_array input=[b'long text here', b'another clob'] +# -> array_out=[b'long text here', b'another clob'] cardinality=2 +#array_blob12 blob_array input=[b'binarydata', b'morebytes', None, b'abc'] +# -> array_out=[b'binarydata', b'morebytes', None, b'abc'] cardinality=4 +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__SYSTEMI_EXPECTED__ +#array_int12 int_array input=[1, 2, 3, 4, 5, None] +# -> array_out=[1, 2, 3, 4, 5, None] cardinality=6 +#array_sint12 sint_array input=[7, 512, -29000, 32000] +# -> array_out=[7, 512, -29000, 32000] cardinality=4 +#array_bint12 bint_array input=[1234567890123, None, -9876543210] +# -> array_out=[1234567890123, None, -9876543210] cardinality=3 +#array_float12 float_array input=[1.1, 2.2, 3.3] +# -> array_out=[1.1, 2.2, 3.3] cardinality=3 +#array_double12 double_array input=[10.5, 20.25, 30.75] +# -> array_out=[10.5, 20.25, 30.75] cardinality=3 +#array_real12 real_array input=[0.5, 1.5, 2.5] +# -> array_out=[0.5, 1.5, 2.5] cardinality=3 +#array_decfloat1612 decfloat16_array input=[1.23, None, 4.56, None] +# -> array_out=[1.23, None, 4.56, None] cardinality=4 +#array_decfloat3412 decfloat34_array input=[1234567890.1234, None] +# -> array_out=[1234567890.1234, None] cardinality=2 +#array_dec12 dec_array input=[12.34, None, 56.78] +# -> array_out=[12.34, None, 56.78] cardinality=3 +#array_time12 time_array input=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +# -> array_out=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] cardinality=2 +#array_date12 date_array input=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +# -> array_out=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] cardinality=2 +#array_ts12 ts_array input=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] +# -> array_out=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] cardinality=3 +#array_ts12 ts_array input=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +# -> array_out=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] cardinality=2 +#array_char12 char_array input=['abc', 'defg', 'jkl'] +# -> array_out=['abc', 'defg', 'jkl'] cardinality=3 +#array_char12 char_array input=[b'abc', b'defg'] +# -> array_out=[b'abc', b'defg'] cardinality=2 +#array_vc12 vc_array input=['hello', 'world'] +# -> array_out=['hello', 'world'] cardinality=2 +#array_vc12 vc_array input=[b'hello', b'world'] +# -> array_out=[b'hello', b'world'] cardinality=2 +#array_vcfbd12 vcfbd_array input=[b'abc', b'dog', b'deadbeef', None, b'foobar'] +# -> array_out=[b'abc', b'dog', b'deadbeef', None, b'foobar'] cardinality=5 +#array_clob12 clob_array input=[b'long text here', b'another clob'] +# -> array_out=[b'long text here', b'another clob'] cardinality=2 +#array_blob12 blob_array input=[b'binarydata', b'morebytes', None, b'abc'] +# -> array_out=[b'binarydata', b'morebytes', None, b'abc'] cardinality=4 +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__IDS_EXPECTED__ +#array_int12 int_array input=[1, 2, 3, 4, 5, None] +# -> array_out=[1, 2, 3, 4, 5, None] cardinality=6 +#array_sint12 sint_array input=[7, 512, -29000, 32000] +# -> array_out=[7, 512, -29000, 32000] cardinality=4 +#array_bint12 bint_array input=[1234567890123, None, -9876543210] +# -> array_out=[1234567890123, None, -9876543210] cardinality=3 +#array_float12 float_array input=[1.1, 2.2, 3.3] +# -> array_out=[1.1, 2.2, 3.3] cardinality=3 +#array_double12 double_array input=[10.5, 20.25, 30.75] +# -> array_out=[10.5, 20.25, 30.75] cardinality=3 +#array_real12 real_array input=[0.5, 1.5, 2.5] +# -> array_out=[0.5, 1.5, 2.5] cardinality=3 +#array_decfloat1612 decfloat16_array input=[1.23, None, 4.56, None] +# -> array_out=[1.23, None, 4.56, None] cardinality=4 +#array_decfloat3412 decfloat34_array input=[1234567890.1234, None] +# -> array_out=[1234567890.1234, None] cardinality=2 +#array_dec12 dec_array input=[12.34, None, 56.78] +# -> array_out=[12.34, None, 56.78] cardinality=3 +#array_time12 time_array input=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +# -> array_out=[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] cardinality=2 +#array_date12 date_array input=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +# -> array_out=[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] cardinality=2 +#array_ts12 ts_array input=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] +# -> array_out=[b'1981-07-08 10:42:34.000010', None, b'1982-07-08 10:42:34.000010'] cardinality=3 +#array_ts12 ts_array input=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +# -> array_out=[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] cardinality=2 +#array_char12 char_array input=['abc', 'defg', 'jkl'] +# -> array_out=['abc', 'defg', 'jkl'] cardinality=3 +#array_char12 char_array input=[b'abc', b'defg'] +# -> array_out=[b'abc', b'defg'] cardinality=2 +#array_vc12 vc_array input=['hello', 'world'] +# -> array_out=['hello', 'world'] cardinality=2 +#array_vc12 vc_array input=[b'hello', b'world'] +# -> array_out=[b'hello', b'world'] cardinality=2 +#array_vcfbd12 vcfbd_array input=[b'abc', b'dog', b'deadbeef', None, b'foobar'] +# -> array_out=[b'abc', b'dog', b'deadbeef', None, b'foobar'] cardinality=5 +#array_clob12 clob_array input=[b'long text here', b'another clob'] +# -> array_out=[b'long text here', b'another clob'] cardinality=2 +#array_blob12 blob_array input=[b'binarydata', b'morebytes', None, b'abc'] +# -> array_out=[b'binarydata', b'morebytes', None, b'abc'] cardinality=4 +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED diff --git a/asyncio_testsuite/test_32_async_sparray_computations.py b/asyncio_testsuite/test_32_async_sparray_computations.py new file mode 100644 index 00000000..ea10615b --- /dev/null +++ b/asyncio_testsuite/test_32_async_sparray_computations.py @@ -0,0 +1,274 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from datetime import date, time, datetime +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_32_async_sparray_computations(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_32) + + def run_test_32(self): + # (type_name, proc_name, input_val[, output_template]) + SCALAR_PROCS = [ + ("int_array", "array_int22", 7), + ("sint_array", "array_sint22", 10), + ("bint_array", "array_bint22", 12345), + ("float_array", "array_float22", 3.14), + ("double_array", "array_double22", 20.25), + ("real_array", "array_real22", 1.5), + ("decfloat16_array", "array_decflt1622", 4.56), + ("decfloat34_array", "array_decfloat3422", 123456.1234), + ("dec_array", "array_dec22", 56.78), + ("time_array", "array_time22", time(12, 20, 30)), + ("date_array", "array_date22", date(2025, 1, 1)), + ("ts_array", "array_ts22", datetime(1989, 2, 12, 23, 55, 59, 342380)), + ("ts_array", "array_ts22", b'1989-02-12 23:55:59.342380'), + ("char_array", "array_char22", "HelloWorld"), + ("char_array", "array_char22", b'HelloWorld'), + ("vc_array", "array_vc22", "basketball"), + ("vc_array", "array_vc22", b'basketball'), + ("vcfbd_array", "array_vcfbd22", b'foobar'), + ("clob_array", "array_clob22", 100, [b'x' * 20] * 4), + ("blob_array", "array_blob22", 200, [b'x' * 20] * 4), + ] + + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + passed = 0 + failed = 0 + skipped = 0 + + for entry in SCALAR_PROCS: + type_name, proc_name, input_val = entry[0], entry[1], entry[2] + out_template = entry[3] if len(entry) > 3 else None + cursor = await conn.cursor() + try: + sql = "CALL %s.%s(?, ?)" % (config.user.upper(), proc_name.upper()) + await cursor.prepare(sql) + + output_array = out_template if out_template is not None else [input_val] * 4 + + await cursor.bind_param(1, input_val, ibm_db.SQL_PARAM_INPUT) + await cursor.bind_param(2, output_array, ibm_db.SQL_PARAM_OUTPUT) + await cursor.execute() + result = await cursor.fetch_callproc() + # result = (stmt, scalar_in, output_array) + out_arr = result[2] + print("%-25s %-20s input=%r" % (proc_name, type_name, input_val)) + print("-> output array=%r" % (out_arr,)) + + if isinstance(out_arr, list) and len(out_arr) > 0: + passed += 1 + else: + print("*** UNEXPECTED output: %r" % (out_arr,)) + failed += 1 + except Exception as e: + err_str = str(e) + if 'SQL0440N' in err_str or 'CLI0102E' in err_str: + print("%-25s %-20s SKIP: procedure not available" % (proc_name, type_name)) + skipped += 1 + else: + print("%-25s %-20s ERROR: %s" % (proc_name, type_name, e)) + failed += 1 + finally: + await cursor.close() + + await conn.close() + total = len(SCALAR_PROCS) + print("\nResults: %d passed, %d skipped, %d failed out of %d" % (passed, skipped, failed, total)) + if failed == 0: + print("PASSED" if passed > 0 else "ALL SKIPPED") + else: + print("FAILED") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#array_int22 int_array input=7 +#-> output array=[49, 343, 28, 2] +#array_sint22 sint_array input=10 +#-> output array=[100, 1000, 40, 5] +#array_bint22 bint_array input=12345 +#-> output array=[152399025, 1881365963625, 49380, 12340] +#array_float22 float_array input=3.14 +#-> output array=[9.8596, 30.959144000000002, 12.56, -1.8599999999999999] +#array_double22 double_array input=20.25 +#-> output array=[410.0625, 8303.765625, 81.0, 15.25] +#array_real22 real_array input=1.5 +#-> output array=[2.25, 3.375, 6.0, -3.5] +#array_decflt1622 decfloat16_array input=4.56 +#-> output array=[20.7936, 94.818816, 18.24, -0.44] +#array_decfloat3422 decfloat34_array input=123456.1234 +#-> output array=[15241414404.956028, 1881645937568789.0, 493824.4936, 123451.1234] +#array_dec22 dec_array input=56.78 +#-> output array=[3223.96, 183056.92, 227.12, 51.78] +#array_time22 time_array input=datetime.time(12, 20, 30) +#-> output array=[datetime.time(13, 21, 31), datetime.time(11, 19, 29), datetime.time(12, 20, 31), datetime.time(12, 20, 29)] +#array_date22 date_array input=datetime.date(2025, 1, 1) +#-> output array=[datetime.date(2026, 2, 2), datetime.date(2023, 11, 30), datetime.date(2025, 1, 2), datetime.date(2024, 12, 31)] +#array_ts22 ts_array input=datetime.datetime(1989, 2, 12, 23, 55, 59, 342380) +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_ts22 ts_array input=b'1989-02-12 23:55:59.342380' +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_char22 char_array input='HelloWorld' +#-> output array=['HelloWorld', 'HelloWorld', 'Hell', ''] +#array_char22 char_array input=b'HelloWorld' +#-> output array=[b'HelloWorld ', b'HelloWorld ', b'Hell ', b' '] +#array_vc22 vc_array input='basketball' +#-> output array=['basketballbasketball', 'basketballbasketball', 'bask', 'tball'] +#array_vc22 vc_array input=b'basketball' +#-> output array=[b'basketballbasketbal', b'basketballbasketbal', b'bask', b'tball'] +#array_vcfbd22 vcfbd_array input=b'foobar' +#-> output array=[b'foobarfoobar', b'foobarfoobarfoobar', b'foobar', b'oobarr'] +#array_clob22 clob_array input=100 +#-> output array=[b'101 ', b'102 ', b'103 ', b'104 '] +#array_blob22 blob_array input=200 +#-> output array=[b'201 ', b'202 ', b'203 ', b'204 '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__ZOS_EXPECTED__ +#array_int22 int_array input=7 +#-> output array=[49, 343, 28, 2] +#array_sint22 sint_array input=10 +#-> output array=[100, 1000, 40, 5] +#array_bint22 bint_array input=12345 +#-> output array=[152399025, 1881365963625, 49380, 12340] +#array_float22 float_array input=3.14 +#-> output array=[9.8596, 30.959144000000002, 12.56, -1.8599999999999999] +#array_double22 double_array input=20.25 +#-> output array=[410.0625, 8303.765625, 81.0, 15.25] +#array_real22 real_array input=1.5 +#-> output array=[2.25, 3.375, 6.0, -3.5] +#array_decflt1622 decfloat16_array input=4.56 +#-> output array=[20.7936, 94.818816, 18.24, -0.44] +#array_decfloat3422 decfloat34_array input=123456.1234 +#-> output array=[15241414404.956028, 1881645937568789.0, 493824.4936, 123451.1234] +#array_dec22 dec_array input=56.78 +#-> output array=[3223.96, 183056.92, 227.12, 51.78] +#array_time22 time_array input=datetime.time(12, 20, 30) +#-> output array=[datetime.time(13, 21, 31), datetime.time(11, 19, 29), datetime.time(12, 20, 31), datetime.time(12, 20, 29)] +#array_date22 date_array input=datetime.date(2025, 1, 1) +#-> output array=[datetime.date(2026, 2, 2), datetime.date(2023, 11, 30), datetime.date(2025, 1, 2), datetime.date(2024, 12, 31)] +#array_ts22 ts_array input=datetime.datetime(1989, 2, 12, 23, 55, 59, 342380) +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_ts22 ts_array input=b'1989-02-12 23:55:59.342380' +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_char22 char_array input='HelloWorld' +#-> output array=['HelloWorld', 'HelloWorld', 'Hell', ''] +#array_char22 char_array input=b'HelloWorld' +#-> output array=[b'HelloWorld ', b'HelloWorld ', b'Hell ', b' '] +#array_vc22 vc_array input='basketball' +#-> output array=['basketballbasketball', 'basketballbasketball', 'bask', 'tball'] +#array_vc22 vc_array input=b'basketball' +#-> output array=[b'basketballbasketbal', b'basketballbasketbal', b'bask', b'tball'] +#array_vcfbd22 vcfbd_array input=b'foobar' +#-> output array=[b'foobarfoobar', b'foobarfoobarfoobar', b'foobar', b'oobarr'] +#array_clob22 clob_array input=100 +#-> output array=[b'101 ', b'102 ', b'103 ', b'104 '] +#array_blob22 blob_array input=200 +#-> output array=[b'201 ', b'202 ', b'203 ', b'204 '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__SYSTEMI_EXPECTED__ +#array_int22 int_array input=7 +#-> output array=[49, 343, 28, 2] +#array_sint22 sint_array input=10 +#-> output array=[100, 1000, 40, 5] +#array_bint22 bint_array input=12345 +#-> output array=[152399025, 1881365963625, 49380, 12340] +#array_float22 float_array input=3.14 +#-> output array=[9.8596, 30.959144000000002, 12.56, -1.8599999999999999] +#array_double22 double_array input=20.25 +#-> output array=[410.0625, 8303.765625, 81.0, 15.25] +#array_real22 real_array input=1.5 +#-> output array=[2.25, 3.375, 6.0, -3.5] +#array_decflt1622 decfloat16_array input=4.56 +#-> output array=[20.7936, 94.818816, 18.24, -0.44] +#array_decfloat3422 decfloat34_array input=123456.1234 +#-> output array=[15241414404.956028, 1881645937568789.0, 493824.4936, 123451.1234] +#array_dec22 dec_array input=56.78 +#-> output array=[3223.96, 183056.92, 227.12, 51.78] +#array_time22 time_array input=datetime.time(12, 20, 30) +#-> output array=[datetime.time(13, 21, 31), datetime.time(11, 19, 29), datetime.time(12, 20, 31), datetime.time(12, 20, 29)] +#array_date22 date_array input=datetime.date(2025, 1, 1) +#-> output array=[datetime.date(2026, 2, 2), datetime.date(2023, 11, 30), datetime.date(2025, 1, 2), datetime.date(2024, 12, 31)] +#array_ts22 ts_array input=datetime.datetime(1989, 2, 12, 23, 55, 59, 342380) +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_ts22 ts_array input=b'1989-02-12 23:55:59.342380' +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_char22 char_array input='HelloWorld' +#-> output array=['HelloWorld', 'HelloWorld', 'Hell', ''] +#array_char22 char_array input=b'HelloWorld' +#-> output array=[b'HelloWorld ', b'HelloWorld ', b'Hell ', b' '] +#array_vc22 vc_array input='basketball' +#-> output array=['basketballbasketball', 'basketballbasketball', 'bask', 'tball'] +#array_vc22 vc_array input=b'basketball' +#-> output array=[b'basketballbasketbal', b'basketballbasketbal', b'bask', b'tball'] +#array_vcfbd22 vcfbd_array input=b'foobar' +#-> output array=[b'foobarfoobar', b'foobarfoobarfoobar', b'foobar', b'oobarr'] +#array_clob22 clob_array input=100 +#-> output array=[b'101 ', b'102 ', b'103 ', b'104 '] +#array_blob22 blob_array input=200 +#-> output array=[b'201 ', b'202 ', b'203 ', b'204 '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__IDS_EXPECTED__ +#array_int22 int_array input=7 +#-> output array=[49, 343, 28, 2] +#array_sint22 sint_array input=10 +#-> output array=[100, 1000, 40, 5] +#array_bint22 bint_array input=12345 +#-> output array=[152399025, 1881365963625, 49380, 12340] +#array_float22 float_array input=3.14 +#-> output array=[9.8596, 30.959144000000002, 12.56, -1.8599999999999999] +#array_double22 double_array input=20.25 +#-> output array=[410.0625, 8303.765625, 81.0, 15.25] +#array_real22 real_array input=1.5 +#-> output array=[2.25, 3.375, 6.0, -3.5] +#array_decflt1622 decfloat16_array input=4.56 +#-> output array=[20.7936, 94.818816, 18.24, -0.44] +#array_decfloat3422 decfloat34_array input=123456.1234 +#-> output array=[15241414404.956028, 1881645937568789.0, 493824.4936, 123451.1234] +#array_dec22 dec_array input=56.78 +#-> output array=[3223.96, 183056.92, 227.12, 51.78] +#array_time22 time_array input=datetime.time(12, 20, 30) +#-> output array=[datetime.time(13, 21, 31), datetime.time(11, 19, 29), datetime.time(12, 20, 31), datetime.time(12, 20, 29)] +#array_date22 date_array input=datetime.date(2025, 1, 1) +#-> output array=[datetime.date(2026, 2, 2), datetime.date(2023, 11, 30), datetime.date(2025, 1, 2), datetime.date(2024, 12, 31)] +#array_ts22 ts_array input=datetime.datetime(1989, 2, 12, 23, 55, 59, 342380) +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_ts22 ts_array input=b'1989-02-12 23:55:59.342380' +#-> output array=[datetime.datetime(1990, 3, 14, 0, 57, 0, 342380), datetime.datetime(1988, 1, 11, 22, 54, 58, 342380), datetime.datetime(1989, 2, 13, 23, 56, 0, 342380), datetime.datetime(1989, 2, 11, 23, 55, 58, 342380)] +#array_char22 char_array input='HelloWorld' +#-> output array=['HelloWorld', 'HelloWorld', 'Hell', ''] +#array_char22 char_array input=b'HelloWorld' +#-> output array=[b'HelloWorld ', b'HelloWorld ', b'Hell ', b' '] +#array_vc22 vc_array input='basketball' +#-> output array=['basketballbasketball', 'basketballbasketball', 'bask', 'tball'] +#array_vc22 vc_array input=b'basketball' +#-> output array=[b'basketballbasketbal', b'basketballbasketbal', b'bask', b'tball'] +#array_vcfbd22 vcfbd_array input=b'foobar' +#-> output array=[b'foobarfoobar', b'foobarfoobarfoobar', b'foobar', b'oobarr'] +#array_clob22 clob_array input=100 +#-> output array=[b'101 ', b'102 ', b'103 ', b'104 '] +#array_blob22 blob_array input=200 +#-> output array=[b'201 ', b'202 ', b'203 ', b'204 '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED diff --git a/asyncio_testsuite/test_33_async_sparray_inout_computations.py b/asyncio_testsuite/test_33_async_sparray_inout_computations.py new file mode 100644 index 00000000..75fcab6d --- /dev/null +++ b/asyncio_testsuite/test_33_async_sparray_inout_computations.py @@ -0,0 +1,354 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from datetime import date, time, datetime +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_33_async_sparray_inout_computations(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_33) + + def run_test_33(self): + ARRAY_PROCS = [ + ("int_array", "array_int31", [1, 2, 3, 4, 5]), + ("sint_array", "array_sint31", [7, 512, -29000, 32000]), + ("bint_array", "array_bint31", [1234567890123, None, -9876543210]), + ("float_array", "array_float31", [1.1, 2.2, 3.3]), + ("double_array", "array_double31", [10.5, 20.25, 30.75]), + ("real_array", "array_real31", [0.5, 1.5, 2.5]), + ("decfloat16_array", "array_decflt1631", [1.23, None, 4.56, None]), + ("decfloat34_array", "array_decfloat3431", [12345678.1234, None]), + ("dec_array", "array_dec31", [12.34, None, 56.78]), + ("time_array", "array_time31", [time(12, 20, 30), time(13, 30, 45)]), + ("date_array", "array_date31", [date(2025, 1, 1), date(2025, 12, 31)]), + ("ts_array", "array_ts31", [datetime(1989, 2, 12, 23, 55, 59, 342380), + datetime(1990, 2, 12, 23, 55, 59, 342380)]), + ("ts_array", "array_ts31", [b'1981-07-08 10:42:34.000010', + b'1982-07-08 10:42:34.000010']), + ("char_array", "array_char31", ["abc", "defg"]), + ("vc_array", "array_vc31", ["hello", "world"]), + ("vc_array", "array_vc31", [b'hello', b'world']), + ("vcfbd_array", "array_vcfbd31", [b'basketball', b'baseball', b'football', + b'pingpong', b'lacrosse']), + ("clob_array", "array_clob31", [b'long text here', b'another clob']), + ("blob_array", "array_blob31", [b"binarydata", b"morebytes", b'abc']), + ("char_array", "array_char31", [b'abc', None, b'defg']), + ] + + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + passed = 0 + failed = 0 + skipped = 0 + + for type_name, proc_name, input_array in ARRAY_PROCS: + cursor = await conn.cursor() + try: + sql = "CALL %s.%s(?)" % (config.user.upper(), proc_name.upper()) + await cursor.prepare(sql) + + await cursor.bind_param(1, input_array, ibm_db.SQL_PARAM_INPUT_OUTPUT) + + await cursor.execute() + result = await cursor.fetch_callproc() + + # result = (stmt, inout_array) + out_arr = result[1] + print("%-25s %-20s" % (proc_name, type_name)) + print("input =%r" % (input_array,)) + print("output=%r" % (out_arr,)) + + if isinstance(out_arr, list) and len(out_arr) > 0: + passed += 1 + else: + print(" *** UNEXPECTED output: %r" % (out_arr,)) + failed += 1 + except Exception as e: + err_str = str(e) + if 'SQL0440N' in err_str or 'CLI0102E' in err_str: + print("%-25s %-20s SKIP: procedure not available" % (proc_name, type_name)) + skipped += 1 + else: + print("%-25s %-20s ERROR: %s" % (proc_name, type_name, e)) + failed += 1 + finally: + await cursor.close() + + await conn.close() + total = len(ARRAY_PROCS) + print("\nResults: %d passed, %d skipped, %d failed out of %d" % (passed, skipped, failed, total)) + if failed == 0: + print("PASSED" if passed > 0 else "ALL SKIPPED") + else: + print("FAILED") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#array_int31 int_array +#input =[1, 2, 3, 4, 5] +#output=[2, 3, 4, 5, 6] +#array_sint31 sint_array +#input =[7, 512, -29000, 32000] +#output=[8, 513, -28999, 32001] +#array_bint31 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890124, None, -9876543209] +#array_float31 float_array +#input =[1.1, 2.2, 3.3] +#output=[2.43, 3.5300000000000002, 4.63] +#array_double31 double_array +#input =[10.5, 20.25, 30.75] +#output=[11.83, 21.58, 32.08] +#array_real31 real_array +#input =[0.5, 1.5, 2.5] +#output=[1.8300000429153442, 2.8299999237060547, 3.8299999237060547] +#array_decflt1631 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[2.56, None, 5.89, None] +#array_decfloat3431 decfloat34_array +#input =[12345678.1234, None] +#output=[12345679.4534, None] +#array_dec31 dec_array +#input =[12.34, None, 56.78] +#output=[13.67, None, 58.11] +#array_time31 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(11, 21, 29), datetime.time(12, 31, 44)] +#array_date31 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2025, 12, 2), datetime.date(2026, 12, 1)] +#array_ts31 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1990, 1, 13, 22, 56, 58, 342380), datetime.datetime(1991, 1, 13, 22, 56, 58, 342380)] +#array_ts31 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1982, 6, 9, 9, 43, 33, 10), datetime.datetime(1983, 6, 9, 9, 43, 33, 10)] +#array_char31 char_array +#input =['abc', 'defg'] +#output=['abc - a', 'defg - d'] +#array_vc31 vc_array +#input =['hello', 'world'] +#output=['hello - h', 'world - w'] +#array_vc31 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - h', b'world - w'] +#array_vcfbd31 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - b', b'baseball - b', b'football - f', b'pingpong - p', b'lacrosse - l'] +#array_clob31 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'another clob', b'long text here'] +#array_blob31 blob_array +#input =[b'binarydata', b'morebytes', b'abc'] +#output=[b'abc', b'morebytes', b'binarydata'] +#array_char31 char_array +#input =[b'abc', None, b'defg'] +#output=[b'abc - a ', None, b'defg - d '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__ZOS_EXPECTED__ +#array_int31 int_array +#input =[1, 2, 3, 4, 5] +#output=[2, 3, 4, 5, 6] +#array_sint31 sint_array +#input =[7, 512, -29000, 32000] +#output=[8, 513, -28999, 32001] +#array_bint31 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890124, None, -9876543209] +#array_float31 float_array +#input =[1.1, 2.2, 3.3] +#output=[2.43, 3.5300000000000002, 4.63] +#array_double31 double_array +#input =[10.5, 20.25, 30.75] +#output=[11.83, 21.58, 32.08] +#array_real31 real_array +#input =[0.5, 1.5, 2.5] +#output=[1.8300000429153442, 2.8299999237060547, 3.8299999237060547] +#array_decflt1631 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[2.56, None, 5.89, None] +#array_decfloat3431 decfloat34_array +#input =[12345678.1234, None] +#output=[12345679.4534, None] +#array_dec31 dec_array +#input =[12.34, None, 56.78] +#output=[13.67, None, 58.11] +#array_time31 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(11, 21, 29), datetime.time(12, 31, 44)] +#array_date31 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2025, 12, 2), datetime.date(2026, 12, 1)] +#array_ts31 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1990, 1, 13, 22, 56, 58, 342380), datetime.datetime(1991, 1, 13, 22, 56, 58, 342380)] +#array_ts31 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1982, 6, 9, 9, 43, 33, 10), datetime.datetime(1983, 6, 9, 9, 43, 33, 10)] +#array_char31 char_array +#input =['abc', 'defg'] +#output=['abc - a', 'defg - d'] +#array_vc31 vc_array +#input =['hello', 'world'] +#output=['hello - h', 'world - w'] +#array_vc31 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - h', b'world - w'] +#array_vcfbd31 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - b', b'baseball - b', b'football - f', b'pingpong - p', b'lacrosse - l'] +#array_clob31 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'another clob', b'long text here'] +#array_blob31 blob_array +#input =[b'binarydata', b'morebytes', b'abc'] +#output=[b'abc', b'morebytes', b'binarydata'] +#array_char31 char_array +#input =[b'abc', None, b'defg'] +#output=[b'abc - a ', None, b'defg - d '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__SYSTEMI_EXPECTED__ +#array_int31 int_array +#input =[1, 2, 3, 4, 5] +#output=[2, 3, 4, 5, 6] +#array_sint31 sint_array +#input =[7, 512, -29000, 32000] +#output=[8, 513, -28999, 32001] +#array_bint31 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890124, None, -9876543209] +#array_float31 float_array +#input =[1.1, 2.2, 3.3] +#output=[2.43, 3.5300000000000002, 4.63] +#array_double31 double_array +#input =[10.5, 20.25, 30.75] +#output=[11.83, 21.58, 32.08] +#array_real31 real_array +#input =[0.5, 1.5, 2.5] +#output=[1.8300000429153442, 2.8299999237060547, 3.8299999237060547] +#array_decflt1631 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[2.56, None, 5.89, None] +#array_decfloat3431 decfloat34_array +#input =[12345678.1234, None] +#output=[12345679.4534, None] +#array_dec31 dec_array +#input =[12.34, None, 56.78] +#output=[13.67, None, 58.11] +#array_time31 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(11, 21, 29), datetime.time(12, 31, 44)] +#array_date31 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2025, 12, 2), datetime.date(2026, 12, 1)] +#array_ts31 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1990, 1, 13, 22, 56, 58, 342380), datetime.datetime(1991, 1, 13, 22, 56, 58, 342380)] +#array_ts31 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1982, 6, 9, 9, 43, 33, 10), datetime.datetime(1983, 6, 9, 9, 43, 33, 10)] +#array_char31 char_array +#input =['abc', 'defg'] +#output=['abc - a', 'defg - d'] +#array_vc31 vc_array +#input =['hello', 'world'] +#output=['hello - h', 'world - w'] +#array_vc31 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - h', b'world - w'] +#array_vcfbd31 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - b', b'baseball - b', b'football - f', b'pingpong - p', b'lacrosse - l'] +#array_clob31 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'another clob', b'long text here'] +#array_blob31 blob_array +#input =[b'binarydata', b'morebytes', b'abc'] +#output=[b'abc', b'morebytes', b'binarydata'] +#array_char31 char_array +#input =[b'abc', None, b'defg'] +#output=[b'abc - a ', None, b'defg - d '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__IDS_EXPECTED__ +#array_int31 int_array +#input =[1, 2, 3, 4, 5] +#output=[2, 3, 4, 5, 6] +#array_sint31 sint_array +#input =[7, 512, -29000, 32000] +#output=[8, 513, -28999, 32001] +#array_bint31 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890124, None, -9876543209] +#array_float31 float_array +#input =[1.1, 2.2, 3.3] +#output=[2.43, 3.5300000000000002, 4.63] +#array_double31 double_array +#input =[10.5, 20.25, 30.75] +#output=[11.83, 21.58, 32.08] +#array_real31 real_array +#input =[0.5, 1.5, 2.5] +#output=[1.8300000429153442, 2.8299999237060547, 3.8299999237060547] +#array_decflt1631 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[2.56, None, 5.89, None] +#array_decfloat3431 decfloat34_array +#input =[12345678.1234, None] +#output=[12345679.4534, None] +#array_dec31 dec_array +#input =[12.34, None, 56.78] +#output=[13.67, None, 58.11] +#array_time31 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(11, 21, 29), datetime.time(12, 31, 44)] +#array_date31 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2025, 12, 2), datetime.date(2026, 12, 1)] +#array_ts31 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1990, 1, 13, 22, 56, 58, 342380), datetime.datetime(1991, 1, 13, 22, 56, 58, 342380)] +#array_ts31 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1982, 6, 9, 9, 43, 33, 10), datetime.datetime(1983, 6, 9, 9, 43, 33, 10)] +#array_char31 char_array +#input =['abc', 'defg'] +#output=['abc - a', 'defg - d'] +#array_vc31 vc_array +#input =['hello', 'world'] +#output=['hello - h', 'world - w'] +#array_vc31 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - h', b'world - w'] +#array_vcfbd31 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - b', b'baseball - b', b'football - f', b'pingpong - p', b'lacrosse - l'] +#array_clob31 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'another clob', b'long text here'] +#array_blob31 blob_array +#input =[b'binarydata', b'morebytes', b'abc'] +#output=[b'abc', b'morebytes', b'binarydata'] +#array_char31 char_array +#input =[b'abc', None, b'defg'] +#output=[b'abc - a ', None, b'defg - d '] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED diff --git a/asyncio_testsuite/test_34_async_sparray_input_output_computations.py b/asyncio_testsuite/test_34_async_sparray_input_output_computations.py new file mode 100644 index 00000000..cb30e197 --- /dev/null +++ b/asyncio_testsuite/test_34_async_sparray_input_output_computations.py @@ -0,0 +1,355 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from datetime import date, time, datetime +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_34_async_sparray_input_output_computations(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_34) + + def run_test_34(self): + ARRAY_PROCS = [ + ("int_array", "array_int41", [1, 2, 3, 4, 5]), + ("sint_array", "array_sint41", [7, 512, -29000, 32000]), + ("bint_array", "array_bint41", [1234567890123, None, -9876543210]), + ("float_array", "array_float41", [1.1, 2.2, 3.3]), + ("double_array", "array_double41", [10.5, 20.25, 30.75]), + ("real_array", "array_real41", [0.5, 1.5, 2.5]), + ("decfloat16_array", "array_decflt1641", [1.23, None, 4.56, None]), + ("decfloat34_array", "array_decfloat3441", [12345678.1234, None]), + ("dec_array", "array_dec41", [12.34, None, 56.78]), + ("time_array", "array_time41", [time(12, 20, 30), time(13, 30, 45)]), + ("date_array", "array_date41", [date(2025, 1, 1), date(2025, 12, 31)]), + ("ts_array", "array_ts41", [datetime(1989, 2, 12, 23, 55, 59, 342380), + datetime(1990, 2, 12, 23, 55, 59, 342380)]), + ("ts_array", "array_ts41", [b'1981-07-08 10:42:34.000010', + b'1982-07-08 10:42:34.000010']), + ("char_array", "array_char41", ["abc", "defg"]), + ("char_array", "array_char41", [b'abc', b'defg']), + ("vc_array", "array_vc41", ["hello", "world"]), + ("vc_array", "array_vc41", [b'hello', b'world']), + ("vcfbd_array", "array_vcfbd41", [b'basketball', b'baseball', b'football', + b'pingpong', b'lacrosse']), + ("blob_array", "array_blob41", [b"binarydata", b"morebytes", None, b'abc']), + ("clob_array", "array_clob41", [b'long text here', b'another clob']), + ] + + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + passed = 0 + failed = 0 + skipped = 0 + + for type_name, proc_name, input_array in ARRAY_PROCS: + cursor = await conn.cursor() + try: + sql = "CALL %s.%s(?, ?)" % (config.user.upper(), proc_name.upper()) + await cursor.prepare(sql) + + await cursor.bind_param(1, input_array, ibm_db.SQL_PARAM_INPUT) + await cursor.bind_param(2, input_array, ibm_db.SQL_PARAM_OUTPUT) + + await cursor.execute() + result = await cursor.fetch_callproc() + + # result = (stmt, input_array_echo, output_array) + out_arr = result[2] + print("%-25s %-20s" % (proc_name, type_name)) + print("input =%r" % (input_array,)) + print("output=%r" % (out_arr,)) + + if isinstance(out_arr, list) and len(out_arr) > 0: + passed += 1 + else: + print(" *** UNEXPECTED output: %r" % (out_arr,)) + failed += 1 + except Exception as e: + err_str = str(e) + if 'SQL0440N' in err_str or 'CLI0102E' in err_str: + print("%-25s %-20s SKIP: procedure not available" % (proc_name, type_name)) + skipped += 1 + else: + print("%-25s %-20s ERROR: %s" % (proc_name, type_name, e)) + failed += 1 + finally: + await cursor.close() + + await conn.close() + total = len(ARRAY_PROCS) + print("\nResults: %d passed, %d skipped, %d failed out of %d" % (passed, skipped, failed, total)) + if failed == 0: + print("PASSED" if passed > 0 else "ALL SKIPPED") + else: + print("FAILED") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#array_int41 int_array +#input =[1, 2, 3, 4, 5] +#output=[0, 1, 2, 3, 4] +#array_sint41 sint_array +#input =[7, 512, -29000, 32000] +#output=[6, 511, -29001, 31999] +#array_bint41 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890122, None, -9876543211] +#array_float41 float_array +#input =[1.1, 2.2, 3.3] +#output=[-0.5699999999999998, 0.5300000000000002, 1.63] +#array_double41 double_array +#input =[10.5, 20.25, 30.75] +#output=[8.83, 18.58, 29.08] +#array_real41 real_array +#input =[0.5, 1.5, 2.5] +#output=[-1.1699999570846558, -0.17000000178813934, 0.8299999833106995] +#array_decflt1641 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[-0.44, None, 2.89, None] +#array_decfloat3441 decfloat34_array +#input =[12345678.1234, None] +#output=[12345676.4534, None] +#array_dec41 dec_array +#input =[12.34, None, 56.78] +#output=[10.67, None, 55.11] +#array_time41 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(13, 19, 31), datetime.time(14, 29, 46)] +#array_date41 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2024, 1, 31), datetime.date(2025, 1, 30)] +#array_ts41 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1988, 3, 12, 0, 55, 0, 342380), datetime.datetime(1989, 3, 12, 0, 55, 0, 342380)] +#array_ts41 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1980, 8, 7, 11, 41, 35, 10), datetime.datetime(1981, 8, 7, 11, 41, 35, 10)] +#array_char41 char_array +#input =['abc', 'defg'] +#output=['abc - c', 'defg - g'] +#array_char41 char_array +#input =[b'abc', b'defg'] +#output=[b'abc - c ', b'defg - g '] +#array_vc41 vc_array +#input =['hello', 'world'] +#output=['hello - o', 'world - d'] +#array_vc41 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - o', b'world - d'] +#array_vcfbd41 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - l', b'baseball - l', b'football - l', b'pingpong - g', b'lacrosse - e'] +#array_blob41 blob_array +#input =[b'binarydata', b'morebytes', None, b'abc'] +#output=[b'binarydata', b'morebytes', None, b'abc'] +#array_clob41 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'long text here', b'another clob'] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__ZOS_EXPECTED__ +#array_int41 int_array +#input =[1, 2, 3, 4, 5] +#output=[0, 1, 2, 3, 4] +#array_sint41 sint_array +#input =[7, 512, -29000, 32000] +#output=[6, 511, -29001, 31999] +#array_bint41 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890122, None, -9876543211] +#array_float41 float_array +#input =[1.1, 2.2, 3.3] +#output=[-0.5699999999999998, 0.5300000000000002, 1.63] +#array_double41 double_array +#input =[10.5, 20.25, 30.75] +#output=[8.83, 18.58, 29.08] +#array_real41 real_array +#input =[0.5, 1.5, 2.5] +#output=[-1.1699999570846558, -0.17000000178813934, 0.8299999833106995] +#array_decflt1641 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[-0.44, None, 2.89, None] +#array_decfloat3441 decfloat34_array +#input =[12345678.1234, None] +#output=[12345676.4534, None] +#array_dec41 dec_array +#input =[12.34, None, 56.78] +#output=[10.67, None, 55.11] +#array_time41 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(13, 19, 31), datetime.time(14, 29, 46)] +#array_date41 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2024, 1, 31), datetime.date(2025, 1, 30)] +#array_ts41 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1988, 3, 12, 0, 55, 0, 342380), datetime.datetime(1989, 3, 12, 0, 55, 0, 342380)] +#array_ts41 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1980, 8, 7, 11, 41, 35, 10), datetime.datetime(1981, 8, 7, 11, 41, 35, 10)] +#array_char41 char_array +#input =['abc', 'defg'] +#output=['abc - c', 'defg - g'] +#array_char41 char_array +#input =[b'abc', b'defg'] +#output=[b'abc - c ', b'defg - g '] +#array_vc41 vc_array +#input =['hello', 'world'] +#output=['hello - o', 'world - d'] +#array_vc41 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - o', b'world - d'] +#array_vcfbd41 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - l', b'baseball - l', b'football - l', b'pingpong - g', b'lacrosse - e'] +#array_blob41 blob_array +#input =[b'binarydata', b'morebytes', None, b'abc'] +#output=[b'binarydata', b'morebytes', None, b'abc'] +#array_clob41 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'long text here', b'another clob'] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__SYSTEMI_EXPECTED__ +#array_int41 int_array +#input =[1, 2, 3, 4, 5] +#output=[0, 1, 2, 3, 4] +#array_sint41 sint_array +#input =[7, 512, -29000, 32000] +#output=[6, 511, -29001, 31999] +#array_bint41 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890122, None, -9876543211] +#array_float41 float_array +#input =[1.1, 2.2, 3.3] +#output=[-0.5699999999999998, 0.5300000000000002, 1.63] +#array_double41 double_array +#input =[10.5, 20.25, 30.75] +#output=[8.83, 18.58, 29.08] +#array_real41 real_array +#input =[0.5, 1.5, 2.5] +#output=[-1.1699999570846558, -0.17000000178813934, 0.8299999833106995] +#array_decflt1641 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[-0.44, None, 2.89, None] +#array_decfloat3441 decfloat34_array +#input =[12345678.1234, None] +#output=[12345676.4534, None] +#array_dec41 dec_array +#input =[12.34, None, 56.78] +#output=[10.67, None, 55.11] +#array_time41 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(13, 19, 31), datetime.time(14, 29, 46)] +#array_date41 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2024, 1, 31), datetime.date(2025, 1, 30)] +#array_ts41 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1988, 3, 12, 0, 55, 0, 342380), datetime.datetime(1989, 3, 12, 0, 55, 0, 342380)] +#array_ts41 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1980, 8, 7, 11, 41, 35, 10), datetime.datetime(1981, 8, 7, 11, 41, 35, 10)] +#array_char41 char_array +#input =['abc', 'defg'] +#output=['abc - c', 'defg - g'] +#array_char41 char_array +#input =[b'abc', b'defg'] +#output=[b'abc - c ', b'defg - g '] +#array_vc41 vc_array +#input =['hello', 'world'] +#output=['hello - o', 'world - d'] +#array_vc41 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - o', b'world - d'] +#array_vcfbd41 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - l', b'baseball - l', b'football - l', b'pingpong - g', b'lacrosse - e'] +#array_blob41 blob_array +#input =[b'binarydata', b'morebytes', None, b'abc'] +#output=[b'binarydata', b'morebytes', None, b'abc'] +#array_clob41 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'long text here', b'another clob'] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__IDS_EXPECTED__ +#array_int41 int_array +#input =[1, 2, 3, 4, 5] +#output=[0, 1, 2, 3, 4] +#array_sint41 sint_array +#input =[7, 512, -29000, 32000] +#output=[6, 511, -29001, 31999] +#array_bint41 bint_array +#input =[1234567890123, None, -9876543210] +#output=[1234567890122, None, -9876543211] +#array_float41 float_array +#input =[1.1, 2.2, 3.3] +#output=[-0.5699999999999998, 0.5300000000000002, 1.63] +#array_double41 double_array +#input =[10.5, 20.25, 30.75] +#output=[8.83, 18.58, 29.08] +#array_real41 real_array +#input =[0.5, 1.5, 2.5] +#output=[-1.1699999570846558, -0.17000000178813934, 0.8299999833106995] +#array_decflt1641 decfloat16_array +#input =[1.23, None, 4.56, None] +#output=[-0.44, None, 2.89, None] +#array_decfloat3441 decfloat34_array +#input =[12345678.1234, None] +#output=[12345676.4534, None] +#array_dec41 dec_array +#input =[12.34, None, 56.78] +#output=[10.67, None, 55.11] +#array_time41 time_array +#input =[datetime.time(12, 20, 30), datetime.time(13, 30, 45)] +#output=[datetime.time(13, 19, 31), datetime.time(14, 29, 46)] +#array_date41 date_array +#input =[datetime.date(2025, 1, 1), datetime.date(2025, 12, 31)] +#output=[datetime.date(2024, 1, 31), datetime.date(2025, 1, 30)] +#array_ts41 ts_array +#input =[datetime.datetime(1989, 2, 12, 23, 55, 59, 342380), datetime.datetime(1990, 2, 12, 23, 55, 59, 342380)] +#output=[datetime.datetime(1988, 3, 12, 0, 55, 0, 342380), datetime.datetime(1989, 3, 12, 0, 55, 0, 342380)] +#array_ts41 ts_array +#input =[b'1981-07-08 10:42:34.000010', b'1982-07-08 10:42:34.000010'] +#output=[datetime.datetime(1980, 8, 7, 11, 41, 35, 10), datetime.datetime(1981, 8, 7, 11, 41, 35, 10)] +#array_char41 char_array +#input =['abc', 'defg'] +#output=['abc - c', 'defg - g'] +#array_char41 char_array +#input =[b'abc', b'defg'] +#output=[b'abc - c ', b'defg - g '] +#array_vc41 vc_array +#input =['hello', 'world'] +#output=['hello - o', 'world - d'] +#array_vc41 vc_array +#input =[b'hello', b'world'] +#output=[b'hello - o', b'world - d'] +#array_vcfbd41 vcfbd_array +#input =[b'basketball', b'baseball', b'football', b'pingpong', b'lacrosse'] +#output=[b'basketball - l', b'baseball - l', b'football - l', b'pingpong - g', b'lacrosse - e'] +#array_blob41 blob_array +#input =[b'binarydata', b'morebytes', None, b'abc'] +#output=[b'binarydata', b'morebytes', None, b'abc'] +#array_clob41 clob_array +#input =[b'long text here', b'another clob'] +#output=[b'long text here', b'another clob'] +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED diff --git a/asyncio_testsuite/test_35_async_scalarsp_execute.py b/asyncio_testsuite/test_35_async_scalarsp_execute.py new file mode 100644 index 00000000..03279609 --- /dev/null +++ b/asyncio_testsuite/test_35_async_scalarsp_execute.py @@ -0,0 +1,195 @@ +from __future__ import print_function +import asyncio +import sys +import unittest +import ibm_db +import config +from ibm_db_dbi import AsyncConnection +from datetime import date, time, datetime +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_35_async_scalarsp_execute(self): + obj = IbmDbTestFunctions() + obj.assert_expect(self.run_test_35) + + def run_test_35(self): + SCALAR_PROCS = [ + # Numeric types + ("INTEGER", "int_scalar", 42), + ("SMALLINT", "sint_scalar", 7), + ("BIGINT", "bint_scalar", 1234), + ("FLOAT", "float_scalar", 3.14), + ("DOUBLE", "double_scalar", 20.25), + ("REAL", "real_scalar", 1.5), + ("DECFLOAT(16)", "decfloat16_scalar", 4.56), + ("DECFLOAT(34)", "decfloat34_scalar", 123456.1234), + ("DECIMAL(10,2)", "decimal_scalar", 56.78), + # Temporal types + ("DATE", "date_scalar", date(2025, 1, 1)), + ("TIME", "time_scalar", time(12, 20, 30)), + ("TIMESTAMP", "ts_scalar", datetime(1989, 2, 12, 23, 55, 59)), + ("TIMESTAMP", "ts_scalar", b'1989-02-12 23:55:59'), + # Character/string types + ("CHAR(20)", "char_scalar", "hello"), + ("CHAR(20)", "char_scalar", b'hello'), + ("VARCHAR(20)", "vc_scalar", "testdata"), + ("VARCHAR(20)", "vc_scalar", b'testdata'), + ("CLOB", "clob_scalar", b'long clob text data'), + # Binary types + ("BLOB", "blob_scalar", b'binarydata'), + ("VARBINARY", "vcfbd_scalar", b'varbindata'), + ] + + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + passed = 0 + failed = 0 + skipped = 0 + + for base_type, proc_name, input_val in SCALAR_PROCS: + cursor = await conn.cursor() + try: + sql = "CALL %s.%s(?, ?)" % (config.user.upper(), proc_name.upper()) + await cursor.prepare(sql) + + await cursor.bind_param(1, input_val, ibm_db.SQL_PARAM_INPUT) + await cursor.bind_param(2, input_val, ibm_db.SQL_PARAM_OUTPUT) + + await cursor.execute() + result = await cursor.fetch_callproc() + + # result = (stmt, input_echo, output_value) + in_echo = result[1] + out_val = result[2] + print("%-22s %-15s input=%r output=%r" % (proc_name, base_type, in_echo, out_val)) + + if out_val is not None: + passed += 1 + else: + print(" *** UNEXPECTED: output is None") + failed += 1 + except Exception as e: + err_str = str(e) + if 'SQL0440N' in err_str or 'CLI0102E' in err_str: + print("%-22s %-15s SKIP: procedure not available" % (proc_name, base_type)) + skipped += 1 + else: + print(" %-22s %-15s ERROR: %s" % (proc_name, base_type, e)) + failed += 1 + finally: + await cursor.close() + + await conn.close() + total = len(SCALAR_PROCS) + print("\nResults: %d passed, %d skipped, %d failed out of %d" % (passed, skipped, failed, total)) + if failed == 0: + print("PASSED" if passed > 0 else "ALL SKIPPED") + else: + print("FAILED") + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#int_scalar INTEGER input=42 output=43 +#sint_scalar SMALLINT input=7 output=8 +#bint_scalar BIGINT input=1234 output=1235 +#float_scalar FLOAT input=3.14 output=4.140000000000001 +#double_scalar DOUBLE input=20.25 output=21.25 +#real_scalar REAL input=1.5 output=2.5 +#decfloat16_scalar DECFLOAT(16) input=4.56 output=5.56 +#decfloat34_scalar DECFLOAT(34) input=123456.1234 output=123457.1234 +#decimal_scalar DECIMAL(10,2) input=56.78 output=57.78 +#date_scalar DATE input=datetime.date(2025, 1, 1) output=datetime.date(2025, 1, 2) +#time_scalar TIME input=datetime.time(12, 20, 30) output=datetime.time(12, 21, 30) +#ts_scalar TIMESTAMP input=datetime.datetime(1989, 2, 12, 23, 55, 59) output=datetime.datetime(1989, 2, 12, 23, 56) +#ts_scalar TIMESTAMP input=b'1989-02-12 23:55:59' output=datetime.datetime(1989, 2, 12, 23, 56) +#char_scalar CHAR(20) input='hello' output='hello_OUT' +#char_scalar CHAR(20) input=b'hello' output=b'hello_OUT ' +#vc_scalar VARCHAR(20) input='testdata' output='testdata_OUT' +#vc_scalar VARCHAR(20) input=b'testdata' output=b'testdata_OUT' +#clob_scalar CLOB input=b'long clob text data' output=b'long clob text data' +#blob_scalar BLOB input=b'binarydata' output=b'binarydata' +#vcfbd_scalar VARBINARY input=b'varbindata' output=b'varbindata' +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__ZOS_EXPECTED__ +#int_scalar INTEGER input=42 output=43 +#sint_scalar SMALLINT input=7 output=8 +#bint_scalar BIGINT input=1234 output=1235 +#float_scalar FLOAT input=3.14 output=4.140000000000001 +#double_scalar DOUBLE input=20.25 output=21.25 +#real_scalar REAL input=1.5 output=2.5 +#decfloat16_scalar DECFLOAT(16) input=4.56 output=5.56 +#decfloat34_scalar DECFLOAT(34) input=123456.1234 output=123457.1234 +#decimal_scalar DECIMAL(10,2) input=56.78 output=57.78 +#date_scalar DATE input=datetime.date(2025, 1, 1) output=datetime.date(2025, 1, 2) +#time_scalar TIME input=datetime.time(12, 20, 30) output=datetime.time(12, 21, 30) +#ts_scalar TIMESTAMP input=datetime.datetime(1989, 2, 12, 23, 55, 59) output=datetime.datetime(1989, 2, 12, 23, 56) +#ts_scalar TIMESTAMP input=b'1989-02-12 23:55:59' output=datetime.datetime(1989, 2, 12, 23, 56) +#char_scalar CHAR(20) input='hello' output='hello_OUT' +#char_scalar CHAR(20) input=b'hello' output=b'hello_OUT ' +#vc_scalar VARCHAR(20) input='testdata' output='testdata_OUT' +#vc_scalar VARCHAR(20) input=b'testdata' output=b'testdata_OUT' +#clob_scalar CLOB input=b'long clob text data' output=b'long clob text data' +#blob_scalar BLOB input=b'binarydata' output=b'binarydata' +#vcfbd_scalar VARBINARY input=b'varbindata' output=b'varbindata' +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__SYSTEMI_EXPECTED__ +#int_scalar INTEGER input=42 output=43 +#sint_scalar SMALLINT input=7 output=8 +#bint_scalar BIGINT input=1234 output=1235 +#float_scalar FLOAT input=3.14 output=4.140000000000001 +#double_scalar DOUBLE input=20.25 output=21.25 +#real_scalar REAL input=1.5 output=2.5 +#decfloat16_scalar DECFLOAT(16) input=4.56 output=5.56 +#decfloat34_scalar DECFLOAT(34) input=123456.1234 output=123457.1234 +#decimal_scalar DECIMAL(10,2) input=56.78 output=57.78 +#date_scalar DATE input=datetime.date(2025, 1, 1) output=datetime.date(2025, 1, 2) +#time_scalar TIME input=datetime.time(12, 20, 30) output=datetime.time(12, 21, 30) +#ts_scalar TIMESTAMP input=datetime.datetime(1989, 2, 12, 23, 55, 59) output=datetime.datetime(1989, 2, 12, 23, 56) +#ts_scalar TIMESTAMP input=b'1989-02-12 23:55:59' output=datetime.datetime(1989, 2, 12, 23, 56) +#char_scalar CHAR(20) input='hello' output='hello_OUT' +#char_scalar CHAR(20) input=b'hello' output=b'hello_OUT ' +#vc_scalar VARCHAR(20) input='testdata' output='testdata_OUT' +#vc_scalar VARCHAR(20) input=b'testdata' output=b'testdata_OUT' +#clob_scalar CLOB input=b'long clob text data' output=b'long clob text data' +#blob_scalar BLOB input=b'binarydata' output=b'binarydata' +#vcfbd_scalar VARBINARY input=b'varbindata' output=b'varbindata' +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED +#__IDS_EXPECTED__ +#int_scalar INTEGER input=42 output=43 +#sint_scalar SMALLINT input=7 output=8 +#bint_scalar BIGINT input=1234 output=1235 +#float_scalar FLOAT input=3.14 output=4.140000000000001 +#double_scalar DOUBLE input=20.25 output=21.25 +#real_scalar REAL input=1.5 output=2.5 +#decfloat16_scalar DECFLOAT(16) input=4.56 output=5.56 +#decfloat34_scalar DECFLOAT(34) input=123456.1234 output=123457.1234 +#decimal_scalar DECIMAL(10,2) input=56.78 output=57.78 +#date_scalar DATE input=datetime.date(2025, 1, 1) output=datetime.date(2025, 1, 2) +#time_scalar TIME input=datetime.time(12, 20, 30) output=datetime.time(12, 21, 30) +#ts_scalar TIMESTAMP input=datetime.datetime(1989, 2, 12, 23, 55, 59) output=datetime.datetime(1989, 2, 12, 23, 56) +#ts_scalar TIMESTAMP input=b'1989-02-12 23:55:59' output=datetime.datetime(1989, 2, 12, 23, 56) +#char_scalar CHAR(20) input='hello' output='hello_OUT' +#char_scalar CHAR(20) input=b'hello' output=b'hello_OUT ' +#vc_scalar VARCHAR(20) input='testdata' output='testdata_OUT' +#vc_scalar VARCHAR(20) input=b'testdata' output=b'testdata_OUT' +#clob_scalar CLOB input=b'long clob text data' output=b'long clob text data' +#blob_scalar BLOB input=b'binarydata' output=b'binarydata' +#vcfbd_scalar VARBINARY input=b'varbindata' output=b'varbindata' +# +#Results: 20 passed, 0 skipped, 0 failed out of 20 +#PASSED diff --git a/asyncio_testsuite/test_async_concurrent_mixed.py b/asyncio_testsuite/test_async_concurrent_mixed.py new file mode 100644 index 00000000..db97231d --- /dev/null +++ b/asyncio_testsuite/test_async_concurrent_mixed.py @@ -0,0 +1,101 @@ +from __future__ import print_function +import asyncio +import time +import sys +import unittest +import config +from ibm_db_dbi import AsyncConnection +from testfunctions import IbmDbTestFunctions + + +class IbmDbTestCase(unittest.TestCase): + + def test_async_concurrent_mixed(self): + obj = IbmDbTestFunctions() + obj.assert_expectf(self.run_test_misc) + + def run_test_misc(self): + async def slow_query(conn): + label = "slow_query" + print(" [%s] started" % label) + t0 = time.perf_counter() + + cursor = await conn.cursor() + await cursor.execute(""" + SELECT COUNT(*) AS cnt, AVG(a.SALARY) AS avg_salary + FROM STAFF a, STAFF b, STAFF c + """) + row = await cursor.fetchone() + await cursor.close() + + elapsed = time.perf_counter() - t0 + print(" [%s] finished in %.3fs => %s" % (label, elapsed, row)) + return row + + async def fast_query_1(conn): + label = "fast_query_1" + print(" [%s] started" % label) + t0 = time.perf_counter() + + cursor = await conn.cursor() + await cursor.execute( + "SELECT ID, NAME, DEPT, JOB FROM STAFF WHERE ID = ?", + (10,) + ) + row = await cursor.fetchone() + await cursor.close() + + elapsed = time.perf_counter() - t0 + print(" [%s] finished in %.3fs => %s" % (label, elapsed, row)) + return row + + async def fast_query_2(conn): + label = "fast_query_2" + print(" [%s] started" % label) + t0 = time.perf_counter() + + cursor = await conn.cursor() + await cursor.execute("SELECT COUNT(*) AS total FROM STAFF") + row = await cursor.fetchone() + await cursor.close() + + elapsed = time.perf_counter() - t0 + print(" [%s] finished in %.3fs => %s" % (label, elapsed, row)) + return row + + async def main(): + conn = await AsyncConnection.connect( + "DATABASE=%s;HOSTNAME=%s;PORT=%d;PROTOCOL=TCPIP;UID=%s;PWD=%s;" % ( + config.database, config.hostname, config.port, + config.user, config.password), + '', '') + + overall_start = time.perf_counter() + + slow_result, fast1_result, fast2_result = await asyncio.gather( + slow_query(conn), + fast_query_1(conn), + fast_query_2(conn), + ) + + overall_elapsed = time.perf_counter() - overall_start + + print("\n--- Results ---") + print(" Slow query : %s" % (slow_result,)) + print(" Fast query 1: %s" % (fast1_result,)) + print(" Fast query 2: %s" % (fast2_result,)) + print("\n Total wall-clock time: %.3fs" % overall_elapsed) + print(" (Should be close to the slow query time, not the sum of all three)") + + await conn.close() + asyncio.run(main()) + +#__END__ +#__LUW_EXPECTED__ +#%s +#__ZOS_EXPECTED__ +#%s +#__SYSTEMI_EXPECTED__ +#%s +#__IDS_EXPECTED__ +#%s diff --git a/ibm_db_ctx.py b/ibm_db_ctx.py index 18c99d22..9a1840d2 100644 --- a/ibm_db_ctx.py +++ b/ibm_db_ctx.py @@ -14,7 +14,7 @@ def __init__(self, dsn: str, username: str, password: str) -> None: def __enter__(self) -> 'IBM_DBConnection': """Connect to DB2.""" - self.__cxn = ibm_db.connect(self.__dsn, '', '') + self.__cxn = ibm_db.connect(self.__dsn, self.__username, self.__password) #print("enter method called") return self.__cxn diff --git a/ibm_db_dbi.py b/ibm_db_dbi.py index ee7e43f6..aae2d79b 100644 --- a/ibm_db_dbi.py +++ b/ibm_db_dbi.py @@ -22,6 +22,7 @@ """ import types, string, time, datetime, decimal, sys +import asyncio import weakref import logging as log_ibmdb_dbi @@ -1785,33 +1786,41 @@ def stmt_error(self): LogMsg(INFO, "exit stmt_error()") return sqlstate - def execute(self, operation, parameters=None): + def execute(self, operation=None, parameters=None): """ This method can be used to prepare and execute an SQL statement. It takes the SQL statement(operation) and a sequence of values to substitute for the parameter markers in the SQL statement as arguments. + + If called with no arguments after prepare() + bind_param(), + it executes the already-prepared statement. """ LogMsg(INFO, "entry execute()") - LogMsg(DEBUG, f"Executing SQL operation: {operation}") - if parameters is not None: - LogMsg(DEBUG, f"SQL parameters: {parameters}") - self.messages = [] - if not isinstance(operation, string_types): - err_msg = "execute expects the first argument [%s] to be of type String or Unicode." % operation - LogMsg(ERROR, err_msg) - self.messages.append( - InterfaceError("execute expects the first argument [%s] to be of type String or Unicode." % operation)) - raise self.messages[len(self.messages) - 1] - if parameters is not None: - if not isinstance(parameters, (list, tuple, dict)): - LogMsg(ERROR, "execute parameters argument should be sequence.") - self.messages.append(InterfaceError("execute parameters argument should be sequence.")) + if operation is not None: + LogMsg(DEBUG, f"Executing SQL operation: {operation}") + if parameters is not None: + LogMsg(DEBUG, f"SQL parameters: {parameters}") + self.messages = [] + if not isinstance(operation, string_types): + err_msg = "execute expects the first argument [%s] to be of type String or Unicode." % operation + LogMsg(ERROR, err_msg) + self.messages.append( + InterfaceError("execute expects the first argument [%s] to be of type String or Unicode." % operation)) raise self.messages[len(self.messages) - 1] - self.__description = None - self._all_stmt_handlers = [] - self._prepare_helper(operation) - self._execute_helper(parameters) + if parameters is not None: + if not isinstance(parameters, (list, tuple, dict)): + LogMsg(ERROR, "execute parameters argument should be sequence.") + self.messages.append(InterfaceError("execute parameters argument should be sequence.")) + raise self.messages[len(self.messages) - 1] + self.__description = None + self._all_stmt_handlers = [] + self._prepare_helper(operation) + self._execute_helper(parameters) + else: + # Execute already-prepared statement (after prepare + bind_param) + LogMsg(DEBUG, "Executing already-prepared statement") + self._execute_helper(parameters) self._set_cursor_helper() LogMsg(INFO, "SQL operation executed successfully.") LogMsg(INFO, "exit execute()") @@ -2025,6 +2034,62 @@ def nextset(self): LogMsg(INFO, "exit nextset()") return True + def prepare(self, operation): + """Prepare an SQL statement for later execution via bind_param + execute.""" + LogMsg(INFO, "entry prepare()") + self.messages = [] + self.__description = None + self._all_stmt_handlers = [] + self._prepare_helper(operation) + LogMsg(INFO, "exit prepare()") + + def bind_param(self, index, value, param_type=None, data_type=None): + """Bind a parameter value for the prepared statement. + + ``index`` is 1-based. ``param_type`` is an optional + ``ibm_db.SQL_PARAM_*`` constant (e.g. SQL_PARAM_INPUT). + ``data_type`` is an optional SQL data type constant + (e.g. ``ibm_db.SQL_INTEGER``). + """ + LogMsg(INFO, "entry bind_param()") + try: + if param_type is not None and data_type is not None: + ibm_db.bind_param(self.stmt_handler, index, value, param_type, data_type) + elif param_type is not None: + ibm_db.bind_param(self.stmt_handler, index, value, param_type) + else: + ibm_db.bind_param(self.stmt_handler, index, value) + except Exception as inst: + LogMsg(ERROR, f"Error binding parameter at index {index}: {_get_exception(inst)}") + self.messages.append(_get_exception(inst)) + raise self.messages[len(self.messages) - 1] + LogMsg(INFO, "exit bind_param()") + + def fetch_callproc(self): + """Fetch the output parameters returned by a stored procedure + executed via the prepare/bind/execute path.""" + LogMsg(INFO, "entry fetch_callproc()") + try: + result = ibm_db.fetch_callproc(self.stmt_handler) + except Exception as inst: + LogMsg(ERROR, f"Error in fetch_callproc: {_get_exception(inst)}") + self.messages.append(_get_exception(inst)) + raise self.messages[len(self.messages) - 1] + LogMsg(INFO, "exit fetch_callproc()") + return result + + def fetch_tuple(self): + """Fetch one row as a tuple from the current result set.""" + LogMsg(INFO, "entry fetch_tuple()") + try: + result = ibm_db.fetch_tuple(self.stmt_handler) + except Exception as inst: + LogMsg(ERROR, f"Error in fetch_tuple: {_get_exception(inst)}") + self.messages.append(_get_exception(inst)) + raise self.messages[len(self.messages) - 1] + LogMsg(INFO, "exit fetch_tuple()") + return result + def setinputsizes(self, sizes): """This method currently does nothing.""" pass @@ -2080,3 +2145,314 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.close() + + +# Module-level async functions + +async def connect_async(dsn, user='', password='', host='', database='', + conn_options=None): + """Async wrapper around :func:`connect`. Returns a :class:`Connection`.""" + LogMsg(INFO, "entry connect_async()") + conn_obj = await asyncio.to_thread( + connect, dsn, user, password, host, database, conn_options + ) + LogMsg(INFO, "exit connect_async()") + return conn_obj + + +async def pconnect_async(dsn, user='', password='', host='', database='', + conn_options=None): + """Async wrapper around :func:`pconnect`. Returns a :class:`Connection`.""" + LogMsg(INFO, "entry pconnect_async()") + conn_obj = await asyncio.to_thread( + pconnect, dsn, user, password, host, database, conn_options + ) + LogMsg(INFO, "exit pconnect_async()") + return conn_obj + + +async def conn_errormsg_async(connection=None): + """Async wrapper around :func:`conn_errormsg`.""" + return await asyncio.to_thread(conn_errormsg, connection) + + +async def conn_error_async(connection=None): + """Async wrapper around :func:`conn_error`.""" + return await asyncio.to_thread(conn_error, connection) + + +async def get_sqlcode_async(handle=None): + """Async wrapper around :func:`get_sqlcode`.""" + return await asyncio.to_thread(get_sqlcode, handle) + + +async def createdb_async(database, dsn, user='', password='', host='', + codeset='', mode=''): + """Async wrapper around :func:`createdb`.""" + return await asyncio.to_thread( + createdb, database, dsn, user, password, host, codeset, mode + ) + + +async def dropdb_async(database, dsn, user='', password='', host=''): + """Async wrapper around :func:`dropdb`.""" + return await asyncio.to_thread( + dropdb, database, dsn, user, password, host + ) + + +async def recreatedb_async(database, dsn, user='', password='', host='', + codeset='', mode=''): + """Async wrapper around :func:`recreatedb`.""" + return await asyncio.to_thread( + recreatedb, database, dsn, user, password, host, codeset, mode + ) + + +async def createdbNX_async(database, dsn, user='', password='', host='', + codeset='', mode=''): + """Async wrapper around :func:`createdbNX`.""" + return await asyncio.to_thread( + createdbNX, database, dsn, user, password, host, codeset, mode + ) + + +# AsyncCursor + +class AsyncCursor: + """Async wrapper around :class:`Cursor`. + + Every public method of the sync Cursor that performs I/O is exposed as + a coroutine. ``prepare``, ``bind_param`` and ``fetch_callproc`` are + added here for the prepare/bind/execute workflow. + """ + + def __init__(self, sync_cursor): + self._cursor = sync_cursor + + #properties (sync, no I/O) + + @property + def description(self): + return self._cursor.description + + @property + def rowcount(self): + return self._cursor.rowcount + + @property + def arraysize(self): + return self._cursor.arraysize + + @arraysize.setter + def arraysize(self, value): + self._cursor.arraysize = value + + @property + def stmt_handler(self): + return self._cursor.stmt_handler + + @property + def conn_handler(self): + return self._cursor.conn_handler + + @property + def messages(self): + return self._cursor.messages + + # async I/O methods + + async def execute(self, operation=None, parameters=None): + """Execute SQL. Supports three calling conventions: + + * ``await cursor.execute(sql)`` + * ``await cursor.execute(sql, params)`` + * ``await cursor.execute()`` (after prepare + bind_param) + """ + return await asyncio.to_thread( + self._cursor.execute, operation, parameters + ) + + async def executemany(self, operation, seq_parameters): + return await asyncio.to_thread( + self._cursor.executemany, operation, seq_parameters + ) + + async def fetchone(self): + return await asyncio.to_thread(self._cursor.fetchone) + + async def fetchmany(self, size=0): + return await asyncio.to_thread(self._cursor.fetchmany, size) + + async def fetchall(self): + return await asyncio.to_thread(self._cursor.fetchall) + + async def callproc(self, procname, parameters=None): + return await asyncio.to_thread( + self._cursor.callproc, procname, parameters + ) + + async def nextset(self): + return await asyncio.to_thread(self._cursor.nextset) + + async def close(self): + return await asyncio.to_thread(self._cursor.close) + + async def prepare(self, operation): + """Prepare an SQL statement for later execution.""" + return await asyncio.to_thread(self._cursor.prepare, operation) + + async def bind_param(self, index, value, param_type=None, data_type=None): + """Bind a parameter value for the prepared statement. + + ``index`` is 1-based. ``param_type`` is an optional + ``ibm_db.SQL_PARAM_*`` constant. ``data_type`` is an optional + SQL data type constant (e.g. ``ibm_db.SQL_INTEGER``). + """ + return await asyncio.to_thread( + self._cursor.bind_param, index, value, param_type, data_type + ) + + async def fetch_callproc(self): + """Fetch the output parameters returned by a CALL statement + executed via the prepare/bind/execute path.""" + return await asyncio.to_thread(self._cursor.fetch_callproc) + + async def fetch_tuple(self): + """Fetch one row as a tuple from the current result set.""" + return await asyncio.to_thread(self._cursor.fetch_tuple) + + async def stmt_errormsg(self): + return await asyncio.to_thread(self._cursor.stmt_errormsg) + + async def stmt_error(self): + return await asyncio.to_thread(self._cursor.stmt_error) + + #async iteration & context manager + + def __aiter__(self): + return self + + async def __anext__(self): + row = await self.fetchone() + if row is None: + raise StopAsyncIteration + return row + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + +#AsyncConnection + +class AsyncConnection: + """Async wrapper around :class:`Connection`. + + Obtain an instance via the :meth:`connect` class method:: + + conn = await AsyncConnection.connect(dsn, user, password) + """ + + def __init__(self, sync_conn): + self._conn = sync_conn + + #properties (read-only, no I/O) + + @property + def conn_handler(self): + return self._conn.conn_handler + + @property + def dbms_name(self): + return self._conn.dbms_name + + @property + def dbms_ver(self): + return self._conn.dbms_ver + + #class-level factory + + @classmethod + async def connect(cls, dsn, user='', password='', host='', database='', + conn_options=None): + """Create a new async connection. Returns an :class:`AsyncConnection`.""" + sync_conn = await asyncio.to_thread( + connect, dsn, user, password, host, database, conn_options + ) + return cls(sync_conn) + + # async methods wrapping Connection + + async def cursor(self): + sync_cursor = self._conn.cursor() + return AsyncCursor(sync_cursor) + + async def close(self): + return await asyncio.to_thread(self._conn.close) + + async def commit(self): + return await asyncio.to_thread(self._conn.commit) + + async def rollback(self): + return await asyncio.to_thread(self._conn.rollback) + + async def set_autocommit(self, is_on): + return await asyncio.to_thread(self._conn.set_autocommit, is_on) + + async def set_option(self, attr_dict): + return await asyncio.to_thread(self._conn.set_option, attr_dict) + + async def get_option(self, attr_key): + return await asyncio.to_thread(self._conn.get_option, attr_key) + + async def set_current_schema(self, schema_name): + return await asyncio.to_thread( + self._conn.set_current_schema, schema_name + ) + + async def get_current_schema(self): + return await asyncio.to_thread(self._conn.get_current_schema) + + async def server_info(self): + return await asyncio.to_thread(self._conn.server_info) + + async def tables(self, schema_name=None, table_name=None): + return await asyncio.to_thread( + self._conn.tables, schema_name, table_name + ) + + async def columns(self, schema_name=None, table_name=None, + column_names=None): + return await asyncio.to_thread( + self._conn.columns, schema_name, table_name, column_names + ) + + async def primary_keys(self, unique=True, schema_name=None, + table_name=None): + return await asyncio.to_thread( + self._conn.primary_keys, unique, schema_name, table_name + ) + + async def indexes(self, unique=True, schema_name=None, table_name=None): + return await asyncio.to_thread( + self._conn.indexes, unique, schema_name, table_name + ) + + async def foreign_keys(self, unique=True, schema_name=None, + table_name=None): + return await asyncio.to_thread( + self._conn.foreign_keys, unique, schema_name, table_name + ) + + async def set_fix_return_type(self, is_on): + return await asyncio.to_thread(self._conn.set_fix_return_type, is_on) + + #async context manager + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() diff --git a/ibmdb_tests.py b/ibmdb_tests.py index 1822330f..80b37949 100644 --- a/ibmdb_tests.py +++ b/ibmdb_tests.py @@ -1,5 +1,6 @@ import os from os.path import basename, join +import argparse import random import sys import unittest @@ -21,26 +22,40 @@ except ImportError as e: print("HtmlTestRunner Package not found .. Running with normal unit test runs") -# Test runner for ibm_db. Normally one could use something like: +# Test runner for ibm_db. # -# Run all in the tests directory: -# python -m unittest discover -v -s tests +# Usage: +# python ibmdb_tests.py # run both sync + async +# python ibmdb_tests.py --async # run only async tests +# python ibmdb_tests.py --sync # run only sync tests +# python ibmdb_tests.py --async --test test_05_async_fetch.py # single async test +# python ibmdb_tests.py --sync --test test_006_ConnPassingOpts.py # single sync test # -# Run all tests matching specified pattern or specific test: -# python -m unittest discover -v -s tests -p 'test_*FetchAssocSelect*.py' -# python -m unittest discover -v -s tests -p test_006_ConnPassingOpts.py -# -# However, for running all the tests, we need to ensure that test_000_PrepareDb -# is run first to set up the database. Possibly this could be moved to a -# separate task which is run by itself prior to running any tests, which -# would simplify things a bit. +# Legacy: SINGLE_PYTHON_TEST env var is also supported for backward compatibility. +# set SINGLE_PYTHON_TEST=test_006_ConnPassingOpts.py +# python ibmdb_tests.py -# Override standard test-loading behavior -def load_tests(loader, tests, pattern): - suite = unittest.TestSuite() +# Parse command-line arguments before unittest sees them +_parser = argparse.ArgumentParser(add_help=False) +_parser.add_argument('--async', dest='async_only', action='store_true', + help='Run only async tests') +_parser.add_argument('--sync', dest='sync_only', action='store_true', + help='Run only sync tests') +_parser.add_argument('--test', dest='single_test', default=None, + help='Run a single test file (glob pattern)') +_args, _remaining_argv = _parser.parse_known_args() +sys.argv = [sys.argv[0]] + _remaining_argv # pass remaining args to unittest +# Support legacy SINGLE_PYTHON_TEST env var +if _args.single_test is None: + _args.single_test = os.environ.get('SINGLE_PYTHON_TEST') + +# Override standard test-loading behavior +def _load_sync_tests(suite, test_glob=None): + """Load sync tests from ibm_db_tests/.""" test_glob_default = "test_*.py" - test_glob = os.environ.get("SINGLE_PYTHON_TEST", "").strip() or test_glob_default + if test_glob is None: + test_glob = test_glob_default # We need files of a given size for some of the test units, so create them # here. with open("ibm_db_tests/spook.png", "wb") as f: @@ -68,9 +83,41 @@ def load_tests(loader, tests, pattern): mod = importlib.import_module(test) suite.addTest(mod.IbmDbTestCase(test)) + +def _load_async_tests(suite, test_glob=None): + """Load async tests from asyncio_testsuite/.""" + if test_glob is None: + test_glob = "test_*.py" + async_test_dir = 'asyncio_testsuite' + if async_test_dir not in sys.path: + sys.path.insert(0, async_test_dir) + async_files = glob.glob(join(async_test_dir, test_glob)) + async_tests = [basename(_).replace('.py', '') for _ in async_files] + async_tests = [t for t in async_tests if t not in ('test_utils', 'run_all')] + async_tests.sort() + for test in async_tests: + mod = importlib.import_module(test) + suite.addTest(mod.IbmDbTestCase(test)) + + +def load_tests(loader, tests, pattern): + suite = unittest.TestSuite() + + single = _args.single_test + + if _args.async_only: + _load_async_tests(suite, single) + elif _args.sync_only: + _load_sync_tests(suite, single) + else: + # Default: run both + _load_sync_tests(suite, single) + _load_async_tests(suite, single) + return suite if __name__ == '__main__': + sys.path.insert(0, 'asyncio_testsuite') if(_HTML_RUNNER): unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(report_name='result',combine_reports=True)) else: diff --git a/run_all_tests b/run_all_tests index 640dcfbe..53210bf5 100755 --- a/run_all_tests +++ b/run_all_tests @@ -57,7 +57,7 @@ AUTOCOMMIT=1 EOF chtag -b $DSNAOINI out="${ENCODING}_${ATTACH}_${MULTICONTEXT}" - python tests.py > $out.out 2>&1 + python ibmdb_tests.py > $out.out 2>&1 ./run_individual_tests $out >> $out.out 2>&1 fgrep -e ' ... ' $out.out | sed -e 's/ .* \.\.\. / /' -e 's/ skipped .*/ skipped/' > $out.summary done @@ -82,7 +82,7 @@ rm *.sorted # export _BPX_SHAREAS=YES # ./odbc_trace_control ON -I 8000000 -# python tests.py +# python ibmdb_tests.py # ./odbc_trace_control DMP $DIAGTRACEFILENAME # ./odbc_trace_control FMT $DIAGTRACEFILENAME $DIAGTRACEFILENAME.out # export _BPX_SHAREAS=NO diff --git a/run_individual_tests b/run_individual_tests index 0bbdf92a..78a8cd30 100755 --- a/run_individual_tests +++ b/run_individual_tests @@ -28,7 +28,7 @@ run_test() { export SINGLE_PYTHON_TEST=$name touch $FILEPREFIX$base.out $FILEPREFIX$base.err chtag -t -c 819 $FILEPREFIX$base.out $FILEPREFIX$base.err - python tests.py 1> $FILEPREFIX$base.out 2> $FILEPREFIX$base.err <&- + python ibmdb_tests.py 1> $FILEPREFIX$base.out 2> $FILEPREFIX$base.err <&- show_file $FILEPREFIX$base.out show_file $FILEPREFIX$base.err } diff --git a/testfunctions.py b/testfunctions.py index d2f7a9c4..911a25ba 100644 --- a/testfunctions.py +++ b/testfunctions.py @@ -15,7 +15,13 @@ class IbmDbTestFunctions(unittest.TestCase): - prepconn = ibm_db.connect(config.database, config.user, config.password) + if hasattr(config, 'hostname') and config.hostname: + _dsn = (f"DATABASE={config.database};HOSTNAME={config.hostname};" + f"PORT={config.port};PROTOCOL=TCPIP;" + f"UID={config.user};PWD={config.password}") + prepconn = ibm_db.connect(_dsn, '', '') + else: + prepconn = ibm_db.connect(config.database, config.user, config.password) server = ibm_db.server_info(prepconn) ibm_db.close(prepconn) @@ -35,30 +41,31 @@ def capture(self, func): return var def testCasesIn(self, fileName): - if (fileName.startswith('ibm_db_tests/test_017') or \ - fileName.startswith('ibm_db_tests/test_005') or \ - fileName.startswith('ibm_db_tests/test_018') or \ - fileName.startswith('ibm_db_tests/test_019') or \ - fileName.startswith('ibm_db_tests/test_024') or \ - fileName.startswith('ibm_db_tests/test_053') or \ - fileName.startswith('ibm_db_tests/test_054') or \ - fileName.startswith('ibm_db_tests/test_080') or \ - fileName.startswith('ibm_db_tests/test_081') or \ - fileName.startswith('ibm_db_tests/test_082') or \ - fileName.startswith('ibm_db_tests/test_090') or \ - fileName.startswith('ibm_db_tests/test_091') or \ - fileName.startswith('ibm_db_tests/test_092') or \ - fileName.startswith('ibm_db_tests/test_103') or \ - fileName.startswith('ibm_db_tests/test_116') or \ - fileName.startswith('ibm_db_tests/test_133') or \ - fileName.startswith('ibm_db_tests/test_147') or \ - fileName.startswith('ibm_db_tests/test_157a') or \ - fileName.startswith('ibm_db_tests/test_240') or \ - fileName.startswith('ibm_db_tests/test_241') or \ - fileName.startswith('ibm_db_tests/test_cursortype') or \ - fileName.startswith('ibm_db_tests/test_decfloat') or \ - fileName.startswith('ibm_db_tests/test_setgetOption') or \ - fileName.startswith('ibm_db_tests/test_warn') \ + basename = os.path.basename(fileName) + if (basename.startswith('test_017') or \ + basename.startswith('test_005') or \ + basename.startswith('test_018') or \ + basename.startswith('test_019') or \ + basename.startswith('test_024') or \ + basename.startswith('test_053') or \ + basename.startswith('test_054') or \ + basename.startswith('test_080') or \ + basename.startswith('test_081') or \ + basename.startswith('test_082') or \ + basename.startswith('test_090') or \ + basename.startswith('test_091') or \ + basename.startswith('test_092') or \ + basename.startswith('test_103') or \ + basename.startswith('test_116') or \ + basename.startswith('test_133') or \ + basename.startswith('test_147') or \ + basename.startswith('test_157a') or \ + basename.startswith('test_240') or \ + basename.startswith('test_241') or \ + basename.startswith('test_cursortype') or \ + basename.startswith('test_decfloat') or \ + basename.startswith('test_setgetOption') or \ + basename.startswith('test_warn') \ ): return True else: @@ -98,10 +105,15 @@ def expected_AS(self, fileName): # This function grabs the expected output of the current test function for z/OS ODBC driver, # located at the bottom of the current test file. + # Falls back to expected_ZOS if no #__ZOS_ODBC_EXPECTED__ section exists. def expected_ZOS_ODBC(self, fileName): - fileHandle = open(fileName,'r') - fileInput = fileHandle.read().split('#__ZOS_ODBC_EXPECTED__')[-1].replace('\n', "").replace('#', '') + fileHandle = open(fileName, 'r') + fileContent = fileHandle.read() fileHandle.close() + if '#__ZOS_ODBC_EXPECTED__' in fileContent: + fileInput = fileContent.split('#__ZOS_ODBC_EXPECTED__')[-1].replace('\n', '').replace('#', '') + else: + fileInput = fileContent.split('#__ZOS_EXPECTED__')[-1].split('#__SYSTEMI_EXPECTED__')[0].replace('\n', '').replace('#', '') return fileInput # This function compares the captured outout with the expected out of @@ -111,7 +123,7 @@ def assert_expect(self, testFuncName): try: if (self.server.DBMS_NAME[0:2] == "AS"): self.assertEqual(self.capture(testFuncName), self.expected_AS(callstack[1][1])) - elif (platform.system() == 'z/OS' or platform.system() == 'OS/390' and self.testCasesIn(callstack[1][1])): + elif ((platform.system() == 'z/OS' or platform.system() == 'OS/390') and self.testCasesIn(callstack[1][1])): self.assertEqual(self.capture(testFuncName), self.expected_ZOS_ODBC(callstack[1][1])) elif (self.server.DBMS_NAME == "DB2" or "DSN" in self.server.DBMS_NAME): self.assertEqual(self.capture(testFuncName), self.expected_ZOS(callstack[1][1])) @@ -130,7 +142,7 @@ def assert_expectf(self, testFuncName): try: if (self.server.DBMS_NAME[0:2] == "AS"): pattern = self.expected_AS(callstack[1][1]) - elif (platform.system() == 'z/OS' or platform.system() == 'OS/390' and self.testCasesIn(callstack[1][1])): + elif ((platform.system() == 'z/OS' or platform.system() == 'OS/390') and self.testCasesIn(callstack[1][1])): pattern = self.expected_ZOS_ODBC(callstack[1][1]) elif (self.server.DBMS_NAME == "DB2" or "DSN" in self.server.DBMS_NAME): pattern = self.expected_ZOS(callstack[1][1]) @@ -139,15 +151,11 @@ def assert_expectf(self, testFuncName): else: pattern = self.expected_LUW(callstack[1][1]) - sym = [r'\[',r'\]',r'\(',r'\)'] - for chr in sym: - pattern = re.sub(chr, '\\' + chr, pattern) - - pattern = re.sub('%s', '.*?', pattern) - if sys.version_info >=(3,7 ): - pattern = re.sub('%d', r'\\d+', pattern) - else: - pattern = re.sub('%d', '\\d+', pattern) + pattern = pattern.replace('%s', '\x00WILDCARD_S\x00') + pattern = pattern.replace('%d', '\x00WILDCARD_D\x00') + pattern = re.escape(pattern) + pattern = pattern.replace('\x00WILDCARD_S\x00', '.*?') + pattern = pattern.replace('\x00WILDCARD_D\x00', r'\d+') result = re.match(pattern, self.capture(testFuncName)) self.assertNotEqual(result, None)