diff --git a/python/python-oracledb/README.md b/python/python-oracledb/README.md index 4d28594a..f1a4e706 100644 --- a/python/python-oracledb/README.md +++ b/python/python-oracledb/README.md @@ -1,16 +1,16 @@ -# Python-oracledb Examples +# Python-oracledb Examples This directory contains samples for python-oracledb, the Python driver for Oracle Database. +### Basic Examples + 1. The schemas and SQL objects that are referenced in the samples can be - created by running the Python script - [create_schema.py](https://github.com/oracle-samples/oracle-db-examples/blob/main/python/create_schema.py). The - script requires SYSDBA privileges and will prompt for these credentials as - well as the names of the schemas and edition that will be created, unless a - number of environment variables are set as documented in the Python script - [sample_env.py](https://github.com/oracle-samples/oracle-db-examples/blob/main/python/sample_env.py). Run - the script using the following command: + created by running the Python script [create_schema.py][1]. The script + requires SYSDBA privileges and will prompt for these credentials as well as + the names of the schemas and edition that will be created, unless a number + of environment variables are set as documented in the Python script + [sample_env.py][2]. Run the script using the following command: python create_schema.py @@ -19,16 +19,24 @@ Oracle Database. python query.py 3. After running python-oracledb samples, the schemas and SQL objects can be - dropped by running the Python script - [drop_schema.py](https://github.com/oracle-samples/oracle-db-examples/blob/main/python/drop_schema.py). The - script requires SYSDBA privileges and will prompt for these credentials as - well as the names of the schemas and edition that will be dropped, unless a - number of environment variables are set as documented in the Python script - [sample_env.py](https://github.com/oracle-samples/oracle-db-examples/blob/main/python/sample_env.py). Run - the script using the following command: + dropped by running the Python script [drop_schema.py][3]. The script + requires SYSDBA privileges and will prompt for these credentials as well as + the names of the schemas and edition that will be dropped, unless a number + of environment variables are set as documented in the Python script + [sample_env.py][2]. Run the script using the following command: python drop_schema.py +### Examples in a Container + +The [sample_container](./sample_container) directory has a Dockerfile that will +build a container with the samples and a running Oracle Database. + +### Notebooks + +The [sample_notebooks](./sample_notebooks) directory has Jupyter notebooks with +runnable examples. + ## About python-oracledb - Python-oracledb is the new name for Oracle's popular Python cx_Oracle driver @@ -71,3 +79,8 @@ PyPI: [pypi.org/project/oracledb/](https://pypi.org/project/oracledb/) Source: [github.com/oracle/python-oracledb](https://github.com/oracle/python-oracledb) Upgrading: [Upgrading from cx_Oracle 8.3 to python-oracledb](https://python-oracledb.readthedocs.io/en/latest/user_guide/appendix_c.html#upgrading-from-cx-oracle-8-3-to-python-oracledb) + + +[1]: https://github.com/oracle/python-oracledb/blob/main/samples/create_schema.py +[2]: https://github.com/oracle/python-oracledb/blob/main/samples/sample_env.py +[3]: https://github.com/oracle/python-oracledb/blob/main/samples/drop_schema.py diff --git a/python/python-oracledb/app_context.py b/python/python-oracledb/app_context.py index ea99d26b..8757764d 100644 --- a/python/python-oracledb/app_context.py +++ b/python/python-oracledb/app_context.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,15 +25,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # app_context.py # # Demonstrates the use of application context. Application context is available # within logon triggers and can be retrieved by using the function # sys_context(). -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -44,20 +44,22 @@ # client context attributes to be set APP_CTX_NAMESPACE = "CLIENTCONTEXT" APP_CTX_ENTRIES = [ - ( APP_CTX_NAMESPACE, "ATTR1", "VALUE1" ), - ( APP_CTX_NAMESPACE, "ATTR2", "VALUE2" ), - ( APP_CTX_NAMESPACE, "ATTR3", "VALUE3" ) + (APP_CTX_NAMESPACE, "ATTR1", "VALUE1"), + (APP_CTX_NAMESPACE, "ATTR2", "VALUE2"), + (APP_CTX_NAMESPACE, "ATTR3", "VALUE3"), ] -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), - appcontext=APP_CTX_ENTRIES) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + appcontext=APP_CTX_ENTRIES, +) with connection.cursor() as cursor: - for namespace, name, value in APP_CTX_ENTRIES: - cursor.execute("select sys_context(:1, :2) from dual", - (namespace, name)) - value, = cursor.fetchone() + cursor.execute( + "select sys_context(:1, :2) from dual", (namespace, name) + ) + (value,) = cursor.fetchone() print("Value of context key", name, "is", value) diff --git a/python/python-oracledb/aq_notification.py b/python/python-oracledb/aq_notification.py index 1eb403f7..5315c26f 100644 --- a/python/python-oracledb/aq_notification.py +++ b/python/python-oracledb/aq_notification.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2018, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2018, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,15 +20,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # aq_notification.py # # Demonstrates using advanced queuing notification. Once this script is # running, run object_aq.py in another terminal to enqueue a few messages to # the "DEMO_BOOK_QUEUE" queue. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import time @@ -40,6 +40,7 @@ registered = True + def process_messages(message): global registered print("Message type:", message.type) @@ -51,14 +52,20 @@ def process_messages(message): print("Consumer name:", message.consumer_name) print("Message id:", message.msgid) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), - events=True) -sub = connection.subscribe(namespace=oracledb.SUBSCR_NAMESPACE_AQ, - name="DEMO_BOOK_QUEUE", callback=process_messages, - timeout=300) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + events=True, +) + +sub = connection.subscribe( + namespace=oracledb.SUBSCR_NAMESPACE_AQ, + name="DEMO_BOOK_QUEUE", + callback=process_messages, + timeout=300, +) print("Subscription:", sub) print("--> Connection:", sub.connection) print("--> Callback:", sub.callback) diff --git a/python/python-oracledb/array_dml_rowcounts.py b/python/python-oracledb/array_dml_rowcounts.py index 487ac8c7..47fc3b1c 100644 --- a/python/python-oracledb/array_dml_rowcounts.py +++ b/python/python-oracledb/array_dml_rowcounts.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,16 +20,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # array_dml_rowcounts.py # # Demonstrates the use of the 12.1 feature that allows cursor.executemany() # to return the number of rows affected by each individual execution as a list. # The parameter "arraydmlrowcounts" must be set to True in the call to # cursor.executemany() after which cursor.getarraydmlrowcounts() can be called. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -38,19 +38,23 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # show the number of rows for each parent ID as a means of verifying the # output from the delete statement - for parent_id, count in cursor.execute(""" - select ParentId, count(*) - from ChildTable - group by ParentId - order by ParentId"""): + for parent_id, count in cursor.execute( + """ + select ParentId, count(*) + from ChildTable + group by ParentId + order by ParentId + """ + ): print("Parent ID:", parent_id, "has", int(count), "rows.") print() @@ -61,11 +65,11 @@ print() # enable array DML row counts for each iteration executed in executemany() - cursor.executemany(""" - delete from ChildTable - where ParentId = :1""", - [(i,) for i in parent_ids_to_delete], - arraydmlrowcounts = True) + cursor.executemany( + "delete from ChildTable where ParentId = :1", + [(i,) for i in parent_ids_to_delete], + arraydmlrowcounts=True, + ) # display the number of rows deleted for each parent ID row_counts = cursor.getarraydmlrowcounts() diff --git a/python/python-oracledb/array_dml_rowcounts_async.py b/python/python-oracledb/array_dml_rowcounts_async.py new file mode 100644 index 00000000..8b5b72b1 --- /dev/null +++ b/python/python-oracledb/array_dml_rowcounts_async.py @@ -0,0 +1,84 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# array_dml_rowcounts_async.py +# +# An asynchronous version of array_dml_rowcounts.py +# +# Demonstrates the use of the 12.1 feature that allows cursor.executemany() +# to return the number of rows affected by each individual execution as a list. +# The parameter "arraydmlrowcounts" must be set to True in the call to +# cursor.executemany() after which cursor.getarraydmlrowcounts() can be called. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # show the number of rows for each parent ID as a means of verifying + # the output from the delete statement + await cursor.execute( + """ + select ParentId, count(*) + from ChildTable + group by ParentId + order by ParentId + """ + ) + async for parent_id, count in cursor: + print("Parent ID:", parent_id, "has", int(count), "rows.") + print() + + # delete the following parent IDs only + parent_ids_to_delete = [20, 30, 50] + + print("Deleting Parent IDs:", parent_ids_to_delete) + print() + + # enable array DML row counts for each iteration executed in + # executemany() + await cursor.executemany( + "delete from ChildTable where ParentId = :1", + [(i,) for i in parent_ids_to_delete], + arraydmlrowcounts=True, + ) + + # display the number of rows deleted for each parent ID + row_counts = cursor.getarraydmlrowcounts() + for parent_id, count in zip(parent_ids_to_delete, row_counts): + print("Parent ID:", parent_id, "deleted", count, "rows.") + + +asyncio.run(main()) diff --git a/python/python-oracledb/async_gather.py b/python/python-oracledb/async_gather.py new file mode 100644 index 00000000..cc21fc1d --- /dev/null +++ b/python/python-oracledb/async_gather.py @@ -0,0 +1,81 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# async_gather.py +# +# Demonstrates using a connection pool with asyncio and gather(). +# +# Multiple database sessions will be opened and used by each coroutine. The +# number of connections opened can depend on the speed of your environment. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + +# Number of coroutines to run +CONCURRENCY = 5 + +# Query the unique session identifier/serial number combination of a connection +SQL = """SELECT UNIQUE CURRENT_TIMESTAMP AS CT, sid||'-'||serial# AS SIDSER + FROM v$session_connect_info + WHERE sid = SYS_CONTEXT('USERENV', 'SID')""" + + +# Show the unique session identifier/serial number of each connection that the +# pool opens +async def init_session(connection, requested_tag): + res = await connection.fetchone(SQL) + print(res[0].strftime("%H:%M:%S.%f"), "- init_session SID-SERIAL#", res[1]) + + +# The coroutine simply shows the session identifier/serial number of the +# connection returned by the pool.acquire() call +async def query(pool): + async with pool.acquire() as connection: + await connection.callproc("dbms_session.sleep", [1]) + res = await connection.fetchone(SQL) + print(res[0].strftime("%H:%M:%S.%f"), "- query SID-SERIAL#", res[1]) + + +async def main(): + pool = oracledb.create_pool_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + min=1, + max=CONCURRENCY, + session_callback=init_session, + ) + + coroutines = [query(pool) for i in range(CONCURRENCY)] + + await asyncio.gather(*coroutines) + + await pool.close() + + +asyncio.run(main()) diff --git a/python/python-oracledb/batch_errors.py b/python/python-oracledb/batch_errors.py index 92bd74b8..b5ef1b26 100644 --- a/python/python-oracledb/batch_errors.py +++ b/python/python-oracledb/batch_errors.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # batch_errors.py # # Demonstrates the use of the Oracle Database 12.1 feature that allows @@ -31,7 +31,7 @@ # executions. The parameter "batcherrors" must be set to True in the # call to cursor.executemany() after which cursor.getbatcherrors() can # be called, which will return a list of error objects. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -40,29 +40,28 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # retrieve the number of rows in the table - cursor.execute(""" - select count(*) - from ChildTable""") - count, = cursor.fetchone() + cursor.execute("select count(*) from ChildTable") + (count,) = cursor.fetchone() print("Number of rows in child table:", int(count)) # define data to insert data_to_insert = [ - (1016, 10, 'Child B of Parent 10'), - (1017, 10, 'Child C of Parent 10'), - (1018, 20, 'Child D of Parent 20'), - (1018, 20, 'Child D of Parent 20'), # duplicate key - (1019, 30, 'Child C of Parent 30'), - (1020, 30, 'Child D of Parent 40'), - (1021, 60, 'Child A of Parent 60'), # parent does not exist - (1022, 40, 'Child F of Parent 40'), + (1016, 10, "Child B of Parent 10"), + (1017, 10, "Child C of Parent 10"), + (1018, 20, "Child D of Parent 20"), + (1018, 20, "Child D of Parent 20"), # duplicate key + (1019, 30, "Child C of Parent 30"), + (1020, 30, "Child D of Parent 40"), + (1021, 60, "Child A of Parent 60"), # parent does not exist + (1022, 40, "Child F of Parent 40"), ] print("Number of rows to insert:", len(data_to_insert)) @@ -70,18 +69,17 @@ # first error takes place; the row count is updated to show how many rows # actually succeeded try: - cursor.executemany("insert into ChildTable values (:1, :2, :3)", - data_to_insert) + cursor.executemany( + "insert into ChildTable values (:1, :2, :3)", data_to_insert + ) except oracledb.DatabaseError as e: - error, = e.args + (error,) = e.args print("Failure with error:", error.message) print("Number of rows successfully inserted:", cursor.rowcount) # demonstrate that the row count is accurate - cursor.execute(""" - select count(*) - from ChildTable""") - count, = cursor.fetchone() + cursor.execute("select count(*) from ChildTable") + (count,) = cursor.fetchone() print("Number of rows in child table after failed insert:", int(count)) # roll back so we can perform the same work using the new method @@ -89,9 +87,12 @@ # new method: executemany() with batch errors enabled (and array DML row # counts also enabled) results in no immediate error being raised - cursor.executemany("insert into ChildTable values (:1, :2, :3)", - data_to_insert, batcherrors=True, - arraydmlrowcounts=True) + cursor.executemany( + "insert into ChildTable values (:1, :2, :3)", + data_to_insert, + batcherrors=True, + arraydmlrowcounts=True, + ) # display the errors that have taken place errors = cursor.getbatcherrors() @@ -106,9 +107,10 @@ # demonstrate that all of the rows without errors have been successfully # inserted - cursor.execute(""" - select count(*) - from ChildTable""") - count, = cursor.fetchone() - print("Number of rows in child table after insert with batcherrors " - "enabled:", int(count)) + cursor.execute("select count(*) from ChildTable") + (count,) = cursor.fetchone() + print( + "Number of rows in child table after insert with batcherrors " + "enabled:", + int(count), + ) diff --git a/python/python-oracledb/batch_errors_async.py b/python/python-oracledb/batch_errors_async.py new file mode 100644 index 00000000..3c865bbc --- /dev/null +++ b/python/python-oracledb/batch_errors_async.py @@ -0,0 +1,123 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# batch_errors_async.py +# +# An asynchronous version of batch_errors.py +# +# Demonstrates the use of the Oracle Database 12.1 feature that allows +# cursor.executemany() to complete successfully, even if errors take +# place during the execution of one or more of the individual +# executions. The parameter "batcherrors" must be set to True in the +# call to cursor.executemany() after which cursor.getbatcherrors() can +# be called, which will return a list of error objects. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # retrieve the number of rows in the table + await cursor.execute("select count(*) from ChildTable") + (count,) = await cursor.fetchone() + print("Number of rows in child table:", int(count)) + + # define data to insert + data_to_insert = [ + (1016, 10, "Child B of Parent 10"), + (1017, 10, "Child C of Parent 10"), + (1018, 20, "Child D of Parent 20"), + (1018, 20, "Child D of Parent 20"), # duplicate key + (1019, 30, "Child C of Parent 30"), + (1020, 30, "Child D of Parent 40"), + (1021, 60, "Child A of Parent 60"), # parent does not exist + (1022, 40, "Child F of Parent 40"), + ] + print("Number of rows to insert:", len(data_to_insert)) + + # old method: executemany() with data errors results in stoppage after + # the first error takes place; the row count is updated to show how + # many rows actually succeeded + try: + await cursor.executemany( + "insert into ChildTable values (:1, :2, :3)", data_to_insert + ) + except oracledb.DatabaseError as e: + (error,) = e.args + print("Failure with error:", error.message) + print("Number of rows successfully inserted:", cursor.rowcount) + + # demonstrate that the row count is accurate + await cursor.execute("select count(*) from ChildTable") + (count,) = await cursor.fetchone() + print("Number of rows in child table after failed insert:", int(count)) + + # roll back so we can perform the same work using the new method + await connection.rollback() + + # new method: executemany() with batch errors enabled (and array DML + # row counts also enabled) results in no immediate error being raised + await cursor.executemany( + "insert into ChildTable values (:1, :2, :3)", + data_to_insert, + batcherrors=True, + arraydmlrowcounts=True, + ) + + # display the errors that have taken place + errors = cursor.getbatcherrors() + print("Number of rows with bad values:", len(errors)) + for error in errors: + print( + "Error", error.message.rstrip(), "at row offset", error.offset + ) + + # arraydmlrowcounts also shows rows with invalid data: they have a row + # count of 0; otherwise 1 is shown + row_counts = cursor.getarraydmlrowcounts() + print("Array DML row counts:", row_counts) + + # demonstrate that all of the rows without errors have been + # successfully inserted + await cursor.execute("select count(*) from ChildTable") + (count,) = await cursor.fetchone() + print( + "Number of rows in child table after insert with batcherrors " + "enabled:", + int(count), + ) + + +asyncio.run(main()) diff --git a/python/python-oracledb/bind_insert.py b/python/python-oracledb/bind_insert.py index 4a74e709..5bb6b3c6 100644 --- a/python/python-oracledb/bind_insert.py +++ b/python/python-oracledb/bind_insert.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,13 +20,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # bind_insert.py # # Demonstrates how to insert rows into a table using bind variables. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -35,26 +35,27 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # "Bind by position" -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- rows = [ (1, "First"), (2, "Second"), (3, "Third"), (4, "Fourth"), - (5, None), # Insert a NULL value + (5, None), # Insert a NULL value (6, "Sixth"), - (7, "Seventh") + (7, "Seventh"), ] with connection.cursor() as cursor: - # predefine the maximum string size to avoid data scans and memory # reallocations. The value 'None' indicates that the default processing # can take place @@ -62,33 +63,29 @@ cursor.executemany("insert into mytab(id, data) values (:1, :2)", rows) -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # "Bind by name" -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- rows = [ {"d": "Eighth", "i": 8}, - {"d": "Ninth", "i": 9}, - {"d": "Tenth", "i": 10}, - {"i": 11} # Insert a NULL value + {"d": "Ninth", "i": 9}, + {"d": "Tenth", "i": 10}, + {"i": 11}, # Insert a NULL value ] with connection.cursor() as cursor: - # Predefine maximum string size to avoid data scans and memory # reallocations cursor.setinputsizes(d=20) cursor.executemany("insert into mytab(id, data) values (:i, :d)", rows) -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Inserting a single bind still needs tuples -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -rows = [ - ("Eleventh",), - ("Twelth",) -] +rows = [("Eleventh",), ("Twelth",)] with connection.cursor() as cursor: cursor.executemany("insert into mytab(id, data) values (12, :1)", rows) @@ -96,9 +93,9 @@ # Don't commit - this lets the demo be run multiple times # connection.commit() -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Now query the results back -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- with connection.cursor() as cursor: for row in cursor.execute("select * from mytab order by id"): diff --git a/python/python-oracledb/bind_insert_async.py b/python/python-oracledb/bind_insert_async.py new file mode 100644 index 00000000..853281cf --- /dev/null +++ b/python/python-oracledb/bind_insert_async.py @@ -0,0 +1,111 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# bind_insert_async.py +# +# An asynchronous version of bind_insert.py +# +# Demonstrates how to insert rows into a table using bind variables. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # ------------------------------------------------------------------------- + # "Bind by position" + # ------------------------------------------------------------------------- + + rows = [ + (1, "First"), + (2, "Second"), + (3, "Third"), + (4, "Fourth"), + (5, None), # Insert a NULL value + (6, "Sixth"), + (7, "Seventh"), + ] + + with connection.cursor() as cursor: + # predefine the maximum string size to avoid data scans and memory + # reallocations. The value 'None' indicates that the default + # processing can take place + cursor.setinputsizes(None, 20) + + await cursor.executemany( + "insert into mytab(id, data) values (:1, :2)", rows + ) + + # ------------------------------------------------------------------------- + # "Bind by name" + # ------------------------------------------------------------------------- + + rows = [ + {"d": "Eighth", "i": 8}, + {"d": "Ninth", "i": 9}, + {"d": "Tenth", "i": 10}, + {"i": 11}, # Insert a NULL value + ] + + with connection.cursor() as cursor: + # Predefine maximum string size to avoid data scans and memory + # reallocations + cursor.setinputsizes(d=20) + + await cursor.executemany( + "insert into mytab(id, data) values (:i, :d)", rows + ) + + # ------------------------------------------------------------------------- + # Inserting a single bind still needs tuples + # ------------------------------------------------------------------------- + + rows = [("Eleventh",), ("Twelth",)] + + await connection.executemany( + "insert into mytab(id, data) values (12, :1)", rows + ) + + # Don't commit - this lets the demo be run multiple times + # await connection.commit() + + # ------------------------------------------------------------------------- + # Now query the results back + # ------------------------------------------------------------------------- + + for row in await connection.fetchall("select * from mytab order by id"): + print(row) + + +asyncio.run(main()) diff --git a/python/python-oracledb/bind_query.py b/python/python-oracledb/bind_query.py index a45595c1..e286cf4c 100644 --- a/python/python-oracledb/bind_query.py +++ b/python/python-oracledb/bind_query.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # bind_query.py # # Demonstrates the use of bind variables in queries. Binding is important for @@ -31,7 +31,7 @@ # increasing performance. It also permits data to be bound without having to be # concerned about escaping special characters, or be concerned about SQL # injection attacks. -##------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -40,21 +40,22 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # Bind by position with lists with connection.cursor() as cursor: - print("1. Bind by position: single value list") - sql = 'select * from SampleQueryTab where id = :bvid' + sql = "select * from SampleQueryTab where id = :bvid" for row in cursor.execute(sql, [1]): print(row) print() print("2. Bind by position: multiple values") - sql = 'select * from SampleQueryTab where id = :bvid and 123 = :otherbind' + sql = "select * from SampleQueryTab where id = :bvid and 123 = :otherbind" for row in cursor.execute(sql, [2, 123]): print(row) print() @@ -63,8 +64,10 @@ # order of the placeholders used in the SQL statement. The bind list data # order is not associated by the name of the bind variable placeholders in # the SQL statement, even though those names are ":1" and ":2". - print("3. Bind by position: multiple values with numeric placeholder names") - sql = 'select * from SampleQueryTab where id = :2 and 456 = :1' + print( + "3. Bind by position: multiple values with numeric placeholder names" + ) + sql = "select * from SampleQueryTab where id = :2 and 456 = :1" for row in cursor.execute(sql, [3, 456]): print(row) print() @@ -72,7 +75,7 @@ # With bind-by-position, repeated use of bind placeholder names in the SQL # statement requires the input list data to be repeated. print("4. Bind by position: multiple values with a repeated placeholder") - sql = 'select * from SampleQueryTab where id = :2 and 3 = :2' + sql = "select * from SampleQueryTab where id = :2 and 3 = :2" for row in cursor.execute(sql, [3, 3]): print(row) print() @@ -80,24 +83,22 @@ # Bind by position with tuples with connection.cursor() as cursor: - print("5. Bind by position with single value tuple") - sql = 'select * from SampleQueryTab where id = :bvid' + sql = "select * from SampleQueryTab where id = :bvid" for row in cursor.execute(sql, (4,)): print(row) print() print("6. Bind by position with a multiple value tuple") - sql = 'select * from SampleQueryTab where id = :bvid and 789 = :otherbind' - for row in cursor.execute(sql, (4,789)): + sql = "select * from SampleQueryTab where id = :bvid and 789 = :otherbind" + for row in cursor.execute(sql, (4, 789)): print(row) print() # Bind by name with a dictionary with connection.cursor() as cursor: - print("7. Bind by name with a dictionary") - sql = 'select * from SampleQueryTab where id = :bvid' + sql = "select * from SampleQueryTab where id = :bvid" for row in cursor.execute(sql, {"bvid": 4}): print(row) print() @@ -105,7 +106,7 @@ # With bind-by-name, repeated use of bind placeholder names in the SQL # statement lets you supply the data once. print("8. Bind by name with multiple value dict and repeated placeholders") - sql = 'select * from SampleQueryTab where id = :bvid and 4 = :bvid' + sql = "select * from SampleQueryTab where id = :bvid and 4 = :bvid" for row in cursor.execute(sql, {"bvid": 4}): print(row) print() @@ -113,15 +114,14 @@ # Bind by name with parameters. The execute() parameter names match the bind # variable placeholder names. with connection.cursor() as cursor: - print("9. Bind by name using parameters") - sql = 'select * from SampleQueryTab where id = :bvid' + sql = "select * from SampleQueryTab where id = :bvid" for row in cursor.execute(sql, bvid=5): print(row) print() print("10. Bind by name using multiple parameters") - sql = 'select * from SampleQueryTab where id = :bvid and 101 = :otherbind' + sql = "select * from SampleQueryTab where id = :bvid and 101 = :otherbind" for row in cursor.execute(sql, bvid=5, otherbind=101): print(row) print() @@ -129,14 +129,14 @@ # With bind-by-name, repeated use of bind placeholder names in the SQL # statement lets you supply the data once. print("11. Bind by name: multiple values with repeated placeholder names") - sql = 'select * from SampleQueryTab where id = :bvid and 6 = :bvid' + sql = "select * from SampleQueryTab where id = :bvid and 6 = :bvid" for row in cursor.execute(sql, bvid=6): print(row) print() # Rexcuting a query with different data values with connection.cursor() as cursor: - sql = 'select * from SampleQueryTab where id = :bvid' + sql = "select * from SampleQueryTab where id = :bvid" print("12. Query results with id = 7") for row in cursor.execute(sql, [4]): diff --git a/python/python-oracledb/bind_query_async.py b/python/python-oracledb/bind_query_async.py new file mode 100644 index 00000000..836bcdd5 --- /dev/null +++ b/python/python-oracledb/bind_query_async.py @@ -0,0 +1,136 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# bind_query_async.py +# +# An asynchronous version of bind_query.py +# +# Demonstrates the use of bind variables in queries. Binding is important for +# scalability and security. Since the text of a query that is re-executed is +# unchanged, no additional parsing is required, thereby reducing overhead and +# increasing performance. It also permits data to be bound without having to be +# concerned about escaping special characters, or be concerned about SQL +# injection attacks. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # Bind by position with lists + print("1. Bind by position: single value list") + sql = "select * from SampleQueryTab where id = :bvid" + print(await connection.fetchone(sql, [1])) + print() + + print("2. Bind by position: multiple values") + sql = "select * from SampleQueryTab where id = :bvid and 123 = :otherbind" + print(await connection.fetchone(sql, [2, 123])) + print() + + # With bind-by-position, the order of the data in the bind list matches + # the order of the placeholders used in the SQL statement. The bind + # list data order is not associated by the name of the bind variable + # placeholders in the SQL statement, even though those names are ":1" + # and ":2". + print( + "3. Bind by position: multiple values with numeric placeholder names" + ) + sql = "select * from SampleQueryTab where id = :2 and 456 = :1" + print(await connection.fetchone(sql, [3, 456])) + print() + + # With bind-by-position, repeated use of bind placeholder names in the + # SQL statement requires the input list data to be repeated. + print("4. Bind by position: multiple values with a repeated placeholder") + sql = "select * from SampleQueryTab where id = :2 and 3 = :2" + print(await connection.fetchall(sql, [3, 3])) + print() + + # Bind by position with tuples + print("5. Bind by position with single value tuple") + sql = "select * from SampleQueryTab where id = :bvid" + print(await connection.fetchone(sql, (4,))) + print() + + print("6. Bind by position with a multiple value tuple") + sql = "select * from SampleQueryTab where id = :bvid and 789 = :otherbind" + print(await connection.fetchone(sql, (4, 789))) + print() + + # Bind by name with a dictionary + print("7. Bind by name with a dictionary") + sql = "select * from SampleQueryTab where id = :bvid" + print(await connection.fetchone(sql, {"bvid": 4})) + print() + + # With bind-by-name, repeated use of bind placeholder names in the SQL + # statement lets you supply the data once. + print("8. Bind by name with multiple value dict and repeated placeholders") + sql = "select * from SampleQueryTab where id = :bvid and 4 = :bvid" + print(await connection.fetchone(sql, {"bvid": 4})) + print() + + # Bind by name with parameters. The execute() parameter names match the + # bind variable placeholder names. + print("9. Bind by name using parameters") + sql = "select * from SampleQueryTab where id = :bvid" + print(await connection.fetchone(sql, dict(bvid=5))) + print() + + print("10. Bind by name using multiple parameters") + sql = "select * from SampleQueryTab where id = :bvid and 101 = :otherbind" + print(await connection.fetchone(sql, dict(bvid=5, otherbind=101))) + print() + + # With bind-by-name, repeated use of bind placeholder names in the SQL + # statement lets you supply the data once. + print("11. Bind by name: multiple values with repeated placeholder names") + sql = "select * from SampleQueryTab where id = :bvid and 6 = :bvid" + print(await connection.fetchone(sql, dict(bvid=6))) + print() + + # Rexcuting a query with different data values + sql = "select * from SampleQueryTab where id = :bvid" + + print("12. Query results with id = 7") + print(await connection.fetchone(sql, [4])) + print() + + print("13. Rexcuted query results with id = 1") + print(await connection.fetchone(sql, [1])) + print() + + +asyncio.run(main()) diff --git a/python/python-oracledb/bulk_aq.py b/python/python-oracledb/bulk_aq.py index 6340a97a..7c0a1e40 100644 --- a/python/python-oracledb/bulk_aq.py +++ b/python/python-oracledb/bulk_aq.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2019, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # bulk_aq.py # # Demonstrates how to use bulk enqueuing and dequeuing of messages with # advanced queuing. It makes use of a RAW queue created in the sample setup. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -50,16 +50,18 @@ "The ninth message", "The tenth message", "The eleventh message", - "The twelfth and final message" + "The twelfth and final message", ] # this script is currently only supported in python-oracledb thick mode oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) # connect to database -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # create a queue with connection.cursor() as cursor: diff --git a/python/python-oracledb/call_timeout.py b/python/python-oracledb/call_timeout.py index d7720597..965ed7fd 100644 --- a/python/python-oracledb/call_timeout.py +++ b/python/python-oracledb/call_timeout.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2019, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # call_timeout.py # # Demonstrates the use of the Oracle Client 18c feature that enables round @@ -30,7 +30,7 @@ # (in milliseconds) has passed without a response from the database. # # This script requires Oracle Client 18.1 and higher when using thick mode. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -39,23 +39,26 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) connection.call_timeout = 2000 print("Call timeout set at", connection.call_timeout, "milliseconds...") with connection.cursor() as cursor: - cursor.execute("select sysdate from dual") - today, = cursor.fetchone() + (today,) = cursor.fetchone() print("Fetch of current date before timeout:", today) # dbms_session.sleep() replaces dbms_lock.sleep() from Oracle Database 18c - sleep_proc_name = "dbms_session.sleep" \ - if int(connection.version.split(".")[0]) >= 18 \ - else "dbms_lock.sleep" + sleep_proc_name = ( + "dbms_session.sleep" + if int(connection.version.split(".")[0]) >= 18 + else "dbms_lock.sleep" + ) print("Sleeping...should time out...") try: @@ -64,5 +67,5 @@ print("ERROR:", e) cursor.execute("select sysdate from dual") - today, = cursor.fetchone() + (today,) = cursor.fetchone() print("Fetch of current date after timeout:", today) diff --git a/python/python-oracledb/call_timeout_async.py b/python/python-oracledb/call_timeout_async.py new file mode 100644 index 00000000..446a710d --- /dev/null +++ b/python/python-oracledb/call_timeout_async.py @@ -0,0 +1,73 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# call_timeout_async.py +# +# An asynchronous version of call_timeout.py +# +# Demonstrates the use of the feature that enables round trips to the database +# to time out if a specified amount of time (in milliseconds) has passed +# without a response from the database. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + connection.call_timeout = 2000 + print("Call timeout set at", connection.call_timeout, "milliseconds...") + + with connection.cursor() as cursor: + (today,) = await connection.fetchone("select sysdate from dual") + print("Fetch of current date before timeout:", today) + + # dbms_session.sleep() replaces dbms_lock.sleep() from Oracle Database + # 18c + sleep_proc_name = ( + "dbms_session.sleep" + if int(connection.version.split(".")[0]) >= 18 + else "dbms_lock.sleep" + ) + + print("Sleeping...should time out...") + try: + await cursor.callproc(sleep_proc_name, (3,)) + except oracledb.DatabaseError as e: + print("ERROR:", e) + + (today,) = await connection.fetchone("select sysdate from dual") + print("Fetch of current date after timeout:", today) + + +asyncio.run(main()) diff --git a/python/python-oracledb/connection_pool.py b/python/python-oracledb/connection_pool.py index 023c2896..92e6a2ad 100644 --- a/python/python-oracledb/connection_pool.py +++ b/python/python-oracledb/connection_pool.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2022, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # connection_pool.py # # Demonstrates the use of connection pooling using a Flask web application. @@ -56,7 +56,7 @@ # To insert new a user 'fred' you can call: # http://127.0.0.1:8080/post/fred # -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import os import sys @@ -67,17 +67,17 @@ import sample_env # Port to listen on -port = int(os.environ.get('PORT', '8080')) +port = int(os.environ.get("PORT", "8080")) # determine whether to use python-oracledb thin mode or thick mode if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + # start_pool(): starts the connection pool def start_pool(): - # Generally a fixed-size pool is recommended, i.e. pool_min=pool_max. Here # the pool contains 4 connections, which will allow 4 concurrent users. @@ -85,16 +85,19 @@ def start_pool(): pool_max = 4 pool_inc = 0 - pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), - min=pool_min, - max=pool_max, - increment=pool_inc, - session_callback=init_session) + pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + min=pool_min, + max=pool_max, + increment=pool_inc, + session_callback=init_session, + ) return pool + # init_session(): a 'session callback' to efficiently set any initial state # that each connection should have. # @@ -107,63 +110,81 @@ def start_pool(): # def init_session(connection, requestedTag_ignored): with connection.cursor() as cursor: - cursor.execute(""" + cursor.execute( + """ alter session set - time_zone = 'UTC' - nls_date_format = 'YYYY-MM-DD HH24:MI'""") + time_zone = 'UTC' + nls_date_format = 'YYYY-MM-DD HH24:MI' + """ + ) + + +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ # create_schema(): drop and create the demo table, and add a row def create_schema(): with pool.acquire() as connection: with connection.cursor() as cursor: - cursor.execute(""" + cursor.execute( + """ begin - begin - execute immediate 'drop table demo'; + begin + execute immediate 'drop table demo'; exception when others then - if sqlcode <> -942 then - raise; - end if; - end; + if sqlcode <> -942 then + raise; + end if; + end; - execute immediate 'create table demo ( - id number generated by default as identity, - username varchar2(40))'; + execute immediate 'create table demo ( + id number generated by default as identity, + username varchar2(40) + )'; - execute immediate 'insert into demo (username) values (''chris'')'; + execute immediate 'insert into demo (username) values + (''chris'')'; - commit; - end;""") + commit; + end; + """ + ) -#------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- app = Flask(__name__) + # Display a welcome message on the 'home' page -@app.route('/') +@app.route("/") def index(): return "Welcome to the demo app" + # Add a new username # # The new user's id is generated by the database and returned in the OUT bind # variable 'idbv'. -@app.route('/post/') +@app.route("/post/") def post(username): with pool.acquire() as connection: with connection.cursor() as cursor: connection.autocommit = True idbv = cursor.var(int) - cursor.execute(""" + cursor.execute( + """ insert into demo (username) values (:unbv) - returning id into :idbv""", [username, idbv]) - return f'Inserted {username} with id {idbv.getvalue()[0]}' + returning id into :idbv + """, + [username, idbv], + ) + return f"Inserted {username} with id {idbv.getvalue()[0]}" + # Show the username for a given id -@app.route('/user/') +@app.route("/user/") def show_username(id): with pool.acquire() as connection: with connection.cursor() as cursor: @@ -171,10 +192,10 @@ def show_username(id): r = cursor.fetchone() return r[0] if r is not None else "Unknown user id" -#------------------------------------------------------------------------------ -if __name__ == '__main__': +# ----------------------------------------------------------------------------- +if __name__ == "__main__": # Start a pool of connections pool = start_pool() @@ -182,7 +203,7 @@ def show_username(id): create_schema() m = f"\nTry loading http://127.0.0.1:{port}/user/1 in a browser\n" - sys.modules['flask.cli'].show_server_banner = lambda *x: print(m) + sys.modules["flask.cli"].show_server_banner = lambda *x: print(m) # Start a webserver app.run(port=port) diff --git a/python/python-oracledb/cqn.py b/python/python-oracledb/cqn.py index 8e1f988f..c5c6516a 100644 --- a/python/python-oracledb/cqn.py +++ b/python/python-oracledb/cqn.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,16 +25,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # cqn.py # # Demonstrates using continuous query notification in Python, a feature that is # available in Oracle 11g and later. Once this script is running, use another # session to insert, update or delete rows from the table TestTempTable and you # will see the notification of that change. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import time @@ -46,6 +46,7 @@ registered = True + def callback(message): global registered print("Message type:", message.type) @@ -70,14 +71,18 @@ def callback(message): print("-" * 60) print("=" * 60) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), - events=True) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + events=True, +) qos = oracledb.SUBSCR_QOS_QUERY | oracledb.SUBSCR_QOS_ROWIDS -sub = connection.subscribe(callback=callback, timeout=1800, - qos=qos, client_initiated=True) +sub = connection.subscribe( + callback=callback, timeout=1800, qos=qos, client_initiated=True +) print("Subscription:", sub) print("--> Connection:", sub.connection) print("--> Callback:", sub.callback) diff --git a/python/python-oracledb/cqn_pool.py b/python/python-oracledb/cqn_pool.py index ec64712a..a4fd6e92 100644 --- a/python/python-oracledb/cqn_pool.py +++ b/python/python-oracledb/cqn_pool.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # cqn_pool.py # # Demonstrates using continuous query notification in Python, a feature that is @@ -33,7 +33,7 @@ # This script differs from cqn.py in that it shows how a connection can be # acquired from a session pool and used to query the changes that have been # made. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import time @@ -45,6 +45,7 @@ registered = True + def callback(message): global registered if not message.registered: @@ -69,26 +70,36 @@ def callback(message): if row.operation & oracledb.OPCODE_UPDATE: ops.append("updated") cursor = connection.cursor() - cursor.execute(""" - select IntCol - from TestTempTable - where rowid = :rid""", - rid=row.rowid) - int_col, = cursor.fetchone() + cursor.execute( + """ + select IntCol + from TestTempTable + where rowid = :rid + """, + rid=row.rowid, + ) + (int_col,) = cursor.fetchone() print(" Row with IntCol", int_col, "was", " and ".join(ops)) if num_rows_deleted > 0: print(" ", num_rows_deleted, "rows deleted") print("=" * 60) -pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), - min=1, max=4, increment=1, events=True) + +pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + min=1, + max=4, + increment=1, + events=True, +) with pool.acquire() as connection: qos = oracledb.SUBSCR_QOS_QUERY | oracledb.SUBSCR_QOS_ROWIDS - sub = connection.subscribe(callback=callback, timeout=1800, - qos=qos, client_initiated=True) + sub = connection.subscribe( + callback=callback, timeout=1800, qos=qos, client_initiated=True + ) print("Subscription created with ID:", sub.id) query_id = sub.registerquery("select * from TestTempTable") print("Registered query with ID:", query_id) diff --git a/python/python-oracledb/create_schema.py b/python/python-oracledb/create_schema.py index 097acd50..e054a015 100644 --- a/python/python-oracledb/create_schema.py +++ b/python/python-oracledb/create_schema.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,36 +20,38 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # create_schema.py # # Creates users and populates their schemas with the tables and packages # necessary for running the python-oracledb sample scripts. An edition is also # created for the demonstration of PL/SQL editioning. -#------------------------------------------------------------------------------ - -import oracledb +# ----------------------------------------------------------------------------- import drop_schema import sample_env # connect as administrative user (usually SYSTEM or ADMIN) -conn = oracledb.connect(sample_env.get_admin_connect_string()) +conn = sample_env.get_admin_connection() # drop existing users and editions, if applicable drop_schema.drop_schema(conn) # create sample schema and edition print("Creating sample schemas and edition...") -sample_env.run_sql_script(conn, "create_schema", - main_user=sample_env.get_main_user(), - main_password=sample_env.get_main_password(), - edition_user=sample_env.get_edition_user(), - edition_password=sample_env.get_edition_password(), - edition_name=sample_env.get_edition_name()) +sample_env.run_sql_script( + conn, + "create_schema", + main_user=sample_env.get_main_user(), + main_password=sample_env.get_main_password(), + edition_user=sample_env.get_edition_user(), + edition_password=sample_env.get_edition_password(), + edition_name=sample_env.get_edition_name(), +) if sample_env.get_server_version() >= (21, 0): - sample_env.run_sql_script(conn, "create_schema_21", - main_user=sample_env.get_main_user()) + sample_env.run_sql_script( + conn, "create_schema_21", main_user=sample_env.get_main_user() + ) print("Done.") diff --git a/python/python-oracledb/database_change_notification.py b/python/python-oracledb/database_change_notification.py index f4e1b07a..7861ab2c 100644 --- a/python/python-oracledb/database_change_notification.py +++ b/python/python-oracledb/database_change_notification.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,16 +25,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # database_change_notification.py # # Demonstrates using database change notification in Python, a feature that is # available in Oracle 10g Release 2. Once this script is running, use another # session to insert, update or delete rows from the table TestTempTable and you # will see the notification of that change. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import time @@ -46,6 +46,7 @@ registered = True + def callback(message): global registered print("Message type:", message.type) @@ -67,13 +68,20 @@ def callback(message): print("-" * 60) print("=" * 60) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), - events=True) -sub = connection.subscribe(callback=callback, timeout=1800, - qos=oracledb.SUBSCR_QOS_ROWIDS, client_initiated=True) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + events=True, +) + +sub = connection.subscribe( + callback=callback, + timeout=1800, + qos=oracledb.SUBSCR_QOS_ROWIDS, + client_initiated=True, +) print("Subscription:", sub) print("--> Connection:", sub.connection) print("--> ID:", sub.id) diff --git a/python/python-oracledb/database_shutdown.py b/python/python-oracledb/database_shutdown.py index 50934b27..a4d940af 100644 --- a/python/python-oracledb/database_shutdown.py +++ b/python/python-oracledb/database_shutdown.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # database_shutdown.py # # Demonstrates shutting down a database using Python. The connection used # assumes that the environment variable ORACLE_SID has been set. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env diff --git a/python/python-oracledb/database_startup.py b/python/python-oracledb/database_startup.py index c92c12f9..adf1ba87 100644 --- a/python/python-oracledb/database_startup.py +++ b/python/python-oracledb/database_startup.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # database_startup.py # # Demonstrates starting up a database using Python. The connection used # assumes that the environment variable ORACLE_SID has been set. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env diff --git a/python/python-oracledb/dbms_output.py b/python/python-oracledb/dbms_output.py index 12a6d82d..0f2202ff 100644 --- a/python/python-oracledb/dbms_output.py +++ b/python/python-oracledb/dbms_output.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,14 +20,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # dbms_output.py # # Demonstrates one method of fetching the lines produced by the DBMS_OUTPUT # package. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -36,22 +36,26 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # enable DBMS_OUTPUT cursor.callproc("dbms_output.enable") # execute some PL/SQL that generates output with DBMS_OUTPUT.PUT_LINE - cursor.execute(""" - begin - dbms_output.put_line('This is some text'); - dbms_output.put_line(''); - dbms_output.put_line('Demonstrating use of DBMS_OUTPUT'); - end;""") + cursor.execute( + """ + begin + dbms_output.put_line('This is some text'); + dbms_output.put_line(''); + dbms_output.put_line('Demonstrating use of DBMS_OUTPUT'); + end; + """ + ) # tune this size for your application chunk_size = 10 diff --git a/python/python-oracledb/dbms_output_async.py b/python/python-oracledb/dbms_output_async.py new file mode 100644 index 00000000..a0f5b2d6 --- /dev/null +++ b/python/python-oracledb/dbms_output_async.py @@ -0,0 +1,83 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dbms_output_async.py +# +# An asynchronous version of dbms_output.py +# +# Demonstrates one method of fetching the lines produced by the DBMS_OUTPUT +# package. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # enable DBMS_OUTPUT + await cursor.callproc("dbms_output.enable") + + # execute some PL/SQL that generates output with DBMS_OUTPUT.PUT_LINE + await cursor.execute( + """ + begin + dbms_output.put_line('This is some text'); + dbms_output.put_line(''); + dbms_output.put_line('Demonstrating use of DBMS_OUTPUT'); + end; + """ + ) + + # tune this size for your application + chunk_size = 10 + + # create variables to hold the output + lines_var = cursor.arrayvar(str, chunk_size) + num_lines_var = cursor.var(int) + num_lines_var.setvalue(0, chunk_size) + + # fetch the text that was added by PL/SQL + while True: + await cursor.callproc( + "dbms_output.get_lines", (lines_var, num_lines_var) + ) + num_lines = num_lines_var.getvalue() + lines = lines_var.getvalue()[:num_lines] + for line in lines: + print(line or "") + if num_lines < chunk_size: + break + + +asyncio.run(main()) diff --git a/python/python-oracledb/dml_returning_multiple_rows.py b/python/python-oracledb/dml_returning_multiple_rows.py index 038100df..f6464ba8 100644 --- a/python/python-oracledb/dml_returning_multiple_rows.py +++ b/python/python-oracledb/dml_returning_multiple_rows.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2017, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2017, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # dml_returning_multiple_rows.py # # Demonstrates the use of DML returning with multiple rows being returned at # once. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -41,12 +41,13 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # truncate table first so that script can be rerun print("Truncating table...") cursor.execute("truncate table TestTempTable") @@ -62,11 +63,14 @@ int_col = cursor.var(int) string_col = cursor.var(str) print("Deleting data with DML returning...") - cursor.execute(""" - delete from TestTempTable - returning IntCol, StringCol into :int_col, :string_col""", - int_col=int_col, - string_col=string_col) + cursor.execute( + """ + delete from TestTempTable + returning IntCol, StringCol into :int_col, :string_col + """, + int_col=int_col, + string_col=string_col, + ) print("Data returned:") for int_val, string_val in zip(int_col.getvalue(), string_col.getvalue()): print(tuple([int_val, string_val])) diff --git a/python/python-oracledb/dml_returning_multiple_rows_async.py b/python/python-oracledb/dml_returning_multiple_rows_async.py new file mode 100644 index 00000000..1a2963e0 --- /dev/null +++ b/python/python-oracledb/dml_returning_multiple_rows_async.py @@ -0,0 +1,80 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dml_returning_multiple_rows_async.py +# +# An asynchronous version of dml_returning_multiple_rows.py +# +# Demonstrates the use of DML returning with multiple rows being returned at +# once. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # truncate table first so that script can be rerun + print("Truncating table...") + await cursor.execute("truncate table TestTempTable") + + # populate table with a few rows + for i in range(5): + data = (i + 1, "Test String #%d" % (i + 1)) + print("Adding row", data) + await cursor.execute( + "insert into TestTempTable values (:1, :2)", data + ) + + # now delete them and use DML returning to return the data that was + # deleted + int_col = cursor.var(int) + string_col = cursor.var(str) + print("Deleting data with DML returning...") + await cursor.execute( + """ + delete from TestTempTable + returning IntCol, StringCol into :int_col, :string_col + """, + int_col=int_col, + string_col=string_col, + ) + print("Data returned:") + for int_val, string_val in zip( + int_col.getvalue(), string_col.getvalue() + ): + print(tuple([int_val, string_val])) + + +asyncio.run(main()) diff --git a/python/python-oracledb/drcp_pool.py b/python/python-oracledb/drcp_pool.py index f1c9962a..3ff8aef8 100644 --- a/python/python-oracledb/drcp_pool.py +++ b/python/python-oracledb/drcp_pool.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2022, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # drcp_pool.py # # Demonstrates the use of Database Resident Connection Pooling (DRCP) @@ -93,18 +93,18 @@ # Then you can query the data dictionary: # # select cclass_name, num_requests, num_hits, -# num_misses, num_waits, num_authentications +# num_misses, num_waits, num_authentications as num_auths # from v$cpool_cc_stats; # # Output will be like: # -# CCLASS_NAME NUM_REQUESTS NUM_HITS NUM_MISSES NUM_WAITS NUM_AUTHENTICATIONS -# ---------------- ------------ -------- ---------- --------- ------------------- -# PYTHONDEMO.MYAPP 1001 997 4 0 4 +# CCLASS_NAME NUM_REQUESTS NUM_HITS NUM_MISSES NUM_WAITS NUM_AUTHS +# ---------------- ------------ -------- ---------- --------- --------- +# PYTHONDEMO.MYAPP 1001 997 4 0 4 # # With ADB-S databases, query V$CPOOL_CONN_INFO instead. # -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import os import sys @@ -115,17 +115,17 @@ import sample_env # Port to listen on -port = int(os.environ.get('PORT', '8080')) +port = int(os.environ.get("PORT", "8080")) # determine whether to use python-oracledb thin mode or thick mode if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + # start_pool(): starts the connection pool def start_pool(): - # Generally a fixed-size pool is recommended, i.e. pool_min=pool_max. Here # the pool contains 4 connections, which will allow 4 concurrent users. @@ -133,16 +133,21 @@ def start_pool(): pool_max = 4 pool_inc = 0 - pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_drcp_connect_string(), - min=pool_min, max=pool_max, increment=pool_inc, - session_callback=init_session, - cclass="MYAPP", - purity=oracledb.ATTR_PURITY_SELF) + pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_drcp_connect_string(), + min=pool_min, + max=pool_max, + increment=pool_inc, + session_callback=init_session, + cclass="MYAPP", + purity=oracledb.ATTR_PURITY_SELF, + ) return pool + # init_session(): a 'session callback' to efficiently set any initial state # that each connection should have. # @@ -155,63 +160,81 @@ def start_pool(): # def init_session(connection, requestedTag_ignored): with connection.cursor() as cursor: - cursor.execute(""" + cursor.execute( + """ alter session set - time_zone = 'UTC' - nls_date_format = 'YYYY-MM-DD HH24:MI'""") + time_zone = 'UTC' + nls_date_format = 'YYYY-MM-DD HH24:MI' + """ + ) + + +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ # create_schema(): drop and create the demo table, and add a row def create_schema(): with pool.acquire() as connection: with connection.cursor() as cursor: - cursor.execute(""" + cursor.execute( + """ begin - begin - execute immediate 'drop table demo'; + begin + execute immediate 'drop table demo'; exception when others then - if sqlcode <> -942 then - raise; - end if; - end; + if sqlcode <> -942 then + raise; + end if; + end; - execute immediate 'create table demo ( - id number generated by default as identity, - username varchar2(40))'; + execute immediate 'create table demo ( + id number generated by default as identity, + username varchar2(40) + )'; - execute immediate 'insert into demo (username) values (''chris'')'; + execute immediate 'insert into demo (username) + values (''chris'')'; - commit; - end;""") + commit; + end; + """ + ) -#------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- app = Flask(__name__) + # Display a welcome message on the 'home' page -@app.route('/') +@app.route("/") def index(): return "Welcome to the demo app" + # Add a new username # # The new user's id is generated by the database and returned in the OUT bind # variable 'idbv'. -@app.route('/post/') +@app.route("/post/") def post(username): with pool.acquire() as connection: with connection.cursor() as cursor: connection.autocommit = True idbv = cursor.var(int) - cursor.execute(""" + cursor.execute( + """ insert into demo (username) values (:unbv) - returning id into :idbv""", [username, idbv]) - return f'Inserted {username} with id {idbv.getvalue()[0]}' + returning id into :idbv + """, + [username, idbv], + ) + return f"Inserted {username} with id {idbv.getvalue()[0]}" + # Show the username for a given id -@app.route('/user/') +@app.route("/user/") def show_username(id): with pool.acquire() as connection: with connection.cursor() as cursor: @@ -219,10 +242,10 @@ def show_username(id): r = cursor.fetchone() return r[0] if r is not None else "Unknown user id" -#------------------------------------------------------------------------------ -if __name__ == '__main__': +# ----------------------------------------------------------------------------- +if __name__ == "__main__": # Start a pool of connections pool = start_pool() @@ -230,7 +253,7 @@ def show_username(id): create_schema() m = f"\nTry loading http://127.0.0.1:{port}/user/1 in a browser\n" - sys.modules['flask.cli'].show_server_banner = lambda *x: print(m) + sys.modules["flask.cli"].show_server_banner = lambda *x: print(m) # Start a webserver app.run(port=port) diff --git a/python/python-oracledb/drop_schema.py b/python/python-oracledb/drop_schema.py index d0a163da..5d0443c3 100644 --- a/python/python-oracledb/drop_schema.py +++ b/python/python-oracledb/drop_schema.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # drop_schema.py # # Drops the database objects used by the python-oracledb samples. @@ -30,19 +30,23 @@ # This script is also executed by the Python script sample_setup.py for # dropping the existing users and editions, if applicable, before creating the # sample schemas and editions. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -import oracledb import sample_env + def drop_schema(conn): print("Dropping sample schemas and edition...") - sample_env.run_sql_script(conn, "drop_schema", - main_user=sample_env.get_main_user(), - edition_user=sample_env.get_edition_user(), - edition_name=sample_env.get_edition_name()) + sample_env.run_sql_script( + conn, + "drop_schema", + main_user=sample_env.get_main_user(), + edition_user=sample_env.get_edition_user(), + edition_name=sample_env.get_edition_name(), + ) + if __name__ == "__main__": - conn = oracledb.connect(sample_env.get_admin_connect_string()) + conn = sample_env.get_admin_connection() drop_schema(conn) print("Done.") diff --git a/python/python-oracledb/editioning.py b/python/python-oracledb/editioning.py index 428ac4e2..3ba403f8 100644 --- a/python/python-oracledb/editioning.py +++ b/python/python-oracledb/editioning.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,16 +25,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # editioning.py # # Demonstrates the use of Edition-Based Redefinition, a feature that is # available in Oracle Database 11.2 and higher. See the Oracle documentation on # the subject for additional information. Adjust the contents at the top of the # script for your own database as needed. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import os @@ -50,49 +50,72 @@ connection = oracledb.connect(edition_connect_string) print("Edition should be None, actual value is:", repr(connection.edition)) cursor = connection.cursor() -cursor.execute(""" - create or replace function TestEditions return varchar2 as - begin - return 'Base Procedure'; - end;""") +cursor.execute( + """ + create or replace function TestEditions return varchar2 as + begin + return 'Base Procedure'; + end; + """ +) result = cursor.callfunc("TestEditions", str) -print("Function should return 'Base Procedure', actually returns:", - repr(result)) +print( + "Function should return 'Base Procedure', actually returns:", repr(result) +) # next, change the edition and recreate the procedure in the new edition cursor.execute("alter session set edition = %s" % edition_name) -print("Edition should be", repr(edition_name.upper()), - "actual value is:", repr(connection.edition)) -cursor.execute(""" - create or replace function TestEditions return varchar2 as - begin - return 'Edition 1 Procedure'; - end;""") +print( + "Edition should be", + repr(edition_name.upper()), + "actual value is:", + repr(connection.edition), +) +cursor.execute( + """ + create or replace function TestEditions return varchar2 as + begin + return 'Edition 1 Procedure'; + end; + """ +) result = cursor.callfunc("TestEditions", str) -print("Function should return 'Edition 1 Procedure', actually returns:", - repr(result)) +print( + "Function should return 'Edition 1 Procedure', actually returns:", + repr(result), +) # next, change the edition back to the base edition and demonstrate that the # original function is being called cursor.execute("alter session set edition = ORA$BASE") result = cursor.callfunc("TestEditions", str) -print("Function should return 'Base Procedure', actually returns:", - repr(result)) +print( + "Function should return 'Base Procedure', actually returns:", repr(result) +) # the edition can be set upon connection -connection = oracledb.connect(edition_connect_string, - edition=edition_name.upper()) +connection = oracledb.connect( + edition_connect_string, edition=edition_name.upper() +) cursor = connection.cursor() result = cursor.callfunc("TestEditions", str) -print("Function should return 'Edition 1 Procedure', actually returns:", - repr(result)) +print( + "Function should return 'Edition 1 Procedure', actually returns:", + repr(result), +) # it can also be set via the environment variable ORA_EDITION os.environ["ORA_EDITION"] = edition_name.upper() connection = oracledb.connect(edition_connect_string) -print("Edition should be", repr(edition_name.upper()), - "actual value is:", repr(connection.edition)) +print( + "Edition should be", + repr(edition_name.upper()), + "actual value is:", + repr(connection.edition), +) cursor = connection.cursor() result = cursor.callfunc("TestEditions", str) -print("Function should return 'Edition 1 Procedure', actually returns:", - repr(result)) +print( + "Function should return 'Edition 1 Procedure', actually returns:", + repr(result), +) diff --git a/python/python-oracledb/generic_row_factory.py b/python/python-oracledb/generic_row_factory.py index 1aac0812..e1a4bbfd 100644 --- a/python/python-oracledb/generic_row_factory.py +++ b/python/python-oracledb/generic_row_factory.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,14 +20,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # generic_row_factory.py # # Demonstrates the ability to return named tuples for all queries using a # subclassed cursor and row factory. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import collections @@ -38,16 +38,15 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -class Connection(oracledb.Connection): +class Connection(oracledb.Connection): def cursor(self): return Cursor(self) class Cursor(oracledb.Cursor): - def execute(self, statement, args=None): - prepare_needed = (self.statement != statement) + prepare_needed = self.statement != statement result = super().execute(statement, args or []) if prepare_needed: description = self.description @@ -58,12 +57,13 @@ def execute(self, statement, args=None): # create a new subclassed connection and cursor -connection = Connection(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = Connection( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # the names are now available directly for each query executed for row in cursor.execute("select ParentId, Description from ParentTable"): print(row.PARENTID, "->", row.DESCRIPTION) diff --git a/python/python-oracledb/generic_row_factory_async.py b/python/python-oracledb/generic_row_factory_async.py new file mode 100644 index 00000000..4b4ed55a --- /dev/null +++ b/python/python-oracledb/generic_row_factory_async.py @@ -0,0 +1,80 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# generic_row_factory_async.py +# +# An asynchronous version of generic_row_factory.py +# +# Demonstrates the ability to return named tuples for all queries using a +# subclassed cursor and row factory. +# ----------------------------------------------------------------------------- + +import asyncio +import collections + +import oracledb +import sample_env + + +class Connection(oracledb.AsyncConnection): + def cursor(self): + return Cursor(self) + + +class Cursor(oracledb.AsyncCursor): + async def execute(self, statement, args=None): + prepare_needed = self.statement != statement + result = await super().execute(statement, args or []) + if prepare_needed: + description = self.description + if description is not None: + names = [d.name for d in description] + self.rowfactory = collections.namedtuple("GenericQuery", names) + return result + + +async def main(): + # create a new subclassed connection and cursor + connection = await oracledb.connect_async( + conn_class=Connection, + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # the names are now available directly for each query executed + await cursor.execute("select ParentId, Description from ParentTable") + async for row in cursor: + print(row.PARENTID, "->", row.DESCRIPTION) + print() + + await cursor.execute("select ChildId, Description from ChildTable") + async for row in cursor: + print(row.CHILDID, "->", row.DESCRIPTION) + print() + + +asyncio.run(main()) diff --git a/python/python-oracledb/implicit_results.py b/python/python-oracledb/implicit_results.py index 2d9a99c1..c4ffe332 100644 --- a/python/python-oracledb/implicit_results.py +++ b/python/python-oracledb/implicit_results.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,15 +25,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # implicit_results.py # # Demonstrates the use of the Oracle Database 12.1 feature that allows PL/SQL # procedures to return result sets implicitly, without having to explicitly # define them. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -42,30 +42,34 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # A PL/SQL block that returns two cursors - cursor.execute(""" - declare - c1 sys_refcursor; - c2 sys_refcursor; - begin + cursor.execute( + """ + declare + c1 sys_refcursor; + c2 sys_refcursor; + begin - open c1 for - select * from TestNumbers; + open c1 for + select * from TestNumbers; - dbms_sql.return_result(c1); + dbms_sql.return_result(c1); - open c2 for - select * from TestStrings; + open c2 for + select * from TestStrings; - dbms_sql.return_result(c2); + dbms_sql.return_result(c2); - end;""") + end; + """ + ) # display results for ix, result_set in enumerate(cursor.getimplicitresults()): diff --git a/python/python-oracledb/implicit_results_async.py b/python/python-oracledb/implicit_results_async.py new file mode 100644 index 00000000..c2afc323 --- /dev/null +++ b/python/python-oracledb/implicit_results_async.py @@ -0,0 +1,79 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# implicit_results_async.py +# +# An asynchronous version of implicit_results.py +# +# Demonstrates the use of the Oracle Database 12.1 feature that allows PL/SQL +# procedures to return result sets implicitly, without having to explicitly +# define them. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # A PL/SQL block that returns two cursors + await cursor.execute( + """ + declare + c1 sys_refcursor; + c2 sys_refcursor; + begin + + open c1 for + select * from TestNumbers; + + dbms_sql.return_result(c1); + + open c2 for + select * from TestStrings; + + dbms_sql.return_result(c2); + + end; + """ + ) + + # display results + for ix, result_set in enumerate(cursor.getimplicitresults()): + print("Result Set #" + str(ix + 1)) + async for row in result_set: + print(row) + print() + + +asyncio.run(main()) diff --git a/python/python-oracledb/insert_geometry.py b/python/python-oracledb/insert_geometry.py index b54fc54c..60938fa2 100644 --- a/python/python-oracledb/insert_geometry.py +++ b/python/python-oracledb/insert_geometry.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # insert_geometry.py # # Demonstrates the ability to create Oracle objects (this example uses # SDO_GEOMETRY) and insert them into a table. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -41,9 +41,11 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # create and populate Oracle objects type_obj = connection.gettype("MDSYS.SDO_GEOMETRY") diff --git a/python/python-oracledb/insert_geometry_async.py b/python/python-oracledb/insert_geometry_async.py new file mode 100644 index 00000000..a9058fde --- /dev/null +++ b/python/python-oracledb/insert_geometry_async.py @@ -0,0 +1,71 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# insert_geometry_async.py +# +# An asynchronous version of insert_geometry.py +# +# Demonstrates the ability to create Oracle objects (this example uses +# SDO_GEOMETRY) and insert them into a table. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # create and populate Oracle objects + type_obj = await connection.gettype("MDSYS.SDO_GEOMETRY") + element_info_type_obj = await connection.gettype( + "MDSYS.SDO_ELEM_INFO_ARRAY" + ) + ordinate_type_obj = await connection.gettype("MDSYS.SDO_ORDINATE_ARRAY") + obj = type_obj.newobject() + obj.SDO_GTYPE = 2003 + obj.SDO_ELEM_INFO = element_info_type_obj.newobject() + obj.SDO_ELEM_INFO.extend([1, 1003, 3]) + obj.SDO_ORDINATES = ordinate_type_obj.newobject() + obj.SDO_ORDINATES.extend([1, 1, 5, 7]) + print("Created object", obj) + + with connection.cursor() as cursor: + await cursor.execute("truncate table TestGeometry") + print("Adding row to table...") + await cursor.execute( + "insert into TestGeometry values (1, :objbv)", objbv=obj + ) + await connection.commit() + print("Success!") + + +asyncio.run(main()) diff --git a/python/python-oracledb/json_blob.py b/python/python-oracledb/json_blob.py index 0e934e29..90757fa7 100644 --- a/python/python-oracledb/json_blob.py +++ b/python/python-oracledb/json_blob.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # json_blob.py # # Demonstrates how to use a BLOB as a JSON column store. @@ -34,7 +34,7 @@ # Documentation: # python-oracledb: https://oracledb.readthedocs.io/en/latest/user_guide/json_data_type.html # Oracle Database: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=ADJSN -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import json import sys @@ -46,13 +46,11 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -# use the feature that transforms JSON data in VARCHAR2 and LOB columns to -# objects -oracledb.__future__.old_json_col_as_obj = True - -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) if not connection.thin: client_version = oracledb.clientversion()[0] @@ -64,7 +62,6 @@ # Insert JSON data with connection.cursor() as cursor: - data = dict(name="Rod", dept="Sales", location="Germany") inssql = "insert into CustomersAsBlob values (:1, :2)" @@ -78,9 +75,8 @@ # Select JSON data with connection.cursor() as cursor: - sql = "select c.json_data from CustomersAsBlob c" - for j, in cursor.execute(sql): + for (j,) in cursor.execute(sql): print(j) # Using JSON_VALUE to extract a value from a JSON column @@ -96,7 +92,7 @@ sql = """select c.json_data.location from CustomersAsBlob c offset 0 rows fetch next 1 rows only""" - for j, in cursor.execute(sql): + for (j,) in cursor.execute(sql): print(j) # Using JSON_OBJECT to extract relational data as JSON diff --git a/python/python-oracledb/json_blob_async.py b/python/python-oracledb/json_blob_async.py new file mode 100644 index 00000000..7f7e18b8 --- /dev/null +++ b/python/python-oracledb/json_blob_async.py @@ -0,0 +1,121 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# json_blob_async.py +# +# An asynchronous version of json_blob.py +# +# Demonstrates how to use a BLOB as a JSON column store. +# +# Note: Oracle Database 12c lets JSON be stored in VARCHAR2 or LOB columns. +# With Oracle Database 21c using the new JSON type is recommended +# instead, see json_direct_async.py +# +# Documentation: +# python-oracledb: https://oracledb.readthedocs.io/en/latest/user_guide/json_data_type.html +# Oracle Database: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=ADJSN +# ----------------------------------------------------------------------------- + +import asyncio +import json +import sys + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # Minimum database vesion is 12 + db_version = int(connection.version.split(".")[0]) + if db_version < 12: + sys.exit("This example requires Oracle Database 12.1.0.2 or later") + + # Insert JSON data + with connection.cursor() as cursor: + data = dict(name="Rod", dept="Sales", location="Germany") + inssql = "insert into CustomersAsBlob values (:1, :2)" + + if db_version >= 21: + # Take advantage of direct binding + cursor.setinputsizes(None, oracledb.DB_TYPE_JSON) + await cursor.execute(inssql, [1, data]) + else: + # Insert the data as a JSON string + await cursor.execute(inssql, [1, json.dumps(data)]) + + # Select JSON data + with connection.cursor() as cursor: + sql = "select c.json_data from CustomersAsBlob c" + await cursor.execute(sql) + async for (j,) in cursor: + print(j) + + # Using JSON_VALUE to extract a value from a JSON column + + sql = """select json_value(json_data, '$.location') + from CustomersAsBlob + offset 0 rows fetch next 1 rows only""" + await cursor.execute(sql) + async for (r,) in cursor: + print(r) + + # Using dot-notation to extract a value from a JSON (BLOB storage) + # column + + sql = """select c.json_data.location + from CustomersAsBlob c + offset 0 rows fetch next 1 rows only""" + await cursor.execute(sql) + async for (j,) in cursor: + print(j) + + # Using JSON_OBJECT to extract relational data as JSON + + sql = """select json_object('key' is d.dummy) dummy + from dual d""" + await cursor.execute(sql) + async for r in cursor: + print(r) + + # Using JSON_ARRAYAGG to extract a whole relational table as JSON + + oracledb.defaults.fetch_lobs = False + sql = """select json_arrayagg( + json_object('key' is c.id, + 'name' is c.json_data) + returning clob) + from CustomersAsBlob c""" + await cursor.execute(sql) + async for r in cursor: + print(r) + + +asyncio.run(main()) diff --git a/python/python-oracledb/json_direct.py b/python/python-oracledb/json_direct.py index 431f98a5..fcccdf83 100644 --- a/python/python-oracledb/json_direct.py +++ b/python/python-oracledb/json_direct.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # json_direct.py # # Demonstrates the use of some JSON features with the JSON type that is @@ -31,7 +31,7 @@ # See https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=ADJSN # # For JSON with older databases see json_blob.py -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import json import sys @@ -43,9 +43,11 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) if not connection.thin: client_version = oracledb.clientversion()[0] @@ -53,12 +55,13 @@ # this script only works with Oracle Database 21 if db_version < 21: - sys.exit("This example requires Oracle Database 21.1 or later. " - "Try json_blob.py instead") + sys.exit( + "This example requires Oracle Database 21.1 or later. " + "Try json_blob.py instead" + ) # Insert JSON data with connection.cursor() as cursor: - data = dict(name="Rod", dept="Sales", location="Germany") inssql = "insert into CustomersAsJson values (:1, :2)" if connection.thin or client_version >= 21: @@ -71,13 +74,12 @@ # Select JSON data with connection.cursor() as cursor: - sql = "select c.json_data from CustomersAsJson c" if connection.thin or client_version >= 21: - for j, in cursor.execute(sql): + for (j,) in cursor.execute(sql): print(j) else: - for j, in cursor.execute(sql): + for (j,) in cursor.execute(sql): print(json.loads(j.read())) # Using JSON_VALUE to extract a value from a JSON column @@ -94,10 +96,10 @@ from CustomersAsJson c offset 0 rows fetch next 1 rows only""" if connection.thin or client_version >= 21: - for j, in cursor.execute(sql): + for (j,) in cursor.execute(sql): print(j) else: - for j, in cursor.execute(sql): + for (j,) in cursor.execute(sql): print(json.loads(j.read())) # Using JSON_OBJECT to extract relational data as JSON diff --git a/python/python-oracledb/json_direct_async.py b/python/python-oracledb/json_direct_async.py new file mode 100644 index 00000000..a2c19628 --- /dev/null +++ b/python/python-oracledb/json_direct_async.py @@ -0,0 +1,113 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# json_direct_async.py +# +# An asynchronous version of json_direct.py +# +# Demonstrates the use of some JSON features with the JSON type that is +# available in Oracle Database 21c and higher. +# +# See https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=ADJSN +# +# For JSON with older databases see json_blob_async.py +# ----------------------------------------------------------------------------- + +import asyncio +import sys + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # this script only works with Oracle Database 21 + db_version = int(connection.version.split(".")[0]) + if db_version < 21: + sys.exit( + "This example requires Oracle Database 21.1 or later. " + "Try json_blob.py instead" + ) + + # Insert JSON data + with connection.cursor() as cursor: + data = dict(name="Rod", dept="Sales", location="Germany") + inssql = "insert into CustomersAsJson values (:1, :2)" + cursor.setinputsizes(None, oracledb.DB_TYPE_JSON) + await cursor.execute(inssql, [1, data]) + + # Select JSON data + with connection.cursor() as cursor: + sql = "select c.json_data from CustomersAsJson c" + await cursor.execute(sql) + async for (j,) in cursor: + print(j) + + # Using JSON_VALUE to extract a value from a JSON column + + sql = """select json_value(json_data, '$.location') + from CustomersAsJson + offset 0 rows fetch next 1 rows only""" + await cursor.execute(sql) + async for r in cursor: + print(r) + + # Using dot-notation to extract a value from a JSON column + + sql = """select c.json_data.location + from CustomersAsJson c + offset 0 rows fetch next 1 rows only""" + await cursor.execute(sql) + async for (j,) in cursor: + print(j) + + # Using JSON_OBJECT to extract relational data as JSON + + sql = """select json_object('key' is d.dummy) dummy + from dual d""" + await cursor.execute(sql) + async for r in cursor: + print(r) + + # Using JSON_ARRAYAGG to extract a whole relational table as JSON + + oracledb.defaults.fetch_lobs = False + sql = """select json_arrayagg( + json_object('key' is c.id, + 'name' is c.json_data) + returning clob) + from CustomersAsJson c""" + await cursor.execute(sql) + async for r in cursor: + print(r) + + +asyncio.run(main()) diff --git a/python/python-oracledb/last_rowid.py b/python/python-oracledb/last_rowid.py index cabd3d87..323bc2c0 100644 --- a/python/python-oracledb/last_rowid.py +++ b/python/python-oracledb/last_rowid.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2019, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,13 +20,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # last_rowid.py # # Demonstrates the use of the cursor.lastrowid attribute. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -35,12 +35,13 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # insert a couple of rows and retain the rowid of each row1 = [1, "First"] row2 = [2, "Second"] @@ -66,8 +67,9 @@ # updating multiple rows only returns the rowid of the last updated row cursor.execute("update mytab set data = data || ' (Modified)'") - cursor.execute("select id, data from mytab where rowid = :1", - [cursor.lastrowid]) + cursor.execute( + "select id, data from mytab where rowid = :1", [cursor.lastrowid] + ) print("Last updated row:", cursor.fetchone()) # deleting multiple rows only returns the rowid of the last deleted row @@ -79,4 +81,4 @@ print("Rowid when no rows are deleted:", cursor.lastrowid) # Don't commit - this lets us run the demo multiple times - #connection.commit() + # connection.commit() diff --git a/python/python-oracledb/last_rowid_async.py b/python/python-oracledb/last_rowid_async.py new file mode 100644 index 00000000..3f4a2a68 --- /dev/null +++ b/python/python-oracledb/last_rowid_async.py @@ -0,0 +1,97 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# last_rowid_async.py +# +# An asynchronous version of last_rowid.py +# +# Demonstrates the use of the cursor.lastrowid attribute. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # insert a couple of rows and retain the rowid of each + row1 = [1, "First"] + row2 = [2, "Second"] + + await cursor.execute( + "insert into mytab (id, data) values (:1, :2)", row1 + ) + rowid1 = cursor.lastrowid + print("Row 1:", row1) + print("Rowid 1:", rowid1) + print() + + await cursor.execute( + "insert into mytab (id, data) values (:1, :2)", row2 + ) + rowid2 = cursor.lastrowid + print("Row 2:", row2) + print("Rowid 2:", rowid2) + print() + + # the row can be fetched with the rowid that was returned + await cursor.execute( + "select id, data from mytab where rowid = :1", [rowid1] + ) + print("Row 1:", await cursor.fetchone()) + await cursor.execute( + "select id, data from mytab where rowid = :1", [rowid2] + ) + print("Row 2:", await cursor.fetchone()) + print() + + # updating multiple rows only returns the rowid of the last updated row + await cursor.execute("update mytab set data = data || ' (Modified)'") + await cursor.execute( + "select id, data from mytab where rowid = :1", [cursor.lastrowid] + ) + print("Last updated row:", await cursor.fetchone()) + + # deleting multiple rows only returns the rowid of the last deleted row + await cursor.execute("delete from mytab") + print("Rowid of last deleted row:", cursor.lastrowid) + + # deleting no rows results in a value of None + await cursor.execute("delete from mytab") + print("Rowid when no rows are deleted:", cursor.lastrowid) + + # Don't commit - this lets us run the demo multiple times + # await connection.commit() + + +asyncio.run(main()) diff --git a/python/python-oracledb/load_csv.py b/python/python-oracledb/load_csv.py index 8ed827b3..045c1c88 100644 --- a/python/python-oracledb/load_csv.py +++ b/python/python-oracledb/load_csv.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2022, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,13 +20,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # load_csv.py # # A sample showing how to load CSV data. -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import csv import os @@ -40,7 +40,7 @@ # CSV file. This sample file has both valid rows and some rows with data too # large to insert. -FILE_NAME = os.path.join('data', 'load_csv.csv') +FILE_NAME = os.path.join("data", "load_csv.csv") # Adjust the number of rows to be inserted in each iteration to meet your # memory and performance requirements. Typically this is a large-ish value to @@ -49,9 +49,12 @@ # behavior of the code. BATCH_SIZE = 19 -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) + def process_batch(batch_number, cursor, data): print("processing batch", batch_number + 1) @@ -60,10 +63,10 @@ def process_batch(batch_number, cursor, data): line_num = (batch_number * BATCH_SIZE) + error.offset + 1 print("Error", error.message, "at line", line_num) -with connection.cursor() as cursor: +with connection.cursor() as cursor: # Clean up the table for demonstration purposes - cursor.execute('truncate table LoadCsvTab'); + cursor.execute("truncate table LoadCsvTab") # Predefine the memory areas to match the table definition. # This can improve performance by avoiding memory reallocations. @@ -74,8 +77,8 @@ def process_batch(batch_number, cursor, data): cursor.setinputsizes(None, 25) # Loop over the data and insert it in batches - with open(FILE_NAME, 'r') as csv_file: - csv_reader = csv.reader(csv_file, delimiter=',') + with open(FILE_NAME, "r") as csv_file: + csv_reader = csv.reader(csv_file, delimiter=",") sql = "insert into LoadCsvTab (id, name) values (:1, :2)" data = [] batch_number = 0 diff --git a/python/python-oracledb/load_csv_async.py b/python/python-oracledb/load_csv_async.py new file mode 100644 index 00000000..e862e733 --- /dev/null +++ b/python/python-oracledb/load_csv_async.py @@ -0,0 +1,100 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# load_csv_async.py +# +# An asynchronous version of load_csv.py +# +# A sample showing how to load CSV data. +# ----------------------------------------------------------------------------- + +import asyncio +import csv +import os + +import oracledb +import sample_env + +# CSV file. This sample file has both valid rows and some rows with data too +# large to insert. +FILE_NAME = os.path.join("data", "load_csv.csv") + +# Adjust the number of rows to be inserted in each iteration to meet your +# memory and performance requirements. Typically this is a large-ish value to +# reduce the number of calls to executemany() to a reasonable size. For this +# demo with a small CSV file a smaller number is used to show the looping +# behavior of the code. +BATCH_SIZE = 19 + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + async def process_batch(batch_number, cursor, data): + print("processing batch", batch_number + 1) + await cursor.executemany(sql, data, batcherrors=True) + for error in cursor.getbatcherrors(): + line_num = (batch_number * BATCH_SIZE) + error.offset + 1 + print("Error", error.message, "at line", line_num) + + with connection.cursor() as cursor: + # Clean up the table for demonstration purposes + await cursor.execute("truncate table LoadCsvTab") + + # Predefine the memory areas to match the table definition. + # This can improve performance by avoiding memory reallocations. + # Here, one parameter is passed for each of the columns. + # "None" is used for the ID column, since the size of NUMBER isn't + # variable. The "25" matches the maximum expected data size for the + # NAME column + cursor.setinputsizes(None, 25) + + # Loop over the data and insert it in batches + with open(FILE_NAME, "r") as csv_file: + csv_reader = csv.reader(csv_file, delimiter=",") + sql = "insert into LoadCsvTab (id, name) values (:1, :2)" + data = [] + batch_number = 0 + for line in csv_reader: + data.append((line[0], line[1])) + if len(data) % BATCH_SIZE == 0: + await process_batch(batch_number, cursor, data) + data = [] + batch_number += 1 + if data: + await process_batch(batch_number, cursor, data) + + # In a production system you might choose to fix any invalid rows, + # re-insert them, and then commit. Or you could rollback + # everything. In this sample we simply commit and ignore the + # invalid rows that couldn't be inserted. + await connection.commit() + + +asyncio.run(main()) diff --git a/python/python-oracledb/multi_consumer_aq.py b/python/python-oracledb/multi_consumer_aq.py index 582cf5fe..c0744d0d 100644 --- a/python/python-oracledb/multi_consumer_aq.py +++ b/python/python-oracledb/multi_consumer_aq.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # multi_consumer_aq.py # # Demonstrates how to use multi-consumer advanced queuing. It makes use of a # RAW queue created in the sample setup. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -45,13 +45,15 @@ "The first message", "The second message", "The third message", - "The fourth and final message" + "The fourth and final message", ] # connect to database -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # create a queue queue = connection.queue(QUEUE_NAME) diff --git a/python/python-oracledb/object_aq.py b/python/python-oracledb/object_aq.py index d952e677..eaf0803c 100644 --- a/python/python-oracledb/object_aq.py +++ b/python/python-oracledb/object_aq.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # object_aq.py # # Demonstrates how to use advanced queuing with objects. It makes use of a # simple type and queue created in the sample setup. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import decimal @@ -45,16 +45,24 @@ BOOK_TYPE_NAME = "UDT_BOOK" QUEUE_NAME = "DEMO_BOOK_QUEUE" BOOK_DATA = [ - ("The Fellowship of the Ring", "Tolkien, J.R.R.", - decimal.Decimal("10.99")), - ("Harry Potter and the Philosopher's Stone", "Rowling, J.K.", - decimal.Decimal("7.99")) + ( + "The Fellowship of the Ring", + "Tolkien, J.R.R.", + decimal.Decimal("10.99"), + ), + ( + "Harry Potter and the Philosopher's Stone", + "Rowling, J.K.", + decimal.Decimal("7.99"), + ), ] # connect to database -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # create a queue books_type = connection.gettype(BOOK_TYPE_NAME) diff --git a/python/python-oracledb/object_dump.py b/python/python-oracledb/object_dump.py index d58fe290..741d9a10 100644 --- a/python/python-oracledb/object_dump.py +++ b/python/python-oracledb/object_dump.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,14 +20,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # object_dump.py # # Shows how to pretty-print an Oracle object or collection. # Also shows how to insert a Python object to an Oracle object column. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -37,14 +37,16 @@ oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) # Create Oracle connection and cursor objects -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) cursor = connection.cursor() + # Create a Python class equivalent to an Oracle SDO object class MySDO(object): - def __init__(self, gtype, elem_info, ordinates): self.gtype = gtype self.elem_info = elem_info @@ -56,6 +58,7 @@ def __init__(self, gtype, elem_info, ordinates): element_info_type_obj = connection.gettype("MDSYS.SDO_ELEM_INFO_ARRAY") ordinate_type_obj = connection.gettype("MDSYS.SDO_ORDINATE_ARRAY") + # Convert a Python object to MDSYS.SDO_GEOMETRY def sdo_input_type_handler(cursor, value, num_elements): def sdo_in_converter(value): @@ -68,8 +71,9 @@ def sdo_in_converter(value): return obj if isinstance(value, MySDO): - return cursor.var(obj_type, arraysize=num_elements, - inconverter=sdo_in_converter) + return cursor.var( + obj_type, arraysize=num_elements, inconverter=sdo_in_converter + ) # Create and insert a Python object @@ -100,6 +104,7 @@ def dump_object(obj, prefix=""): print(f"{prefix} {attr.name}: {repr(value)}") print(f"{prefix}}}") + # Query the row back cursor.execute("select geometry from TestGeometry") for (obj,) in cursor: diff --git a/python/python-oracledb/object_dump_async.py b/python/python-oracledb/object_dump_async.py new file mode 100644 index 00000000..50e9a028 --- /dev/null +++ b/python/python-oracledb/object_dump_async.py @@ -0,0 +1,112 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# object_dump_async.py +# +# An asynchronous version of object_dump.py +# +# Shows how to pretty-print an Oracle object or collection. +# Also shows how to insert a Python object to an Oracle object column. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + # Create Oracle connection and cursor objects + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + cursor = connection.cursor() + + # Create a Python class equivalent to an Oracle SDO object + class MySDO(object): + def __init__(self, gtype, elem_info, ordinates): + self.gtype = gtype + self.elem_info = elem_info + self.ordinates = ordinates + + # Get Oracle type information + obj_type = await connection.gettype("MDSYS.SDO_GEOMETRY") + element_info_type_obj = await connection.gettype( + "MDSYS.SDO_ELEM_INFO_ARRAY" + ) + ordinate_type_obj = await connection.gettype("MDSYS.SDO_ORDINATE_ARRAY") + + # Convert a Python object to MDSYS.SDO_GEOMETRY + def sdo_input_type_handler(cursor, value, num_elements): + def sdo_in_converter(value): + obj = obj_type.newobject() + obj.SDO_GTYPE = value.gtype + obj.SDO_ELEM_INFO = element_info_type_obj.newobject() + obj.SDO_ELEM_INFO.extend(value.elem_info) + obj.SDO_ORDINATES = ordinate_type_obj.newobject() + obj.SDO_ORDINATES.extend(value.ordinates) + return obj + + if isinstance(value, MySDO): + return cursor.var( + obj_type, arraysize=num_elements, inconverter=sdo_in_converter + ) + + # Create and insert a Python object + sdo = MySDO(2003, [1, 1003, 3], [1, 1, 5, 7]) + cursor.inputtypehandler = sdo_input_type_handler + await cursor.execute("truncate table TestGeometry") + await cursor.execute("insert into TestGeometry values (1, :1)", [sdo]) + + # Define a function to pretty-print the contents of an Oracle object + def dump_object(obj, prefix=""): + if obj.type.iscollection: + print(f"{prefix}[") + for value in obj.aslist(): + if isinstance(value, oracledb.DbObject): + dump_object(value, prefix + " ") + else: + print(f"{prefix} {repr(value)}") + print(f"{prefix}]") + else: + print(f"{prefix}{{") + for attr in obj.type.attributes: + value = getattr(obj, attr.name) + if isinstance(value, oracledb.DbObject): + print(f"{prefix} {attr.name}:") + dump_object(value, prefix + " ") + else: + print(f"{prefix} {attr.name}: {repr(value)}") + print(f"{prefix}}}") + + # Query the row back + await cursor.execute("select geometry from TestGeometry") + async for (obj,) in cursor: + dump_object(obj) + + +asyncio.run(main()) diff --git a/python/python-oracledb/oracledb_upgrade.py b/python/python-oracledb/oracledb_upgrade.py index 81b3c4e3..2b1ee64d 100644 --- a/python/python-oracledb/oracledb_upgrade.py +++ b/python/python-oracledb/oracledb_upgrade.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2022, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # oracledb_upgrade.py # # Example module to assist upgrading large applications from cx_Oracle 8 to @@ -111,7 +111,7 @@ # for r, in cursor.execute(sql): # print(r) # -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import os import sys @@ -134,7 +134,7 @@ # Python. lib_dir = None if platform.system() == "Darwin" and platform.machine() == "x86_64": - lib_dir = os.environ.get("HOME")+"/Downloads/instantclient_19_8" + lib_dir = os.environ.get("HOME") + "/Downloads/instantclient_19_8" elif platform.system() == "Windows": lib_dir = r"C:\oracle\instantclient_19_14" @@ -143,23 +143,29 @@ driver_type = os.environ.get("ORA_PYTHON_DRIVER_TYPE", "thin") if driver_type.lower() == "cx": - if MODE_TRACE: print("Using cx_Oracle") - from cx_Oracle import * + if MODE_TRACE: + print("Using cx_Oracle") + from cx_Oracle import * # noqa: F403 + sys.modules["oracledb"] = cx_Oracle sys.modules["cx_Oracle"] = cx_Oracle oracledb.init_oracle_client(lib_dir=lib_dir) else: - from oracledb import * + from oracledb import * # noqa: F403 + sys.modules["oracledb"] = oracledb sys.modules["cx_Oracle"] = oracledb if driver_type.lower() == "thick": - if MODE_TRACE: print("Using python-oracledb thick") + if MODE_TRACE: + print("Using python-oracledb thick") # For python-oracledb Thick mode, init_oracle_client() MUST be called # on all operating systems. Whether to use a lib_dir value depends on # how your system library search path is configured. oracledb.init_oracle_client(lib_dir=lib_dir) else: - if MODE_TRACE: print("Using python-oracledb thin") + if MODE_TRACE: + print("Using python-oracledb thin") + # If your existing cx_Oracle code never used positional arguments for # connection and pool creation calls then inject_connect_shim() is not @@ -171,50 +177,105 @@ def inject_connect_shim(): """ class ShimConnection(oracledb.Connection): - - def __init__(self, user=None, password=None, dsn=None, - mode=oracledb.DEFAULT_AUTH, handle=0, pool=None, - threaded=False, events=False, cclass=None, - purity=oracledb.ATTR_PURITY_DEFAULT, - newpassword=None, encoding=None, nencoding=None, - edition=None, appcontext=[], tag=None, - matchanytag=False, shardingkey=[], - supershardingkey=[], stmtcachesize=20): + def __init__( + self, + user=None, + password=None, + dsn=None, + mode=oracledb.DEFAULT_AUTH, + handle=0, + pool=None, + threaded=False, + events=False, + cclass=None, + purity=oracledb.ATTR_PURITY_DEFAULT, + newpassword=None, + encoding=None, + nencoding=None, + edition=None, + appcontext=[], + tag=None, + matchanytag=False, + shardingkey=[], + supershardingkey=[], + stmtcachesize=20, + ): if dsn is None and password is None: dsn = user user = None - super().__init__(dsn=dsn, user=user, password=password, - mode=mode, handle=handle, pool=pool, - threaded=threaded, events=events, cclass=cclass, - purity=purity, newpassword=newpassword, - edition=edition, appcontext=appcontext, tag=tag, - matchanytag=matchanytag, shardingkey=shardingkey, - supershardingkey=supershardingkey, - stmtcachesize=stmtcachesize) + super().__init__( + dsn=dsn, + user=user, + password=password, + mode=mode, + handle=handle, + pool=pool, + threaded=threaded, + events=events, + cclass=cclass, + purity=purity, + newpassword=newpassword, + edition=edition, + appcontext=appcontext, + tag=tag, + matchanytag=matchanytag, + shardingkey=shardingkey, + supershardingkey=supershardingkey, + stmtcachesize=stmtcachesize, + ) class ShimPool(oracledb.SessionPool): - - def __init__(self, user=None, password=None, dsn=None, min=1, max=2, - increment=1, connectiontype=oracledb.Connection, - threaded=True, getmode=oracledb.SPOOL_ATTRVAL_NOWAIT, - events=False, homogeneous=True, externalauth=False, - encoding=None, nencoding=None, edition=None, timeout=0, - wait_timeout=0, max_lifetime_session=0, session_callback=None, - max_sessions_per_shard=0, soda_metadata_cache=False, - stmtcachesize=20, ping_interval=60): - - super().__init__(dsn=dsn, user=user, password=password, - min=min, max=max, increment=increment, - connectiontype=connectiontype, threaded=threaded, - getmode=getmode, events=events, homogeneous=homogeneous, - externalauth=externalauth, encoding=encoding, - nencoding=nencoding, edition=edition, timeout=timeout, - wait_timeout=wait_timeout, - max_lifetime_session=max_lifetime_session, - session_callback=session_callback, - max_sessions_per_shard=max_sessions_per_shard, - soda_metadata_cache=soda_metadata_cache, - stmtcachesize=stmtcachesize, ping_interval=ping_interval) + def __init__( + self, + user=None, + password=None, + dsn=None, + min=1, + max=2, + increment=1, + connectiontype=oracledb.Connection, + threaded=True, + getmode=oracledb.SPOOL_ATTRVAL_NOWAIT, + events=False, + homogeneous=True, + externalauth=False, + encoding=None, + nencoding=None, + edition=None, + timeout=0, + wait_timeout=0, + max_lifetime_session=0, + session_callback=None, + max_sessions_per_shard=0, + soda_metadata_cache=False, + stmtcachesize=20, + ping_interval=60, + ): + super().__init__( + dsn=dsn, + user=user, + password=password, + min=min, + max=max, + increment=increment, + connectiontype=connectiontype, + threaded=threaded, + getmode=getmode, + events=events, + homogeneous=homogeneous, + externalauth=externalauth, + encoding=encoding, + nencoding=nencoding, + edition=edition, + timeout=timeout, + wait_timeout=wait_timeout, + max_lifetime_session=max_lifetime_session, + session_callback=session_callback, + max_sessions_per_shard=max_sessions_per_shard, + soda_metadata_cache=soda_metadata_cache, + stmtcachesize=stmtcachesize, + ping_interval=ping_interval, + ) global connect connect = ShimConnection @@ -223,5 +284,6 @@ def __init__(self, user=None, password=None, dsn=None, min=1, max=2, global SessionPool SessionPool = ShimPool + if ALLOW_POSITIONAL_CONNECT_ARGS and driver_type.lower() != "cx": inject_connect_shim() diff --git a/python/python-oracledb/plsql_collection.py b/python/python-oracledb/plsql_collection.py index 4d834294..33687031 100644 --- a/python/python-oracledb/plsql_collection.py +++ b/python/python-oracledb/plsql_collection.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,16 +20,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # plsql_collection.py # # Demonstrates how to get the value of a PL/SQL collection from a stored # procedure. # # This feature is only available in Oracle Database 12.1 and higher. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -38,9 +38,11 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # create a new empty object of the correct type. # note the use of a PL/SQL type that is defined in a package @@ -49,7 +51,6 @@ # call the stored procedure which will populate the object with connection.cursor() as cursor: - cursor.callproc("pkg_Demo.DemoCollectionOut", (obj,)) # show the indexes that are used by the collection diff --git a/python/python-oracledb/plsql_collection_async.py b/python/python-oracledb/plsql_collection_async.py new file mode 100644 index 00000000..e39d11f7 --- /dev/null +++ b/python/python-oracledb/plsql_collection_async.py @@ -0,0 +1,77 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# plsql_collection_async.py +# +# An asynchronous version of plsql_collection.py +# +# Demonstrates how to get the value of a PL/SQL collection from a stored +# procedure. +# +# This feature is only available in Oracle Database 12.1 and higher. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # create a new empty object of the correct type. + # note the use of a PL/SQL type that is defined in a package + type_obj = await connection.gettype("PKG_DEMO.UDT_STRINGLIST") + obj = type_obj.newobject() + + # call the stored procedure which will populate the object + with connection.cursor() as cursor: + await cursor.callproc("pkg_Demo.DemoCollectionOut", (obj,)) + + # show the indexes that are used by the collection + print("Indexes and values of collection:") + ix = obj.first() + while ix is not None: + print(ix, "->", obj.getelement(ix)) + ix = obj.next(ix) + print() + + # show the values as a simple list + print("Values of collection as list:") + print(obj.aslist()) + print() + + # show the values as a simple dictionary + print("Values of collection as dictionary:") + print(obj.asdict()) + print() + + +asyncio.run(main()) diff --git a/python/python-oracledb/plsql_function.py b/python/python-oracledb/plsql_function.py index 28e0ff58..36679742 100644 --- a/python/python-oracledb/plsql_function.py +++ b/python/python-oracledb/plsql_function.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,13 +20,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # plsql_function.py # # Demonstrates how to call a PL/SQL function and get its return value. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -35,11 +35,13 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: # The second parameter is the expected return type of the PL/SQL function - res = cursor.callfunc('myfunc', int, ('abc', 2)) + res = cursor.callfunc("myfunc", int, ("abc", 2)) print(res) diff --git a/python/python-oracledb/plsql_function_async.py b/python/python-oracledb/plsql_function_async.py new file mode 100644 index 00000000..4606c3ba --- /dev/null +++ b/python/python-oracledb/plsql_function_async.py @@ -0,0 +1,53 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# plsql_function_async.py +# +# An asynchronous version of plsql_function.py +# +# Demonstrates how to call a PL/SQL function and get its return value. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # The second parameter is the expected return type of the PL/SQL + # function + res = await cursor.callfunc("myfunc", int, ("abc", 2)) + print(res) + + +asyncio.run(main()) diff --git a/python/python-oracledb/plsql_procedure.py b/python/python-oracledb/plsql_procedure.py index 84e771f9..7703cff3 100644 --- a/python/python-oracledb/plsql_procedure.py +++ b/python/python-oracledb/plsql_procedure.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,14 +20,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # plsql_procedure.py # # Demonstrates how to call a PL/SQL stored procedure and get the results of an # OUT variable. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -36,11 +36,13 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: myvar = cursor.var(int) - cursor.callproc('myproc', (123, myvar)) + cursor.callproc("myproc", (123, myvar)) print(myvar.getvalue()) diff --git a/python/python-oracledb/plsql_procedure_async.py b/python/python-oracledb/plsql_procedure_async.py new file mode 100644 index 00000000..d5c9875e --- /dev/null +++ b/python/python-oracledb/plsql_procedure_async.py @@ -0,0 +1,53 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# plsql_procedure_async.py +# +# An asynchronous version of plsql_procedure.py +# +# Demonstrates how to call a PL/SQL stored procedure and get the results of an +# OUT variable. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + myvar = cursor.var(int) + await cursor.callproc("myproc", (123, myvar)) + print(myvar.getvalue()) + + +asyncio.run(main()) diff --git a/python/python-oracledb/plsql_record.py b/python/python-oracledb/plsql_record.py index 3a526959..d3bc2a9c 100644 --- a/python/python-oracledb/plsql_record.py +++ b/python/python-oracledb/plsql_record.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,15 +20,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # plsql_record.py # # Demonstrates how to bind (IN and OUT) a PL/SQL record. # # This feature is only available in Oracle Database 12.1 and higher. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import datetime @@ -39,9 +39,11 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # create new object of the correct type # note the use of a PL/SQL record defined in a package @@ -61,7 +63,6 @@ print() with connection.cursor() as cursor: - # call the stored procedure which will modify the object cursor.callproc("pkg_Demo.DemoRecordsInOut", (obj,)) diff --git a/python/python-oracledb/plsql_record_async.py b/python/python-oracledb/plsql_record_async.py new file mode 100644 index 00000000..633b1f40 --- /dev/null +++ b/python/python-oracledb/plsql_record_async.py @@ -0,0 +1,78 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# plsql_record_async.py +# +# An asynchronous version of plsql_record.py +# +# Demonstrates how to bind (IN and OUT) a PL/SQL record. +# +# This feature is only available in Oracle Database 12.1 and higher. +# ----------------------------------------------------------------------------- + +import asyncio +import datetime + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # create new object of the correct type + # note the use of a PL/SQL record defined in a package + # a table record identified by TABLE%ROWTYPE can also be used + type_obj = await connection.gettype("PKG_DEMO.UDT_DEMORECORD") + obj = type_obj.newobject() + obj.NUMBERVALUE = 6 + obj.STRINGVALUE = "Test String" + obj.DATEVALUE = datetime.datetime(2016, 5, 28) + obj.BOOLEANVALUE = False + + # show the original values + print("NUMBERVALUE ->", obj.NUMBERVALUE) + print("STRINGVALUE ->", obj.STRINGVALUE) + print("DATEVALUE ->", obj.DATEVALUE) + print("BOOLEANVALUE ->", obj.BOOLEANVALUE) + print() + + with connection.cursor() as cursor: + # call the stored procedure which will modify the object + await cursor.callproc("pkg_Demo.DemoRecordsInOut", (obj,)) + + # show the modified values + print("NUMBERVALUE ->", obj.NUMBERVALUE) + print("STRINGVALUE ->", obj.STRINGVALUE) + print("DATEVALUE ->", obj.DATEVALUE) + print("BOOLEANVALUE ->", obj.BOOLEANVALUE) + print() + + +asyncio.run(main()) diff --git a/python/python-oracledb/query.py b/python/python-oracledb/query.py index 10f81cce..8a0d42e6 100644 --- a/python/python-oracledb/query.py +++ b/python/python-oracledb/query.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,13 +20,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # query.py # # Demonstrates different ways of fetching rows from a query. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -35,16 +35,17 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) sql = """select * from SampleQueryTab where id < 6 order by id""" with connection.cursor() as cursor: - print("Get all rows via an iterator") for result in cursor.execute(sql): print(result) diff --git a/python/python-oracledb/query_arraysize.py b/python/python-oracledb/query_arraysize.py index e9feae94..3d1af672 100644 --- a/python/python-oracledb/query_arraysize.py +++ b/python/python-oracledb/query_arraysize.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # query_arraysize.py # # Demonstrates how to alter the arraysize and prefetchrows values in order to @@ -33,7 +33,7 @@ # # The best values need to be determined by tuning in your production # environment. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import time @@ -44,18 +44,19 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # Global values can be set to override the defaults used when a cursor is # created # -#oracledb.defaults.prefetchrows = 200 # default is 2 -#oracledb.defaults.arraysize = 200 # default is 100 +# oracledb.defaults.prefetchrows = 200 # default is 2 +# oracledb.defaults.arraysize = 200 # default is 100 with connection.cursor() as cursor: - # Scenario 1: Selecting from a "large" table start = time.time() @@ -67,40 +68,40 @@ cursor.execute("select * from bigtab") res = cursor.fetchall() - elapsed = (time.time() - start) + elapsed = time.time() - start print("Prefetchrows:", cursor.prefetchrows, "Arraysize:", cursor.arraysize) print("Retrieved", len(res), "rows in", elapsed, "seconds") - # Scenario 2: Selecting a "page" of data PAGE_SIZE = 20 # number of rows to fetch from the table start = time.time() - cursor.arraysize = PAGE_SIZE - cursor.prefetchrows = PAGE_SIZE + 1 # Set this one larger than arraysize - # to remove an extra round-trip + cursor.arraysize = PAGE_SIZE + cursor.prefetchrows = PAGE_SIZE + 1 # Set this one larger than arraysize + # to remove an extra round-trip - cursor.execute("""select * from bigtab - offset 0 rows fetch next :r rows only""", [PAGE_SIZE]) + cursor.execute( + "select * from bigtab offset 0 rows fetch next :r rows only", + [PAGE_SIZE], + ) res = cursor.fetchall() - elapsed = (time.time() - start) + elapsed = time.time() - start print("Prefetchrows:", cursor.prefetchrows, "Arraysize:", cursor.arraysize) print("Retrieved", len(res), "rows in", elapsed, "seconds") - # Scenario 3: Selecting one row of data is similar to the previous example start = time.time() - cursor.arraysize = 1 + cursor.arraysize = 1 cursor.prefetchrows = 2 cursor.execute("select * from bigtab where rownum < 2") res = cursor.fetchall() - elapsed = (time.time() - start) + elapsed = time.time() - start print("Prefetchrows:", cursor.prefetchrows, "Arraysize:", cursor.arraysize) print("Retrieved", len(res), "row in", elapsed, "seconds") diff --git a/python/python-oracledb/query_arraysize_async.py b/python/python-oracledb/query_arraysize_async.py new file mode 100644 index 00000000..965bcc8c --- /dev/null +++ b/python/python-oracledb/query_arraysize_async.py @@ -0,0 +1,128 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# query_arraysize_async.py +# +# An asynchronous version of query_arraysize.py +# +# Demonstrates how to alter the arraysize and prefetchrows values in order to +# tune the performance of fetching data from the database. Increasing these +# values can reduce the number of network round trips and overhead required to +# fetch all of the rows from a large table. The value affect internal buffers +# and do not affect how, or when, rows are returned to your application. +# +# The best values need to be determined by tuning in your production +# environment. +# ----------------------------------------------------------------------------- + +import asyncio +import time + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # Global values can be set to override the defaults used when a cursor is + # created + # + # oracledb.defaults.prefetchrows = 200 # default is 2 + # oracledb.defaults.arraysize = 200 # default is 100 + + with connection.cursor() as cursor: + # Scenario 1: Selecting from a "large" table + + start = time.time() + + # Tune arraysize for your memory, network, and performance + # requirements. Generally leave prefetchrows at its default of 2. + cursor.arraysize = 1000 + + await cursor.execute("select * from bigtab") + res = await cursor.fetchall() + + elapsed = time.time() - start + print( + "Prefetchrows:", + cursor.prefetchrows, + "Arraysize:", + cursor.arraysize, + ) + print("Retrieved", len(res), "rows in", elapsed, "seconds") + + # Scenario 2: Selecting a "page" of data + + PAGE_SIZE = 20 # number of rows to fetch from the table + + start = time.time() + + # Set prefetchrows one larger than arraysize + # to remove an extra round-trip + cursor.arraysize = PAGE_SIZE + cursor.prefetchrows = PAGE_SIZE + 1 + + await cursor.execute( + "select * from bigtab offset 0 rows fetch next :r rows only", + [PAGE_SIZE], + ) + res = await cursor.fetchall() + + elapsed = time.time() - start + print( + "Prefetchrows:", + cursor.prefetchrows, + "Arraysize:", + cursor.arraysize, + ) + print("Retrieved", len(res), "rows in", elapsed, "seconds") + + # Scenario 3: Selecting one row of data is similar to the previous + # example + + start = time.time() + + cursor.arraysize = 1 + cursor.prefetchrows = 2 + + await cursor.execute("select * from bigtab where rownum < 2") + res = await cursor.fetchall() + + elapsed = time.time() - start + print( + "Prefetchrows:", + cursor.prefetchrows, + "Arraysize:", + cursor.arraysize, + ) + print("Retrieved", len(res), "row in", elapsed, "seconds") + + +asyncio.run(main()) diff --git a/python/python-oracledb/query_async.py b/python/python-oracledb/query_async.py new file mode 100644 index 00000000..d12b4a59 --- /dev/null +++ b/python/python-oracledb/query_async.py @@ -0,0 +1,84 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# query_async.py +# +# An asynchronous version of query.py +# +# Demonstrates different ways of fetching rows from a query with asyncio. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + sql = """select * from SampleQueryTab + where id < 6 + order by id""" + + with connection.cursor() as cursor: + print("Get all rows via an iterator") + await cursor.execute(sql) + async for result in cursor: + print(result) + print() + + print("Query one row at a time") + await cursor.execute(sql) + row = await cursor.fetchone() + print(row) + row = await cursor.fetchone() + print(row) + print() + + print("Fetch many rows") + res = await connection.fetchmany(sql, num_rows=3) + print(res) + print() + + print("Fetch all rows") + res = await connection.fetchall(sql) + print(res) + print() + + with connection.cursor() as cursor: + print("Fetch each row as a Dictionary") + await cursor.execute(sql) + columns = [col.name for col in cursor.description] + cursor.rowfactory = lambda *args: dict(zip(columns, args)) + async for row in cursor: + print(row) + + +asyncio.run(main()) diff --git a/python/python-oracledb/query_strings_as_bytes.py b/python/python-oracledb/query_strings_as_bytes.py index 07ea2394..102c097b 100644 --- a/python/python-oracledb/query_strings_as_bytes.py +++ b/python/python-oracledb/query_strings_as_bytes.py @@ -20,15 +20,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # query_strings_as_bytes.py # # Demonstrates how to query strings as bytes (bypassing decoding of the bytes # into a Python string). This can be useful when attempting to fetch data that # was stored in the database in the wrong encoding. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -37,21 +37,26 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -STRING_VAL = 'I bought a cafetière on the Champs-Élysées' +STRING_VAL = "I bought a cafetière on the Champs-Élysées" + def return_strings_as_bytes(cursor, metadata): if metadata.type_code is oracledb.DB_TYPE_VARCHAR: return cursor.var(str, arraysize=cursor.arraysize, bypass_decode=True) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # truncate table and populate with our data of choice with connection.cursor() as cursor: cursor.execute("truncate table TestTempTable") - cursor.execute("insert into TestTempTable values (1, :val)", - val=STRING_VAL) + cursor.execute( + "insert into TestTempTable values (1, :val)", val=STRING_VAL + ) connection.commit() # fetch the data normally and show that it is returned as a string diff --git a/python/python-oracledb/query_strings_as_bytes_async.py b/python/python-oracledb/query_strings_as_bytes_async.py new file mode 100644 index 00000000..984ec02c --- /dev/null +++ b/python/python-oracledb/query_strings_as_bytes_async.py @@ -0,0 +1,81 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# query_strings_as_bytes_async.py +# +# An asynchronous version of query_strings_as_bytes.py +# +# Demonstrates how to query strings as bytes (bypassing decoding of the bytes +# into a Python string). This can be useful when attempting to fetch data that +# was stored in the database in the wrong encoding. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + +STRING_VAL = "I bought a cafetière on the Champs-Élysées" + + +def return_strings_as_bytes(cursor, metadata): + if metadata.type_code is oracledb.DB_TYPE_VARCHAR: + return cursor.var(str, arraysize=cursor.arraysize, bypass_decode=True) + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + # truncate table and populate with our data of choice + with connection.cursor() as cursor: + await cursor.execute("truncate table TestTempTable") + await cursor.execute( + "insert into TestTempTable values (1, :val)", val=STRING_VAL + ) + await connection.commit() + + # fetch the data normally and show that it is returned as a string + with connection.cursor() as cursor: + await cursor.execute("select IntCol, StringCol from TestTempTable") + print("Data fetched using normal technique:") + async for row in cursor: + print(row) + print() + + # fetch the data, bypassing the decode and show that it is returned as + # bytes + with connection.cursor() as cursor: + cursor.outputtypehandler = return_strings_as_bytes + await cursor.execute("select IntCol, StringCol from TestTempTable") + print("Data fetched using bypass decode technique:") + async for row in cursor: + print(row) + + +asyncio.run(main()) diff --git a/python/python-oracledb/raw_aq.py b/python/python-oracledb/raw_aq.py index 4f9ceac8..526a00e7 100644 --- a/python/python-oracledb/raw_aq.py +++ b/python/python-oracledb/raw_aq.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2019, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,14 +25,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # raw_aq.py # # Demonstrates how to use advanced queuing with RAW data. It makes use of a # RAW queue created in the sample setup. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -45,16 +45,17 @@ "The first message", "The second message", "The third message", - "The fourth and final message" + "The fourth and final message", ] -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # create a queue with connection.cursor() as cursor: - queue = connection.queue(QUEUE_NAME) queue.deqoptions.wait = oracledb.DEQ_NO_WAIT queue.deqoptions.navigation = oracledb.DEQ_FIRST_MSG @@ -67,7 +68,6 @@ # enqueue a few messages print("Enqueuing messages...") with connection.cursor() as cursor: - for data in PAYLOAD_DATA: print(data) queue.enqone(connection.msgproperties(payload=data)) @@ -76,7 +76,6 @@ # dequeue the messages print("\nDequeuing messages...") with connection.cursor() as cursor: - while True: props = queue.deqone() if not props: diff --git a/python/python-oracledb/ref_cursor.py b/python/python-oracledb/ref_cursor.py index f36b725b..d6a99ed1 100644 --- a/python/python-oracledb/ref_cursor.py +++ b/python/python-oracledb/ref_cursor.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2018, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2018, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,13 +20,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # ref_cursor.py # # Demonstrates the use of REF CURSORS. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import time @@ -37,12 +37,13 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - ref_cursor = connection.cursor() cursor.callproc("myrefcursorproc", (2, 6, ref_cursor)) print("Rows between 2 and 6:") @@ -57,11 +58,11 @@ print(row) print() - #-------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Setting prefetchrows and arraysize of a REF CURSOR can improve # performance when fetching a large number of rows by reducing network # round-trips. - #-------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Truncate the table used for this demo cursor.execute("truncate table TestTempTable") @@ -75,16 +76,27 @@ # Perform an untuned fetch ref_cursor = connection.cursor() - print("ref_cursor.prefetchrows =", ref_cursor.prefetchrows, - "ref_cursor.arraysize =", ref_cursor.arraysize) + print( + "ref_cursor.prefetchrows =", + ref_cursor.prefetchrows, + "ref_cursor.arraysize =", + ref_cursor.arraysize, + ) start = time.time() sum_rows = 0 cursor.callproc("myrefcursorproc2", [ref_cursor]) for row in ref_cursor: sum_rows += row[0] - elapsed = (time.time() - start) - print("Sum of IntCol for", num_rows, "rows is ", sum_rows, - "in", elapsed, "seconds") + elapsed = time.time() - start + print( + "Sum of IntCol for", + num_rows, + "rows is ", + sum_rows, + "in", + elapsed, + "seconds", + ) print() # Repeat the call but increase the internal arraysize and prefetch row @@ -94,13 +106,24 @@ ref_cursor.prefetchrows = 1000 ref_cursor.arraysize = 1000 - print("ref_cursor.prefetchrows =", ref_cursor.prefetchrows, - "ref_cursor.arraysize =", ref_cursor.arraysize) + print( + "ref_cursor.prefetchrows =", + ref_cursor.prefetchrows, + "ref_cursor.arraysize =", + ref_cursor.arraysize, + ) start = time.time() sum_rows = 0 cursor.callproc("myrefcursorproc2", [ref_cursor]) for row in ref_cursor: sum_rows += row[0] - elapsed = (time.time() - start) - print("Sum of IntCol for", num_rows, "rows is ", sum_rows, - "in", elapsed, "seconds") + elapsed = time.time() - start + print( + "Sum of IntCol for", + num_rows, + "rows is ", + sum_rows, + "in", + elapsed, + "seconds", + ) diff --git a/python/python-oracledb/ref_cursor_async.py b/python/python-oracledb/ref_cursor_async.py new file mode 100644 index 00000000..8de5b727 --- /dev/null +++ b/python/python-oracledb/ref_cursor_async.py @@ -0,0 +1,132 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# ref_cursor_async.py +# +# An asynchronous version of ref_cursor.py +# +# Demonstrates the use of REF CURSORS. +# ----------------------------------------------------------------------------- + +import asyncio +import time + +import oracledb +import sample_env + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + ref_cursor = connection.cursor() + await connection.callproc("myrefcursorproc", (2, 6, ref_cursor)) + print("Rows between 2 and 6:") + async for row in ref_cursor: + print(row) + print() + + ref_cursor = connection.cursor() + await connection.callproc("myrefcursorproc", (8, 9, ref_cursor)) + print("Rows between 8 and 9:") + async for row in ref_cursor: + print(row) + print() + + # --------------------------------------------------------------------- + # Setting prefetchrows and arraysize of a REF CURSOR can improve + # performance when fetching a large number of rows by reducing network + # round-trips. + # --------------------------------------------------------------------- + + # Truncate the table used for this demo + await connection.execute("truncate table TestTempTable") + + # Populate the table with a large number of rows + num_rows = 50000 + sql = "insert into TestTempTable (IntCol) values (:1)" + data = [(n + 1,) for n in range(num_rows)] + await connection.executemany(sql, data) + + # Perform an untuned fetch + ref_cursor = connection.cursor() + + print( + "ref_cursor.prefetchrows =", + ref_cursor.prefetchrows, + "ref_cursor.arraysize =", + ref_cursor.arraysize, + ) + start = time.time() + sum_rows = 0 + await connection.callproc("myrefcursorproc2", [ref_cursor]) + async for row in ref_cursor: + sum_rows += row[0] + elapsed = time.time() - start + print( + "Sum of IntCol for", + num_rows, + "rows is ", + sum_rows, + "in", + elapsed, + "seconds", + ) + print() + + # Repeat the call but increase the internal arraysize and prefetch row + # buffers for the REF CURSOR to tune the number of round-trips to the + # database + ref_cursor = connection.cursor() + ref_cursor.prefetchrows = 1000 + ref_cursor.arraysize = 1000 + + print( + "ref_cursor.prefetchrows =", + ref_cursor.prefetchrows, + "ref_cursor.arraysize =", + ref_cursor.arraysize, + ) + start = time.time() + sum_rows = 0 + await connection.callproc("myrefcursorproc2", [ref_cursor]) + async for row in ref_cursor: + sum_rows += row[0] + elapsed = time.time() - start + print( + "Sum of IntCol for", + num_rows, + "rows is ", + sum_rows, + "in", + elapsed, + "seconds", + ) + + +asyncio.run(main()) diff --git a/python/python-oracledb/return_lobs_as_strings.py b/python/python-oracledb/return_lobs_as_strings.py index 23610767..71a33e39 100644 --- a/python/python-oracledb/return_lobs_as_strings.py +++ b/python/python-oracledb/return_lobs_as_strings.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,9 +25,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # return_lobs_as_strings.py # # Returns all CLOB values as strings and BLOB values as bytes. The @@ -35,7 +35,7 @@ # and then reading the contents of the LOBs as it avoids round-trips to the # database. Be aware, however, that this method requires contiguous memory so # is not suitable for very large LOBs. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -47,43 +47,54 @@ # indicate that LOBS should not be fetched oracledb.defaults.fetch_lobs = False -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # add some data to the tables print("Populating tables with data...") cursor.execute("truncate table TestClobs") cursor.execute("truncate table TestBlobs") long_string = "" for i in range(10): - char = chr(ord('A') + i) + char = chr(ord("A") + i) long_string += char * 25000 - cursor.execute("insert into TestClobs values (:1, :2)", - (i + 1, "STRING " + long_string)) - cursor.execute("insert into TestBlobs values (:1, :2)", - (i + 1, long_string.encode("ascii"))) + cursor.execute( + "insert into TestClobs values (:1, :2)", + (i + 1, "STRING " + long_string), + ) + cursor.execute( + "insert into TestBlobs values (:1, :2)", + (i + 1, long_string.encode("ascii")), + ) connection.commit() # fetch the data and show the results print("CLOBS returned as strings") - cursor.execute(""" - select - IntCol, - ClobCol - from TestClobs - order by IntCol""") + cursor.execute( + """ + select + IntCol, + ClobCol + from TestClobs + order by IntCol + """ + ) for int_col, value in cursor: print("Row:", int_col, "string of length", len(value)) print() print("BLOBS returned as bytes") - cursor.execute(""" - select - IntCol, - BlobCol - from TestBlobs - order by IntCol""") + cursor.execute( + """ + select + IntCol, + BlobCol + from TestBlobs + order by IntCol + """ + ) for int_col, value in cursor: print("Row:", int_col, "string of length", value and len(value) or 0) diff --git a/python/python-oracledb/return_lobs_as_strings_async.py b/python/python-oracledb/return_lobs_as_strings_async.py new file mode 100644 index 00000000..8259cfb6 --- /dev/null +++ b/python/python-oracledb/return_lobs_as_strings_async.py @@ -0,0 +1,102 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# return_lobs_as_strings_async.py +# +# An asynchronous version of return_lobs_as_strings.py +# +# Returns all CLOB values as strings and BLOB values as bytes. The +# performance of this technique is significantly better than fetching the LOBs +# and then reading the contents of the LOBs as it avoids round-trips to the +# database. Be aware, however, that this method requires contiguous memory so +# is not suitable for very large LOBs. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + +# indicate that LOBS should not be fetched +oracledb.defaults.fetch_lobs = False + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # add some data to the tables + print("Populating tables with data...") + await cursor.execute("truncate table TestClobs") + await cursor.execute("truncate table TestBlobs") + long_string = "" + for i in range(10): + char = chr(ord("A") + i) + long_string += char * 25000 + await cursor.execute( + "insert into TestClobs values (:1, :2)", + (i + 1, "STRING " + long_string), + ) + await cursor.execute( + "insert into TestBlobs values (:1, :2)", + (i + 1, long_string.encode("ascii")), + ) + await connection.commit() + + # fetch the data and show the results + print("CLOBS returned as strings") + await cursor.execute( + """ + select + IntCol, + ClobCol + from TestClobs + order by IntCol + """ + ) + async for int_col, value in cursor: + print("Row:", int_col, "string of length", len(value)) + print() + print("BLOBS returned as bytes") + await cursor.execute( + """ + select + IntCol, + BlobCol + from TestBlobs + order by IntCol + """ + ) + async for int_col, value in cursor: + print( + "Row:", int_col, "string of length", value and len(value) or 0 + ) + + +asyncio.run(main()) diff --git a/python/python-oracledb/return_numbers_as_decimals.py b/python/python-oracledb/return_numbers_as_decimals.py index 2a3347a6..eaeed69c 100644 --- a/python/python-oracledb/return_numbers_as_decimals.py +++ b/python/python-oracledb/return_numbers_as_decimals.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2017, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2017, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # return_numbers_as_decimals.py # # Returns all numbers as decimals. This is needed if the full decimal @@ -30,9 +30,7 @@ # (http://blog.reverberate.org/2016/02/06/floating-point-demystified-part2.html) # for an explanation of why decimal numbers (like Oracle numbers) cannot be # represented exactly by floating point numbers. -#------------------------------------------------------------------------------ - -import decimal +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -44,9 +42,11 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: cursor.execute("select * from TestNumbers") diff --git a/python/python-oracledb/return_numbers_as_decimals_async.py b/python/python-oracledb/return_numbers_as_decimals_async.py new file mode 100644 index 00000000..f304e98c --- /dev/null +++ b/python/python-oracledb/return_numbers_as_decimals_async.py @@ -0,0 +1,59 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# return_numbers_as_decimals_async.py +# +# An asynchronous version of return_numbers_as_decimals.py +# +# Returns all numbers as decimals. This is needed if the full decimal +# precision of Oracle numbers is required by the application. See this article +# (http://blog.reverberate.org/2016/02/06/floating-point-demystified-part2.html) +# for an explanation of why decimal numbers (like Oracle numbers) cannot be +# represented exactly by floating point numbers. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + +# indicate that numbers should be fetched as decimals +oracledb.defaults.fetch_decimals = True + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + await cursor.execute("select * from TestNumbers") + async for row in cursor: + print("Row:", row) + + +asyncio.run(main()) diff --git a/python/python-oracledb/rows_as_instance.py b/python/python-oracledb/rows_as_instance.py index ca95337c..67973a93 100644 --- a/python/python-oracledb/rows_as_instance.py +++ b/python/python-oracledb/rows_as_instance.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,15 +25,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # rows_as_instance.py # # Returns rows as instances instead of tuples. See the ceDatabase.Row class # in the cx_PyGenLib project (http://cx-pygenlib.sourceforge.net) for a more # advanced example. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -42,44 +42,50 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -class Test: +class Test: def __init__(self, a, b, c): self.a = a self.b = b self.c = c -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) -with connection.cursor() as cursor: +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) +with connection.cursor() as cursor: # create sample data - cursor.execute(""" + cursor.execute( + """ begin - begin - execute immediate 'drop table TestInstances'; - exception - when others then - if sqlcode <> -942 then - raise; - end if; - end; + begin + execute immediate 'drop table TestInstances'; + exception + when others then + if sqlcode <> -942 then + raise; + end if; + end; - execute immediate 'create table TestInstances ( + execute immediate 'create table TestInstances ( a varchar2(60) not null, b number(9) not null, c date not null)'; - execute immediate - 'insert into TestInstances values (''First'', 5, sysdate)'; + execute immediate + 'insert into TestInstances values (''First'', 5, sysdate)'; - execute immediate - 'insert into TestInstances values (''Second'', 25, sysdate)'; + execute immediate + 'insert into TestInstances + values (''Second'', 25, sysdate)'; - commit; - end;""") + commit; + end; + """ + ) # retrieve the data and display it cursor.execute("select * from TestInstances") diff --git a/python/python-oracledb/rows_as_instance_async.py b/python/python-oracledb/rows_as_instance_async.py new file mode 100644 index 00000000..41a0402b --- /dev/null +++ b/python/python-oracledb/rows_as_instance_async.py @@ -0,0 +1,95 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# rows_as_instance_async.py +# +# An asynchronous version of rows_as_instance.py +# +# Returns rows as instances instead of tuples. See the ceDatabase.Row class +# in the cx_PyGenLib project (http://cx-pygenlib.sourceforge.net) for a more +# advanced example. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +class Test: + def __init__(self, a, b, c): + self.a = a + self.b = b + self.c = c + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # create sample data + await cursor.execute( + """ + begin + begin + execute immediate 'drop table TestInstances'; + exception + when others then + if sqlcode <> -942 then + raise; + end if; + end; + + execute immediate 'create table TestInstances ( + a varchar2(60) not null, + b number(9) not null, + c date not null)'; + + execute immediate + 'insert into TestInstances + values (''First'', 5, sysdate)'; + + execute immediate + 'insert into TestInstances + values (''Second'', 25, sysdate)'; + + commit; + end; + """ + ) + + # retrieve the data and display it + await cursor.execute("select * from TestInstances") + cursor.rowfactory = Test + print("Rows:") + async for row in cursor: + print("a = %s, b = %s, c = %s" % (row.a, row.b, row.c)) + + +asyncio.run(main()) diff --git a/python/python-oracledb/sample_container/Dockerfile b/python/python-oracledb/sample_container/Dockerfile index f0951b9b..6460ca73 100644 --- a/python/python-oracledb/sample_container/Dockerfile +++ b/python/python-oracledb/sample_container/Dockerfile @@ -13,9 +13,9 @@ # USAGE # # Get an Oracle Database container (see -# https://hub.docker.com/r/gvenzl/oracle-xe): +# https://hub.docker.com/r/gvenzl/oracle-free): # -# podman pull docker.io/gvenzl/oracle-xe:21-slim +# podman pull docker.io/gvenzl/oracle-free # # Create a container with the database, Python, python-oracledb and the # samples. Choose a password for the sample schemas and pass it as an @@ -40,10 +40,12 @@ # # python bind_insert.py # +# Use `vim` to edit files, if required. +# # The database will persist across container shutdowns, but will be deleted # when the container is deleted. -FROM docker.io/gvenzl/oracle-xe:21-slim +FROM docker.io/gvenzl/oracle-free USER root @@ -54,16 +56,18 @@ RUN microdnf module disable python36 && \ WORKDIR /samples/ -COPY setup.py setup.py - -RUN curl -LO https://github.com/oracle/python-oracledb/archive/refs/heads/main.zip && \ - unzip main.zip && mv python-oracledb-main/samples/* . && \ - /bin/rm -rf python-oracledb-main samples main.zip && \ - cat create_schema.py >> /samples/setup.py && chown -R oracle.oinstall /samples/ +RUN curl -LO https://github.com/oracle/python-oracledb/archive/refs/heads/main.zip && \ + unzip main.zip && \ + cp python-oracledb-main/samples/sample_container/setup.py . && \ + /bin/rm -rf python-oracledb-main/samples/sample_container/ python-oracledb-main/samples/sample_notebooks/ && \ + mv python-oracledb-main/samples/* . && \ + /bin/rm -rf python-oracledb-main samples main.zip && \ + cat create_schema.py >> /samples/setup.py && \ + chown -R oracle.oinstall /samples/ USER oracle -RUN python3 -m pip install oracledb Flask --user --no-warn-script-location +RUN python3 -m pip install oracledb Flask --user --no-warn-script-location ARG PYO_PASSWORD @@ -72,8 +76,8 @@ ENV PYO_SAMPLES_MAIN_PASSWORD=${PYO_PASSWORD} ENV PYO_SAMPLES_EDITION_USER=pythoneditions ENV PYO_SAMPLES_EDITION_PASSWORD=${PYO_PASSWORD} ENV PYO_SAMPLES_EDITION_NAME=python_e1 -ENV PYO_SAMPLES_CONNECT_STRING="localhost/xepdb1" -ENV PYO_SAMPLES_DRCP_CONNECT_STRING="localhost/xepdb1:pooled" +ENV PYO_SAMPLES_CONNECT_STRING="localhost/freepdb1" +ENV PYO_SAMPLES_DRCP_CONNECT_STRING="localhost/freepdb1:pooled" ENV PYO_SAMPLES_ADMIN_USER=system # Run the samples using the default python-oracledb 'Thin' mode, if possible diff --git a/python/python-oracledb/sample_container/README.md b/python/python-oracledb/sample_container/README.md index 5a6fe102..24371801 100644 --- a/python/python-oracledb/sample_container/README.md +++ b/python/python-oracledb/sample_container/README.md @@ -9,10 +9,12 @@ It has been tested in an Oracle Linux 8 environment using 'podman', but ## Usage - Get an Oracle Database container (see - https://hub.docker.com/r/gvenzl/oracle-xe): + https://hub.docker.com/r/gvenzl/oracle-free): + + The steps below use 'podman', but 'docker' will also work. ``` - podman pull docker.io/gvenzl/oracle-xe:21-slim + podman pull docker.io/gvenzl/oracle-free ``` - Create a container with the database, Python, python-oracledb and the diff --git a/python/python-oracledb/sample_container/setup.py b/python/python-oracledb/sample_container/setup.py index e93d4fe5..80e2976d 100644 --- a/python/python-oracledb/sample_container/setup.py +++ b/python/python-oracledb/sample_container/setup.py @@ -26,12 +26,14 @@ for i in range(30): try: - c = oracledb.connect(user="system", - password=pw, - dsn="localhost/xepdb1", - tcp_connect_timeout=5) + c = oracledb.connect( + user="system", + password=pw, + dsn="localhost/freepdb1", + tcp_connect_timeout=5, + ) break - except (OSError, oracledb.Error) as e: + except (OSError, oracledb.Error): print("Waiting for database to open") time.sleep(5) @@ -48,7 +50,9 @@ c = oracledb.connect(mode=oracledb.SYSDBA) cursor = c.cursor() cursor.execute("alter pluggable database all close") -cursor.execute("alter system set enable_per_pdb_drcp=true scope=spfile sid='*'") +cursor.execute( + "alter system set enable_per_pdb_drcp=true scope=spfile sid='*'" +) c = oracledb.connect(mode=oracledb.SYSDBA | oracledb.PRELIM_AUTH) c.startup(force=True) @@ -58,10 +62,9 @@ cursor.execute("alter database mount") cursor.execute("alter database open") -c = oracledb.connect(user="sys", - password=pw, - dsn="localhost/xepdb1", - mode=oracledb.SYSDBA) +c = oracledb.connect( + user="sys", password=pw, dsn="localhost/freepdb1", mode=oracledb.SYSDBA +) cursor = c.cursor() cursor.callproc("dbms_connection_pool.start_pool") diff --git a/python/python-oracledb/sample_env.py b/python/python-oracledb/sample_env.py index c80c1ba2..c94d2165 100644 --- a/python/python-oracledb/sample_env.py +++ b/python/python-oracledb/sample_env.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2017, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Sets the environment used by the python-oracledb sample scripts. Production # applications should consider using External Authentication to avoid hard # coded credentials. @@ -31,7 +31,7 @@ # following environment variables are set: # # PYO_SAMPLES_ORACLE_CLIENT_PATH: Oracle Client or Instant Client library dir -# PYO_SAMPLES_ADMIN_USER: privileged administrative user for setting up samples +# PYO_SAMPLES_ADMIN_USER: privileged admin user for setting up samples # PYO_SAMPLES_ADMIN_PASSWORD: password of PYO_SAMPLES_ADMIN_USER # PYO_SAMPLES_CONNECT_STRING: database connection string # PYO_SAMPLES_DRCP_CONNECT_STRING: database connection string for DRCP @@ -81,7 +81,7 @@ # - PYO_SAMPLES_DRIVER_MODE should be "thin" or "thick". It is used by samples # that can run in both python-oracledb modes. # -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import getpass import os @@ -102,6 +102,7 @@ # directly) and then stored so that a value is not requested more than once PARAMETERS = {} + def get_value(name, label, default_value=None, password=False): value = PARAMETERS.get(name) if value is not None: @@ -120,74 +121,122 @@ def get_value(name, label, default_value=None, password=False): PARAMETERS[name] = value return value + def get_main_user(): - return get_value("PYO_SAMPLES_MAIN_USER", "Main User Name", - DEFAULT_MAIN_USER) + return get_value( + "PYO_SAMPLES_MAIN_USER", "Main User Name", DEFAULT_MAIN_USER + ) + def get_main_password(): - return get_value("PYO_SAMPLES_MAIN_PASSWORD", - f"Password for {get_main_user()}", password=True) + return get_value( + "PYO_SAMPLES_MAIN_PASSWORD", + f"Password for {get_main_user()}", + password=True, + ) + def get_edition_user(): - return get_value("PYO_SAMPLES_EDITION_USER", "Edition User Name", - DEFAULT_EDITION_USER) + return get_value( + "PYO_SAMPLES_EDITION_USER", "Edition User Name", DEFAULT_EDITION_USER + ) + def get_edition_password(): - return get_value("PYO_SAMPLES_EDITION_PASSWORD", - f"Password for {get_edition_user()}", password=True) + return get_value( + "PYO_SAMPLES_EDITION_PASSWORD", + f"Password for {get_edition_user()}", + password=True, + ) + def get_edition_name(): - return get_value("PYO_SAMPLES_EDITION_NAME", "Edition Name", - DEFAULT_EDITION_NAME) + return get_value( + "PYO_SAMPLES_EDITION_NAME", "Edition Name", DEFAULT_EDITION_NAME + ) + def get_connect_string(): - return get_value("PYO_SAMPLES_CONNECT_STRING", "Connect String", - DEFAULT_CONNECT_STRING) + return get_value( + "PYO_SAMPLES_CONNECT_STRING", "Connect String", DEFAULT_CONNECT_STRING + ) + def get_drcp_connect_string(): - return get_value("PYO_SAMPLES_DRCP_CONNECT_STRING", "DRCP Connect String", - DEFAULT_DRCP_CONNECT_STRING) + return get_value( + "PYO_SAMPLES_DRCP_CONNECT_STRING", + "DRCP Connect String", + DEFAULT_DRCP_CONNECT_STRING, + ) + def get_driver_mode(): - return get_value("PYO_SAMPLES_DRIVER_MODE", "Driver mode (thin|thick)", - "thin") + return get_value( + "PYO_SAMPLES_DRIVER_MODE", "Driver mode (thin|thick)", "thin" + ) + def get_is_thin(): return get_driver_mode() == "thin" + def get_edition_connect_string(): - return "%s/%s@%s" % \ - (get_edition_user(), get_edition_password(), get_connect_string()) + return "%s/%s@%s" % ( + get_edition_user(), + get_edition_password(), + get_connect_string(), + ) + + +def get_admin_connection(): + admin_user = get_value( + "PYO_SAMPLES_ADMIN_USER", "Administrative user", "admin" + ) + admin_password = get_value( + "PYO_SAMPLES_ADMIN_PASSWORD", + f"Password for {admin_user}", + password=True, + ) + params = oracledb.ConnectParams() + if admin_user and admin_user.upper() == "SYS": + params.set(mode=oracledb.AUTH_MODE_SYSDBA) + return oracledb.connect( + dsn=get_connect_string(), + params=params, + user=admin_user, + password=admin_password, + ) -def get_admin_connect_string(): - admin_user = get_value("PYO_SAMPLES_ADMIN_USER", "Administrative user", - "admin") - admin_password = get_value("PYO_SAMPLES_ADMIN_PASSWORD", - f"Password for {admin_user}", password=True) - return "%s/%s@%s" % (admin_user, admin_password, get_connect_string()) def get_oracle_client(): - if ((platform.system() == "Darwin" and platform.machine() == "x86_64") or - platform.system() == "Windows"): - return get_value("PYO_SAMPLES_ORACLE_CLIENT_PATH", - "Oracle Instant Client Path") + if ( + platform.system() == "Darwin" and platform.machine() == "x86_64" + ) or platform.system() == "Windows": + return get_value( + "PYO_SAMPLES_ORACLE_CLIENT_PATH", "Oracle Instant Client Path" + ) + def get_server_version(): name = "SERVER_VERSION" value = PARAMETERS.get(name) if value is None: - conn = oracledb.connect(user=get_main_user(), - password=get_main_password(), - dsn=get_connect_string()) + conn = oracledb.connect( + user=get_main_user(), + password=get_main_password(), + dsn=get_connect_string(), + ) value = tuple(int(s) for s in conn.version.split("."))[:2] PARAMETERS[name] = value return value + def run_sql_script(conn, script_name, **kwargs): statement_parts = [] cursor = conn.cursor() - replace_values = [("&" + k + ".", v) for k, v in kwargs.items()] + \ - [("&" + k, v) for k, v in kwargs.items()] + replace_values = [("&" + k + ".", v) for k, v in kwargs.items()] + [ + ("&" + k, v) for k, v in kwargs.items() + ] script_dir = os.path.dirname(os.path.abspath(sys.argv[0])) file_name = os.path.join(script_dir, "sql", script_name + ".sql") for line in open(file_name): @@ -204,12 +253,15 @@ def run_sql_script(conn, script_name, **kwargs): statement_parts = [] else: statement_parts.append(line) - cursor.execute(""" - select name, type, line, position, text - from dba_errors - where owner = upper(:owner) - order by name, type, line, position""", - owner = get_main_user()) + cursor.execute( + """ + select name, type, line, position, text + from dba_errors + where owner = upper(:owner) + order by name, type, line, position + """, + owner=get_main_user(), + ) prev_name = prev_obj_type = None for name, obj_type, line_num, position, text in cursor: if name != prev_name or obj_type != prev_obj_type: diff --git a/python/python-oracledb/sample_notebooks/1-Connection.ipynb b/python/python-oracledb/sample_notebooks/1-Connection.ipynb new file mode 100644 index 00000000..74a75078 --- /dev/null +++ b/python/python-oracledb/sample_notebooks/1-Connection.ipynb @@ -0,0 +1,383 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Banner](images/banner.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# python-oracledb Introduction" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Jupyter Notebook shows how to use [python-oracledb](https://oracle.github.io/python-oracledb/) in its default 'Thin' mode that connects directly to Oracle Database." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# python-oracledb Connection" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Architecture\n", + "\n", + "Documentation reference link: [Introduction to python-oracledb](https://python-oracledb.readthedocs.io/en/latest/user_guide/introduction.html)\n", + "\n", + "![Architecture](images/architecture-thin.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Installation\n", + "\n", + "Documentation reference link: [python-oracledb Installation](https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Install python-oracledb**\n", + "\n", + "Install with a command like one of the following:\n", + "\n", + "```\n", + "$ python3 -m pip install oracledb --upgrade\n", + "$ python3 -m pip install oracledb --upgrade --user\n", + "$ python3 -m pip install oracledb --upgrade --user --proxy=http://proxy.example.com:80\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use python-oracledb, your application code can import the module:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import oracledb" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Connecting to a Database\n", + "\n", + "**Connections are used for executing SQL and PL/SQL in an Oracle Database**\n", + "\n", + "Documentation reference link: [Connecting to Oracle Database](https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Credentials\n", + "un = \"pythondemo\"\n", + "pw = \"welcome\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Instead of hard coding the password, you could prompt for a value, pass it as an environment variable, or use Oracle \"external authentication\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Easy Connect Syntax: \"hostname/servicename\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "cs = \"localhost/orclpdb1\"\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)\n", + "print(connection)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Oracle Client 19c improved [Easy Connect Plus](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-8C85D289-6AF3-41BC-848B-BF39D32648BA) syntax with additional optional settings, for example:\n", + "\n", + "```\n", + "cs = \"tcps://my.cloud.com:1522/orclpdb1?connect_timeout=4&expire_time=10\"\n", + "```\n", + "\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Oracle Network and Oracle Client Configuration Files\n", + "\n", + "Oracle Database's `tnsnames.ora` file can be used. This file maps a connect descriptor to an alias. \n", + "\n", + "Documentation reference link: [Optional configuration files](https://python-oracledb.readthedocs.io/en/latest/user_guide/initialization.html#optional-oracle-net-configuration-files)" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "\n", + "# tnsnames.ora in /opt/oracle/configdir\n", + " \n", + "highperfdb = (description= \n", + " (retry_count=5)(retry_delay=3)\n", + " (address=(protocol=tcps)(port=1522)(host=xxxxxx.oraclecloud.com))\n", + " (connect_data=(service_name=yyyyyyyyyy.oraclecloud.com))\n", + " (security=(ssl_server_cert_dn=\n", + " \"CN=zzzzzzzz.oraclecloud.com,OU=Oracle ADB,O=Oracle Corporation,L=Redwood City,ST=California,C=US\")))\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Your Python code could use the alias as the connection `dsn` value:\n", + "```\n", + "connection = oracledb.connect(user=un, password=pw, dsn=\"highperfdb\", config_dir=\"/opt/oracle/configdir\")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Connection Types" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Standalone Connections\n", + "\n", + "Standalone connections are simple to create." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Stand-alone Connection](images/standalone-connection.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Stand-alone Connections\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)\n", + "\n", + "print(connection)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Pooled Connections" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Pools are highly recommended if you have:\n", + "- a lot of connections that will be used for short periods of time\n", + "- or a small number of connections that are idle for long periods of time\n", + "\n", + "#### Pool advantages\n", + "- Reduced cost of setting up and tearing down connections\n", + "- Dead connection detection and automatic re-establishment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Pooled connection](images/pooled-connection.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Pooled Connections\n", + "\n", + "# Call once during application initization\n", + "pool = oracledb.create_pool(user=un, password=pw, dsn=cs,\n", + " min=1, max=10, increment=1)\n", + "\n", + "# Get a connection when needed in the application body\n", + "with pool.acquire() as connection:\n", + " # do_something_useful(connection)\n", + " print(f\"Got a connection to Oracle Database {connection.version}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Tip** Use a fixed size pool `min` = `max` and `increment = 0`. See [Guideline for Preventing Connection Storms: Use Static Pools](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-7DFBA826-7CC0-4D16-B19C-31D168069B54)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Closing Connections" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Close connections when not needed. This is important for pooled connections.\n", + "\n", + "```\n", + "connection.close()\n", + "```\n", + "\n", + "To avoid resource closing order issues, you may want to use `with` or let resources be closed at end of scope:\n", + "\n", + "```\n", + "with pool.acquire() as connection:\n", + " do_something(connection)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Database Resident Connection Pooling\n", + "\n", + "**Connection pooling on the database tier**\n", + "\n", + "Documentation reference link: [Database Resident Connection Pooling (DRCP)](https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dedicated server processes are the default in the database, but DRCP is an alternative when the database server is short of memory." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![DRCP architecture](images/drcp-architecture.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use DRCP if and only if:\n", + "- The database computer doesn't have enough memory for all the server processes for all open application connections\n", + "- When you have thousands of users which need access to a database server session for a short period of time\n", + "- Applications mostly use same database credentials, and have identical session settings\n", + "\n", + "Using DRCP in conjunction with a python-oracledb connection pool is recommended." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Memory example with 5000 application users and a DRCP pool of size 100\n", + "![DRCP memory comparison](images/drcp-comparison.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In Python, the connect string must request a pooled server, for example with ':pooled'. For best efficiency to allow database server session re-use, set a connection class and use the purity 'PURITY_SELF'.\n", + "\n", + "```\n", + "pool = oracledb.create_pool(user=un, password=pw, dsn=\"dbhost.example.com/orclpdb1:pooled\",\n", + " cclass=\"MYCLASS\", purity=oracledb.PURITY_SELF)\n", + "\n", + "connection = pool.acquire()\n", + "```\n", + "\n", + "Don't forget to start the pool first!:\n", + "```\n", + "SQL> execute dbms_connection_pool.start_pool()\n", + "```\n", + "\n", + "Note DRCP is pre-enabled on Oracle Autonomous Database." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/python/python-oracledb/sample_notebooks/2-Queries.ipynb b/python/python-oracledb/sample_notebooks/2-Queries.ipynb new file mode 100644 index 00000000..803051db --- /dev/null +++ b/python/python-oracledb/sample_notebooks/2-Queries.ipynb @@ -0,0 +1,527 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Banner](images/banner.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Queries - SELECT and WITH Statements" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import oracledb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "un = \"pythondemo\"\n", + "pw = \"welcome\"\n", + "\n", + "adminun = \"system\"\n", + "adminpw = \"oracle\"\n", + "\n", + "cs = \"localhost/orclpdb1\"\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)\n", + "\n", + "systemconnection = oracledb.connect(user=adminun, password=adminpw, dsn=cs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fetching Rows\n", + "\n", + "Documentation reference link: [Fetch Methods](https://python-oracledb.readthedocs.io/en/latest/user_guide/sql_execution.html#fetch-methods)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fetching single rows\n", + "\n", + "with connection.cursor() as cursor:\n", + " \n", + " cursor.execute(\"select * from SampleQueryTab where id = :1\", [6])\n", + " \n", + " row = cursor.fetchone()\n", + " print(row)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fetching all rows from a small table of not too big size\n", + "\n", + "with connection.cursor() as cursor:\n", + " \n", + " cursor.execute(\"select * from SampleQueryTab\")\n", + " \n", + " rows = cursor.fetchall()\n", + " \n", + " for r in rows:\n", + " print(r)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Fetching all rows from a big table\n", + "\n", + "with connection.cursor() as cursor:\n", + " \n", + " cursor.execute(\"select * from SampleQueryTab\") # pretend this is big\n", + " \n", + " while True:\n", + " rows = cursor.fetchmany() # get a batch rows (of size cursor.arraysize=100)\n", + " for r in rows:\n", + " print(r)\n", + " if len(rows) < cursor.arraysize:\n", + " break\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## A key tuning goal is to reduce \"round-trips\"\n", + "\n", + "\n", + "> \"A server round-trip is defined as the trip from the client to the server and back to the client.\"\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "![Round-trip](images/roundtrip.png)\n", + "\n", + "Round-trips affect performance and system scalability.\n", + "\n", + "Make every round-trip useful:\n", + "\n", + "- fetch multiple rows at a time\n", + "- insert multiple rows at a time\n", + "- don't select more data than needed\n", + "- don't overuse commit or rollback" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Round-trip comparison - prefetchrows & arraysize\n", + "\n", + "Documentation reference link: [Tuning python-oracledb](https://python-oracledb.readthedocs.io/en/latest/user_guide/tuning.html)\n", + "\n", + "Regardless of which fetch method is used there are two tuning parameters that affect internal buffering of fetched rows. This alters the number of round-trips required to fetch all the query data from the database. These parameters do not affect how, or when, rows are returned to the application itself.\n", + "\n", + "Here's a demo counting round-trips:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "def get_session_id(connection):\n", + " sid, = connection.cursor().execute(\"SELECT sys_context('userenv', 'sid') FROM dual\").fetchone()\n", + " return sid\n", + "\n", + "def get_round_trips(systemconnection, sid):\n", + " sql = \"\"\"SELECT ss.value\n", + " FROM v$sesstat ss, v$statname sn\n", + " WHERE ss.sid = :sid\n", + " AND ss.statistic# = sn.statistic#\n", + " AND sn.name LIKE '%roundtrip%client%'\"\"\" \n", + " roundtrips, = systemconnection.cursor().execute(sql, [sid]).fetchone()\n", + " return roundtrips\n", + "\n", + "sid = get_session_id(connection)\n", + "\n", + "def do_query(connection, numrows, prefetchrows, arraysize):\n", + "\n", + " roundtrips = get_round_trips(systemconnection, sid)\n", + "\n", + " with connection.cursor() as cursor:\n", + " cursor.prefetchrows = prefetchrows\n", + " cursor.arraysize = arraysize\n", + " cursor.execute(\"select * from all_objects where rownum <= :nr\", [numrows])\n", + " rows = cursor.fetchall()\n", + "\n", + " roundtrips = get_round_trips(systemconnection, sid) - roundtrips\n", + "\n", + " print(\"Number of rows: {:5}, prefetchrows {:4} arraysize {:4} roundtrips {:3} \".format(len(rows), prefetchrows, arraysize, roundtrips))\n", + "\n", + "\n", + "# do_query(connection, number of rows, prefetchrows, arraysize)\n", + "\n", + "print(\"Default prefetch & arraysize values:\")\n", + "do_query(connection, 1, 2, 100)\n", + "do_query(connection, 100, 2, 100)\n", + "do_query(connection, 1000, 2, 100)\n", + "do_query(connection, 10000, 2, 100)\n", + "\n", + "print(\"\\n'Unknown' large number of rows:\")\n", + "do_query(connection, 10000, 2, 1000)\n", + "do_query(connection, 10000, 1000, 1000)\n", + "\n", + "print(\"\\n'Page' of rows:\")\n", + "do_query(connection, 20, 20, 20)\n", + "do_query(connection, 20, 21, 20)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Your tuning goal is to reduce round-trips (thus improving performance) without using too much internal buffer memory.\n", + "\n", + "When selecting a huge number of rows, tuning `arraysize` is important. In general there is no need to set `prefetchrows` in this scenario.\n", + "\n", + "When selecting a known, small number of rows such as for 'paging' through a result set, then set `prefetchrows` to be one larger than the number of rows returned." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Querying LOB Columns\n", + "\n", + "Documentation reference link: [Using CLOB and BLOB Data](https://python-oracledb.readthedocs.io/en/latest/user_guide/lob_data.html)\n", + "\n", + "When fetching LOBs there are two modes that can be used:\n", + "- Fetching as a \"locator\" for streaming use\n", + "- Fetching directly as a string or buffer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Fetching LOBs for streaming\n", + "\n", + "- this is the default\n", + "- it requires more round trips and has more overhead\n", + "- it is useful when very long LOBs are being used" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + "\n", + " id = 1\n", + " textData = \"The quick brown fox jumps over the lazy dog\"\n", + " cursor.execute(\"truncate table TestClobs\")\n", + " cursor.execute(\"insert into TestClobs (IntCol, ClobCol) values (:1, :2)\", [id, textData])\n", + "\n", + " cursor.execute(\"select ClobCol from TestClobs where IntCol = :ic1\", [id])\n", + " c, = cursor.fetchone()\n", + " print(\"CLOB length:\", c.size())\n", + " print(\"CLOB data:\", c.read())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Fetching CLOBs as String\n", + "\n", + "- this is much faster than the streaming method\n", + "- LOBs are limited to 1 GB (and obviously must all be in memory at once)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "oracledb.defaults.fetch_lobs = False\n", + "\n", + "with connection.cursor() as cursor:\n", + " id = 1\n", + " cursor.execute(\"select ClobCol from TestClobs where IntCol = :ic2\", [id])\n", + " c, = cursor.fetchone()\n", + "\n", + " print(\"CLOB length:\", len(c))\n", + " print(\"CLOB data:\", c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The same `defaults` setting will return BLOBs as buffers." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sample benchmark\n", + "A sample benchmark shows the performance benefit of querying using the direct String method compared with using the default locator stream method. \n", + "![LOB Benchmark](images/lob-benchmark.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Fetching Numbers as Decimals\n", + "\n", + "Oracle's NUMBER format is Decimal but Python uses floating point so there can be conversion artifacts.\n", + "\n", + "Documentation reference link: [Fetched Number Precision](https://python-oracledb.readthedocs.io/en/latest/user_guide/sql_execution.html#fetched-number-precision)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " cursor.execute(\"select 0.1 as d1 from dual\")\n", + " v, = cursor.fetchone()\n", + " print('Value =', v, '\\tValue * 3 =', v * 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Fetching as Decimal may be useful." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import decimal\n", + "\n", + "oracledb.defaults.fetch_decimals = True\n", + "\n", + "with connection.cursor() as cursor:\n", + " cursor.execute(\"select 0.1 as d2 from dual\")\n", + " v, = cursor.fetchone()\n", + " print('Value =', v, '\\tValue * 3 =', v * 3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multiple Values in \"WHERE ... IN\" Clauses\n", + "\n", + "Documentation reference link: [Binding Multiple Values to a SQL WHERE IN Clause](https://python-oracledb.readthedocs.io/en/latest/user_guide/bind.html#binding-multiple-values-to-a-sql-where-in-clause)\n", + "\n", + "### A fixed number of binds\n", + "\n", + "It's important to use bind variables for security. For small numbers of binds this is simple. One placeholder must be used for each value in the IN clause." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sql = \"\"\"select name\n", + " from SampleQueryTab\n", + " where id in (:id1, :id2, :id3, :id4, :id5)\n", + " order by id\"\"\"\n", + "\n", + "with connection.cursor() as cursor:\n", + " cursor.execute(sql,\n", + " id1=5, id2=7, id3=1, id4=3, id5=2)\n", + " for row, in cursor:\n", + " print(row) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If statements are re-executed but the number of values varies, then pass None (i.e. NULL) for 'missing' values. This lets the database execute the same statement text as before, which helps performance and scalability. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " cursor.execute(sql,\n", + " id1=2, id2=4, id3=1, id4=None, id5=None)\n", + " for row, in cursor:\n", + " print(row) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When using character data and the size of the values varies between statement execution, then using `setinputsizes()` with the column size or maximum expected input data size can help reduce the number of SQL 'versions' of the statement used by the database optimizer, although this is not often an issue and it's not uncommon to see SQL statements with tens or low hundreds of versions.\n", + "\n", + "For example with the query:\n", + "```\n", + "sql = \"\"\"select name\n", + " from SampleQueryTab\n", + " where name in (:n1, :n2, :n3, :n4, :n5)\n", + " order by id\"\"\"\n", + "```\n", + "the statement\n", + "```\n", + "cursor.setinputsizes(n1=20, n2=20, n3=20, n4=20, n5=20)\n", + "```\n", + "\n", + "indicates that each value bound will be a string of no more than 20 characters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sql = \"\"\"select name\n", + " from SampleQueryTab\n", + " where name in (:n1, :n2, :n3, :n4, :n5)\n", + " order by id\"\"\"\n", + "\n", + "with connection.cursor() as cursor:\n", + " cursor.setinputsizes(n1=20, n2=20, n3=20, n4=20, n5=20)\n", + " cursor.execute(sql,\n", + " n1='Anthony', n2='Barbie', n3='Bogus', n4=None, n5=None)\n", + " for row, in cursor:\n", + " print(row) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dynamically creating the statement\n", + "\n", + "If the number of bind values is not known, and the statement is never rexecuted, you can consider dynamically creating a statement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bind_values = [5, 7, 1, 3, 2, 6]\n", + "bind_names = [\":\" + str(i + 1) for i in range(len(bind_values))]\n", + "sql = \"\"\"select name from SampleQueryTab where id in (%s)\"\"\" % (\",\".join(bind_names))\n", + "print(sql, \"\\n\")\n", + "with connection.cursor() as cursor:\n", + " cursor.execute(sql, bind_values)\n", + " for row, in cursor:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Binding using an Oracle type \n", + "\n", + "One solution when matching a huge number of values is to use the SQL `table()` clause and an Oracle type. This works for up to 32K values." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type_obj = connection.gettype(\"SYS.ODCINUMBERLIST\")\n", + "\n", + "with connection.cursor() as cursor:\n", + " obj = type_obj.newobject()\n", + " obj.extend([3, 4, 7])\n", + " cursor.execute(\"\"\"select name\n", + " from SampleQueryTab\n", + " where id in (select * from table(:1))\"\"\",\n", + " [obj])\n", + " for row, in cursor:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The presupplied types `SYS.ODCIVARCHAR2LIST` or `SYS.ODCIDATELIST` can similarly be used for VARCHAR2 and DATE, respectively." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Accessing object type information takes a few round-trips so the earlier methods are better for small numbers of values. If you use this solution in a more complex statement, check the optimizer plan is still OK. " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/python/python-oracledb/sample_notebooks/3-DML.ipynb b/python/python-oracledb/sample_notebooks/3-DML.ipynb new file mode 100644 index 00000000..71eb1f72 --- /dev/null +++ b/python/python-oracledb/sample_notebooks/3-DML.ipynb @@ -0,0 +1,316 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Banner](images/banner.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# DML - INSERT, UPDATE, DELETE, and MERGE Statements" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import oracledb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "un = \"pythondemo\"\n", + "pw = \"welcome\"\n", + "cs = \"localhost/orclpdb1\"\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cursor = connection.cursor()\n", + "cursor.execute(\"drop table mytab\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cursor.execute(\"create table mytab (id number, data varchar2(1000))\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Binding for Insertion\n", + "\n", + "Documentation reference link: [Using Bind Variables](https://python-oracledb.readthedocs.io/en/latest/user_guide/bind.html)\n", + "\n", + "Binding is very, very important. It:\n", + "- eliminates escaping special characters and helps prevent SQL injection attacks\n", + "- is important for performance and scalability" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " cursor.execute(\"truncate table mytab\")\n", + "\n", + " sql = \"insert into mytab (id, data) values (:idVal, :dataVal)\"\n", + "\n", + " # bind by position using a sequence (list or tuple)\n", + " cursor.execute(sql, [1, \"String 1\"])\n", + " cursor.execute(sql, (2, \"String 2\"))\n", + "\n", + " # bind by name using a dictionary\n", + " cursor = connection.cursor()\n", + " cursor.execute(sql, {\"idVal\": 3, \"dataVal\": \"String 3\"})\n", + "\n", + " # bind by name using keyword arguments\n", + " cursor.execute(sql, idVal=4, dataVal=\"String 4\")\n", + "\n", + " print(\"Done\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Batch execution - Inserting multiple rows with executemany()\n", + "\n", + "Documentation reference link: [Executing Batch Statements and Bulk Loading](https://python-oracledb.readthedocs.io/en/latest/user_guide/batch_statement.html)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " cursor.execute(\"truncate table mytab\")\n", + "\n", + " rows = [ (1, \"First\" ),\n", + " (2, \"Second\" ),\n", + " (3, \"Third\" ),\n", + " (4, \"Fourth\" ),\n", + " (5, \"Fifth\" ),\n", + " (6, \"Sixth\" ),\n", + " (7, \"Seventh\" ) ]\n", + "\n", + " # Using setinputsizes helps avoid memory reallocations.\n", + " # The parameters correspond to the insert columns. \n", + " # The value None says use python-oracledb's default size for a NUMBER column. \n", + " # The second value is the maximum input data (or column) width for the VARCHAR2 column\n", + " cursor.setinputsizes(None, 7)\n", + "\n", + " cursor.executemany(\"insert into mytab(id, data) values (:1, :2)\", rows)\n", + "\n", + " # Now query the results back\n", + "\n", + " for row in cursor.execute('select * from mytab'):\n", + " print(row)\n", + "\n", + " connection.rollback()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Benchmark - executemany() vs execute()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import time\n", + "\n", + "cursor = connection.cursor()\n", + "cursor.execute(\"truncate table mytab\")\n", + "\n", + "# Row counts to test inserting\n", + "numrows = (1, 5, 10, 100, 1000)\n", + "\n", + "longstring = \"x\" * 1000\n", + "\n", + "def create_data(n):\n", + " d = []\n", + " for i in range(n):\n", + " d.append((i, longstring))\n", + " return d\n", + "\n", + "ex = [] # seconds for execute() loop\n", + "em = [] # seconds for executemany()\n", + "\n", + "for n in numrows:\n", + " \n", + " rows = create_data(n)\n", + " \n", + " ############################################################\n", + " #\n", + " # Loop over each row\n", + " #\n", + "\n", + " start=time.time()\n", + "\n", + " for r in rows:\n", + " cursor.execute(\"insert into mytab(id, data) values (:1, :2)\", r) # <==== Loop over execute()\n", + " \n", + " elapsed = time.time() - start\n", + " ex.append(elapsed)\n", + " \n", + " r, = cursor.execute(\"select count(*) from mytab\").fetchone()\n", + " print(\"execute() loop {:6d} rows in {:06.4f} seconds\".format(r, elapsed)) \n", + " connection.rollback()\n", + " \n", + " ############################################################# \n", + " #\n", + " # Insert all rows in one call\n", + " #\n", + "\n", + " start = time.time()\n", + "\n", + " cursor.executemany(\"insert into mytab(id, data) values (:1, :2)\", rows) # <==== One executemany()\n", + " \n", + " elapsed = time.time() - start\n", + " em.append(elapsed)\n", + " \n", + " r, = cursor.execute(\"select count(*) from mytab\").fetchone()\n", + " print(\"executemany() {:6d} rows in {:06.4f} seconds\".format(r, elapsed)) \n", + " connection.rollback()\n", + "\n", + "\n", + "print(\"Plot is:\")\n", + "plt.xticks(numrows)\n", + "plt.plot(numrows, ex, label=\"execute() loop\", marker=\"o\")\n", + "plt.plot(numrows, em, label=\"one executemany()\", marker=\"o\")\n", + "plt.xscale(\"log\")\n", + "plt.xlabel('Number of rows')\n", + "plt.ylabel('Seconds')\n", + "plt.legend(loc=\"upper left\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Noisy Data - Batch Errors\n", + "\n", + "Dealing with bad data is easy with the `batcherrors` parameter." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# Initial data\n", + "\n", + "with connection.cursor() as cursor:\n", + "\n", + " for row in cursor.execute(\"select * from ParentTable order by ParentId\"):\n", + " print(row)\n", + "\n", + " for row in cursor.execute(\"select * from ChildTable order by ChildId\"):\n", + " print(row)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dataToInsert = [\n", + " (1016, 10, 'Child Red'),\n", + " (1018, 20, 'Child Blue'),\n", + " (1018, 30, 'Child Green'), # duplicate key\n", + " (1022, 40, 'Child Yellow'),\n", + " (1021, 75, 'Child Orange') # parent does not exist\n", + "]\n", + "\n", + "with connection.cursor() as cursor:\n", + " \n", + " cursor.executemany(\"insert into ChildTable values (:1, :2, :3)\", dataToInsert, batcherrors=True)\n", + " \n", + " print(\"\\nErrors in rows that were not inserted:\\n\")\n", + " for error in cursor.getbatcherrors():\n", + " print(\"Error\", error.message, \"at row offset\", error.offset) \n", + " \n", + " print(\"\\nRows that were successfully inserted:\\n\")\n", + " for row in cursor.execute(\"select * from ChildTable order by ChildId\"):\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you can choose whether or not to fix failed records and reinsert them.\n", + "You can then rollback or commit.\n", + "\n", + "This is true even if you had enabled autocommit mode - no commit will occur if there are batch errors." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "connection.rollback()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/python/python-oracledb/sample_notebooks/4-CSV.ipynb b/python/python-oracledb/sample_notebooks/4-CSV.ipynb new file mode 100644 index 00000000..458b8ae2 --- /dev/null +++ b/python/python-oracledb/sample_notebooks/4-CSV.ipynb @@ -0,0 +1,261 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "04c694cd", + "metadata": {}, + "source": [ + "![Banner](images/banner.png)" + ] + }, + { + "cell_type": "markdown", + "id": "b739ae65", + "metadata": {}, + "source": [ + "# Loading and Unloading Data: Working with Comma Separated Values (CSV) files" + ] + }, + { + "cell_type": "markdown", + "id": "2b027728", + "metadata": {}, + "source": [ + "CSV is not a well-defined standard! " + ] + }, + { + "cell_type": "markdown", + "id": "581e9486", + "metadata": {}, + "source": [ + "\"Unhelpful\" (that's a joke) suggestions for Python programmers:\n", + "- Don't use CSV files: Keep the data in the database.\n", + "- Don't use Excel - use Oracle APEX\n", + "- Use Oracle Data Pump to load CSV files into Oracle Database\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "6cb0a244", + "metadata": {}, + "source": [ + "Helpful suggestions:\n", + "- Python's [\"csv\" module](https://docs.python.org/3/library/csv.html) has extensive reading and writing support" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9cda404e", + "metadata": {}, + "outputs": [], + "source": [ + "import oracledb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d9c084a", + "metadata": {}, + "outputs": [], + "source": [ + "un = \"pythondemo\"\n", + "pw = \"welcome\"\n", + "cs = \"localhost/orclpdb1\"\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)" + ] + }, + { + "cell_type": "markdown", + "id": "3ae66a62", + "metadata": {}, + "source": [ + "## Reading CSV Files and Inserting Data into Oracle Database\n", + "\n", + "Set up the schema:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed1517dd", + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " try:\n", + " cursor.execute(\"drop table t\")\n", + " except:\n", + " ;\n", + "\n", + " cursor.execute(\"\"\"create table t (k number, \n", + " first_name varchar2(30), \n", + " last_name varchar2(30), \n", + " country varchar2(30))\"\"\")" + ] + }, + { + "cell_type": "markdown", + "id": "8dba72c1", + "metadata": {}, + "source": [ + "Data in the external CSV file looks like:\n", + "```\n", + "1,Fred,Nurke,UK\n", + "2,Henry,Crun,UK\n", + "```\n", + "\n", + "The Python csv module has extensive functionality. One sample is shown below. For python-oracledb users the important points are to use `executemany()` and send batches of rows to the database. Tuning in your environment will determine the best batch size." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bace97d8", + "metadata": {}, + "outputs": [], + "source": [ + "import csv\n", + "\n", + "# The batch size determines how many records are inserted at a time.\n", + "# Adjust the size to meet your memory and performance requirements.\n", + "batch_size = 10000\n", + "\n", + "with connection.cursor() as cursor:\n", + " \n", + " sql = \"insert into t (k, first_name, last_name, country) values (:1, :2, :3, :4)\"\n", + " \n", + " # Predefine memory areas to match the table definition (or max data) to avoid memory re-allocs\n", + " cursor.setinputsizes(None, 30, 30, 30)\n", + "\n", + " with open(\"csv/data1.csv\", \"r\") as csv_file:\n", + " csv_reader = csv.reader(csv_file, delimiter=',')\n", + " data = []\n", + " for line in csv_reader:\n", + " data.append((line[0], line[1], line[2], line[3])) # e.g [('1', 'Fred', 'Nurke', 'UK')]\n", + " if len(data) % batch_size == 0:\n", + " cursor.executemany(sql, data)\n", + " data = []\n", + " if data:\n", + " cursor.executemany(sql, data)\n", + " connection.commit()\n", + "\n", + "print(\"Done\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a0b4215", + "metadata": {}, + "outputs": [], + "source": [ + "# Check the results\n", + "\n", + "with connection.cursor() as cursor:\n", + " sql = \"select * from t order by k\"\n", + " for r in cursor.execute(sql):\n", + " print(r)" + ] + }, + { + "cell_type": "markdown", + "id": "7ff76bf5", + "metadata": {}, + "source": [ + "Tuning database features may also be beneficial. For example, disabling logging and/or indexes." + ] + }, + { + "cell_type": "markdown", + "id": "08f9bc4e", + "metadata": {}, + "source": [ + "## Writing CSV Files from Queried Data\n", + "\n", + "This example shows just one way to write CSV files. The important point for python-oracledb users is to tune `cursor.arraysize` for your data and network." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bbc9db48", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "\n", + "sql = \"select * from all_objects where rownum <= 10000\"\n", + "\n", + "with connection.cursor() as cursor:\n", + "\n", + " start = time.time()\n", + "\n", + " cursor.arraysize = 1000\n", + "\n", + " with open(\"testwrite.csv\", \"w\", encoding=\"utf-8\") as outputfile:\n", + " writer = csv.writer(outputfile, lineterminator=\"\\n\")\n", + " results = cursor.execute(sql)\n", + " writer.writerows(results)\n", + "\n", + " elapsed = time.time() - start\n", + " print(\"Writing CSV: 10000 rows in {:06.4f} seconds\".format(elapsed)) " + ] + }, + { + "cell_type": "markdown", + "id": "7b3ddf5e", + "metadata": {}, + "source": [ + "If you change the arraysize and rerun the cell, the time taken may vary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62a7ecfe", + "metadata": {}, + "outputs": [], + "source": [ + "# Confirm the number of lines in the output file is correct\n", + "\n", + "import os\n", + "\n", + "r = os.system(\"wc -l testwrite.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e812be8c-339e-4f14-9b5b-1ea5163cee93", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/python-oracledb/sample_notebooks/5-JSON.ipynb b/python/python-oracledb/sample_notebooks/5-JSON.ipynb new file mode 100644 index 00000000..09b31da9 --- /dev/null +++ b/python/python-oracledb/sample_notebooks/5-JSON.ipynb @@ -0,0 +1,206 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a9825ce4", + "metadata": {}, + "source": [ + "![Banner](images/banner.png)" + ] + }, + { + "cell_type": "markdown", + "id": "7b00c7cf", + "metadata": {}, + "source": [ + "# Working with JSON Data\n", + "\n", + "Documentation reference link: [Using JSON Data](https://python-oracledb.readthedocs.io/en/latest/user_guide/json_data_type.html)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4933ee95", + "metadata": {}, + "outputs": [], + "source": [ + "import oracledb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1e091ca8", + "metadata": {}, + "outputs": [], + "source": [ + "un = \"pythondemo\"\n", + "pw = \"welcome\"\n", + "cs = \"localhost/orclpdb1\"\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)" + ] + }, + { + "cell_type": "markdown", + "id": "a81ba26a", + "metadata": {}, + "source": [ + "### JSON Storage:\n", + "\n", + "- Oracle Database 12c introduced JSON stored as a LOB or VARCHAR2\n", + "\n", + "- Oracle Database 21c a new optimized native binary format and a dedicated JSON type\n", + "\n", + "**Careful coding is required for apps that run in a mixed version environment**\n", + "\n", + "The first JSON example assumes you are Oracle Database 21c or later.\n", + "\n", + "Setup the schema:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "caa04809", + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " try:\n", + " cursor.execute(\"drop table customers\")\n", + " except:\n", + " ;\n", + " cursor.execute(\"create table customers (k number, json_data json)\")" + ] + }, + { + "cell_type": "markdown", + "id": "a84dfaeb", + "metadata": {}, + "source": [ + "With Oracle Database 21c or later, you can bind Python objects directly to the JSON column:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e14826f4", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "\n", + "json_data = [\n", + " 2.78,\n", + " True,\n", + " 'Ocean Beach',\n", + " b'Some bytes',\n", + " {'keyA': 1, 'KeyB': 'Melbourne'},\n", + " datetime.date.today()\n", + "]\n", + "\n", + "with connection.cursor() as cursor:\n", + " cursor.setinputsizes(oracledb.DB_TYPE_JSON)\n", + " cursor.execute(\"insert into customers (k, json_data) values (1, :jbv)\", [json_data])\n", + " \n", + "print(\"Done\")" + ] + }, + { + "cell_type": "markdown", + "id": "c21d71e3", + "metadata": {}, + "source": [ + "Querying returns the JSON in a familiar Python data structure:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d84a494d", + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " for row, in cursor.execute(\"select c.json_data from customers c where k = 1\"):\n", + " print(row)\n", + " \n", + "# With Oracle Database 21c or later, this gives:\n", + "# [Decimal('2.78'), True, 'Ocean Beach', b'Some bytes', {'keyA': Decimal('1'), 'KeyB': 'Melbourne'}, datetime.datetime(2022, 3, 4, 0, 0)]" + ] + }, + { + "cell_type": "markdown", + "id": "e18fc3b4", + "metadata": {}, + "source": [ + "If you don't have a recent database, then you can still easily work with JSON. Store it using BLOB and work with JSON strings. The Python \"json\" package can be used with many Python types:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a54a7bca-c8d9-4f47-89a2-edaf6ada0a44", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "with connection.cursor() as cursor:\n", + " try:\n", + " cursor.execute(\"drop table customersblob\")\n", + " except:\n", + " ;\n", + " cursor.execute(\"\"\"create table customersblob (k number, \n", + " json_data blob check (json_data is json)) \n", + " lob (json_data) store as (cache)\"\"\")\n", + " \n", + "# INSERT\n", + "\n", + "with connection.cursor() as cursor:\n", + " data = json_data = [\n", + " 2.78,\n", + " True,\n", + " 'Ocean Beach',\n", + " {'keyA': 1, 'KeyB': 'Melbourne'},\n", + " ]\n", + " cursor.execute(\"insert into customersblob (k, json_data) values (2, :jbv)\", [json.dumps(data)])\n", + " \n", + "# FETCH\n", + "\n", + "# Allow the BLOB column to be automatically recognized as storing JSON and not as storing arbitrary binary data.\n", + "# Without this, you would need to use the Python json package on the returned row.\n", + "oracledb.__future__.old_json_col_as_obj = True\n", + "\n", + "with connection.cursor() as cursor:\n", + " for row, in cursor.execute(\"SELECT c.json_data FROM customersblob c where k = 2\"):\n", + " print(row)\n", + " \n", + "connection.rollback() " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/python-oracledb/sample_notebooks/6-PLSQL.ipynb b/python/python-oracledb/sample_notebooks/6-PLSQL.ipynb new file mode 100644 index 00000000..4a2b8174 --- /dev/null +++ b/python/python-oracledb/sample_notebooks/6-PLSQL.ipynb @@ -0,0 +1,293 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Banner](images/banner.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Calling PL/SQL\n", + "\n", + "Documentation reference link: [Executing PL/SQL](https://python-oracledb.readthedocs.io/en/latest/user_guide/plsql_execution.html)\n", + "\n", + "PL/SQL is a 'stored' procedural language that is stored and run inside the database itself. PL/SQL lets you capture business logic for reuse across all your applications. You can call stored procedures and functions easily from python-oracledb." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import oracledb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "un = \"pythondemo\"\n", + "pw = \"welcome\"\n", + "cs = \"localhost/orclpdb1\"\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## PL/SQL Procedures\n", + "\n", + "This shows the PL/SQL procedure `MYPROC` used in this demo:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " \n", + " cursor.execute(\"select dbms_metadata.get_ddl('PROCEDURE', 'MYPROC') from dual\")\n", + " ddl, = cursor.fetchone()\n", + " print(ddl.read())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use `callproc()` to call the procedure. Bind variables are passed by position:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + "\n", + " myinvar = 22\n", + " myoutvar = cursor.var(int) # allocate a 'variable' of integer type to hold the OUT bind parameter\n", + "\n", + " cursor.callproc('myproc', [myinvar, myoutvar])\n", + " print(myoutvar.getvalue())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also call PL/SQL procedures via an 'anonymous' PL/SQL block. This can be useful if you want to use named bind placeholders:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + "\n", + " myinvar = 33\n", + " myoutvar = cursor.var(int)\n", + "\n", + " cursor.execute(' begin myproc(:v1, :v2); end;', {\"v1\": myinvar, \"v2\": myoutvar})\n", + " print(myoutvar.getvalue())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## PL/SQL Functions\n", + "\n", + "This shows the PL/SQL function `MYFUNC` used in this demo:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " \n", + " cursor.execute(\"select dbms_metadata.get_ddl('FUNCTION', 'MYFUNC') from dual\")\n", + " ddl, = cursor.fetchone()\n", + " print(ddl.read())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use `callfunc()` to call the function. Bind variables are passed by position. The second argument to `callfunc()` is the type of the PL/SQL function return value. Here it is an integer:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + "\n", + " data = \"abc\"\n", + " id = 3\n", + " res = cursor.callfunc('myfunc', int, (data, id))\n", + " print(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to calling PL/SQL procedures, you can also invoke PL/SQL procedures via an anonymous block, and optionally used named bind placeholders:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + "\n", + " data = \"def\"\n", + " id = 4\n", + " ret = cursor.var(int)\n", + "\n", + " cursor.execute(' begin :ret := myfunc(:data, :id); end;', {\"ret\": ret, \"data\": data, \"id\": id})\n", + " print(ret.getvalue())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## REF CURSORS\n", + "\n", + "REF CURSORS let result sets be returned to python-oracledb, commonly from PL/SQL.\n", + "\n", + "Here is the PL/SQL procedure used in this example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + "\n", + " cursor.execute(\"\"\"select text from all_source \n", + " where name = 'MYREFCURSORPROC' and type = 'PROCEDURE' \n", + " order by line\"\"\")\n", + " rows = cursor.fetchall()\n", + " for r, in rows:\n", + " print(r, end=\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use `callproc()` as shown before to call the PL/SQL procedure. The `ref_cursor` variable needs to be defined as a cursor so it can hold the returned REF CURSOR. This second cursor is then simply iterated over exactly like a cursor for simple SELECT would be:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " ref_cursor = connection.cursor()\n", + "\n", + " cursor.callproc(\"myrefcursorproc\", (2, 6, ref_cursor))\n", + "\n", + " print(\"Rows between 2 and 6:\")\n", + " for row in ref_cursor:\n", + " print(row)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implicit Cursors\n", + "\n", + "Instead of binding a cursor to get a REF CURSOR, the `dbms_sql.return_result()` procedure can alternatively return a result set back which is fetched in python-oracledb using `getimplicitresults()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + "\n", + " cursor.execute(\"\"\"\n", + " declare\n", + " c1 sys_refcursor;\n", + " c2 sys_refcursor;\n", + " begin\n", + " open c1 for\n", + " select * from ParentTable;\n", + " dbms_sql.return_result(c1);\n", + "\n", + " open c2 for\n", + " select * from ChildTable;\n", + " dbms_sql.return_result(c2);\n", + " end;\"\"\")\n", + "\n", + " for resultSet in cursor.getimplicitresults():\n", + " print(\"Result Set:\")\n", + " for row in resultSet:\n", + " print(row)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/python/python-oracledb/sample_notebooks/7-Objects.ipynb b/python/python-oracledb/sample_notebooks/7-Objects.ipynb new file mode 100644 index 00000000..3570eb6a --- /dev/null +++ b/python/python-oracledb/sample_notebooks/7-Objects.ipynb @@ -0,0 +1,392 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![Banner](images/banner.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Oracle Database Objects and Collections\n", + "\n", + "Documentation reference link: [Fetching Oracle Database Objects and Collections](https://python-oracledb.readthedocs.io/en/latest/user_guide/sql_execution.html#fetching-oracle-database-objects-and-collections)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import oracledb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "un = \"pythondemo\"\n", + "pw = \"welcome\"\n", + "cs = \"localhost/orclpdb1\"\n", + "\n", + "connection = oracledb.connect(user=un, password=pw, dsn=cs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Binding Named Objects\n", + "\n", + "Create a demonstration table. This table uses the predefined SDO_GEOMETRY object which stores spatial information:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " try:\n", + " cursor.execute(\"drop table TestGeometry\")\n", + " except:\n", + " ;\n", + " \n", + " cursor.execute(\"\"\"create table TestGeometry (\n", + " IntCol number(9) not null,\n", + " Geometry sdo_geometry not null)\"\"\")\n", + " \n", + "print(\"Done\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using python-oracledb functions like `gettype()` and `extend()` you can create a Python representation of the database object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " \n", + " typeObj = connection.gettype(\"SDO_GEOMETRY\")\n", + " elementInfoTypeObj = connection.gettype(\"SDO_ELEM_INFO_ARRAY\")\n", + " ordinateTypeObj = connection.gettype(\"SDO_ORDINATE_ARRAY\")\n", + "\n", + " obj = typeObj() # Alternatively use 'obj = typeObj.newobject()''\n", + " obj.SDO_GTYPE = 2003\n", + " obj.SDO_ELEM_INFO = elementInfoTypeObj()\n", + " obj.SDO_ELEM_INFO.extend([1, 1003, 3])\n", + " obj.SDO_ORDINATES = ordinateTypeObj()\n", + " obj.SDO_ORDINATES.extend([1, 1, 5, 7])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calling `gettype()` requires multiple round-trips to the database, so avoid calling it unnecessarily." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The new object can be bound directly for insertion:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " cursor.execute(\"insert into TestGeometry values (1, :objbv)\", {\"objbv\": obj})\n", + " \n", + "print(\"Done\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And then fetched back:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " for (id, obj) in cursor.execute(\"select IntCol, Geometry from testgeometry\"):\n", + " print(id, obj)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Simple attribute access is easy:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " for (id, obj) in cursor.execute(\"select IntCol, Geometry from testgeometry\"):\n", + " print(\"SDO_GTYPE is\", obj.SDO_GTYPE)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " To display all attributes, create a helper function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Oracle Database object dumper\n", + "\n", + "def dumpobject(obj, prefix = \" \"):\n", + " if obj.type.iscollection:\n", + " print(prefix, \"[\")\n", + " for value in obj.aslist():\n", + " if isinstance(value, oracledb.Object):\n", + " dumpobject(value, prefix + \" \")\n", + " else:\n", + " print(prefix + \" \", repr(value))\n", + " print(prefix, \"]\")\n", + " else:\n", + " print(prefix, \"{\")\n", + " for attr in obj.type.attributes:\n", + " value = getattr(obj, attr.name)\n", + " if isinstance(value, oracledb.Object):\n", + " print(prefix + \" \" + attr.name + \" :\")\n", + " dumpobject(value, prefix + \" \")\n", + " else:\n", + " print(prefix + \" \" + attr.name + \" :\", repr(value))\n", + " print(prefix, \"}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the helper function shows the full object structure:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " for (id, obj) in cursor.execute(\"select IntCol, Geometry from testgeometry\"):\n", + " print(\"Id: \", id)\n", + " dumpobject(obj)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# PL/SQL Collections\n", + "\n", + "The sample schema uses PL/SQL collections" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cursor = connection.cursor()\n", + "\n", + "cursor.execute(\"select dbms_metadata.get_ddl('PACKAGE', 'PKG_DEMO') from dual\")\n", + "ddl, = cursor.fetchone()\n", + "print(ddl.read())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To get a collection, create a Python variable with the database object type:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "typeObj = connection.gettype(\"PKG_DEMO.UDT_STRINGLIST\")\n", + "obj = typeObj()\n", + "\n", + "# call the stored procedure which will populate the object\n", + "cursor = connection.cursor()\n", + "cursor.callproc(\"pkg_Demo.DemoCollectionOut\", (obj,))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To show the collection indexes and values:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ix = obj.first()\n", + "while ix is not None:\n", + " print(ix, \"->\", obj.getelement(ix))\n", + " ix = obj.next(ix)\n", + "print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Show the values as a simple list:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(obj.aslist())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Show the values as a simple dictionary:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(obj.asdict())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Binding PL/SQL Records\n", + "\n", + "Create a new Python object of the correct type and set attribute values:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "\n", + "typeObj = connection.gettype(\"PKG_DEMO.UDT_DEMORECORD\")\n", + "obj = typeObj()\n", + "\n", + "obj.NUMBERVALUE = 6\n", + "obj.STRINGVALUE = \"Test String\"\n", + "obj.DATEVALUE = datetime.datetime(2016, 5, 28)\n", + "obj.BOOLEANVALUE = False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the stored procedure which will modify the object:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with connection.cursor() as cursor:\n", + " cursor.callproc(\"pkg_Demo.DemoRecordsInOut\", (obj,))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Show the modified values:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"NUMBERVALUE ->\", obj.NUMBERVALUE)\n", + "print(\"STRINGVALUE ->\", obj.STRINGVALUE)\n", + "print(\"DATEVALUE ->\", obj.DATEVALUE)\n", + "print(\"BOOLEANVALUE ->\", obj.BOOLEANVALUE)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/python/python-oracledb/sample_notebooks/README.md b/python/python-oracledb/sample_notebooks/README.md new file mode 100644 index 00000000..dee07944 --- /dev/null +++ b/python/python-oracledb/sample_notebooks/README.md @@ -0,0 +1,82 @@ +# Python python-oracledb Notebooks + +This directory contains Jupyter notebooks showing best practices for using +python-oracledb. The notebooks cover: + +- Connecting +- Queries +- DML +- Data loading and unloading (CSV Files) +- JSON +- PL/SQL +- Objects + +Python-oracledb's default 'Thin' mode is used. + +Jupyter notebooks let you easily step through, modify, and execute Python code: + +![A screenshot of a notebook running in a browser](./images/jupyter-notebook-screenshot.png) + +# Setup + +An existing Oracle Database is required. The JSON demo assumes that Oracle +Database 21c or later is being used. + +### Install Python 3 + +See https://www.python.org/downloads/ + +### Install Jupyter + +See https://jupyter.org/install: + + python3 -m pip install notebook + +### Install the python-oracledb driver + + python3 -m pip install oracledb + +### Install some libraries used by the examples + + python3 -m pip install numpy matplotlib + +### Create the python-oracledb sample schema + +Clone the python-oracledb repository, for example in a terminal window: + + git clone https://github.com/oracle/python-oracledb.git + + cd python-oracledb/samples + +Review README.md and sample_env.py + +In the terminal, set desired credentials, for example: + + export PYO_SAMPLES_ADMIN_USER=system + export PYO_SAMPLES_ADMIN_PASSWORD=oracle + export PYO_SAMPLES_CONNECT_STRING=localhost/orclpdb1 + export PYO_SAMPLES_MAIN_USER=pythondemo + export PYO_SAMPLES_MAIN_PASSWORD=welcome + export PYO_SAMPLES_EDITION_USER=pythoneditions + export PYO_SAMPLES_EDITION_PASSWORD=welcome + export PYO_SAMPLES_EDITION_NAME=python_e1 + +Install the schema: + + python3 create_schema.py + +### Start Jupyter + + cd sample_notebooks + jupyter notebook + +If Jupyter is not in your path, you may need to find it on your computer and +invoke it with an absolute path, for example on macOS: + + $HOME/Library/Python/3.9/bin/jupyter notebook + +Load each notebook *.ipynb file and step through the cells. + +Before running the notebooks cells, edit the credentials and connect string +near the top of each notebook to match those used when installing the sample +schema. diff --git a/python/python-oracledb/sample_notebooks/csv/data1.csv b/python/python-oracledb/sample_notebooks/csv/data1.csv new file mode 100644 index 00000000..afa32dde --- /dev/null +++ b/python/python-oracledb/sample_notebooks/csv/data1.csv @@ -0,0 +1,2 @@ +1,Fred,Nurke,UK +2,Henry,Crun,UK diff --git a/python/python-oracledb/sample_notebooks/images/architecture-thin.png b/python/python-oracledb/sample_notebooks/images/architecture-thin.png new file mode 100644 index 00000000..52153129 Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/architecture-thin.png differ diff --git a/python/python-oracledb/sample_notebooks/images/banner.png b/python/python-oracledb/sample_notebooks/images/banner.png new file mode 100644 index 00000000..523ce17e Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/banner.png differ diff --git a/python/python-oracledb/sample_notebooks/images/drcp-architecture.png b/python/python-oracledb/sample_notebooks/images/drcp-architecture.png new file mode 100644 index 00000000..60271890 Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/drcp-architecture.png differ diff --git a/python/python-oracledb/sample_notebooks/images/drcp-comparison.png b/python/python-oracledb/sample_notebooks/images/drcp-comparison.png new file mode 100644 index 00000000..e2ddde43 Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/drcp-comparison.png differ diff --git a/python/python-oracledb/sample_notebooks/images/jupyter-notebook-screenshot.png b/python/python-oracledb/sample_notebooks/images/jupyter-notebook-screenshot.png new file mode 100644 index 00000000..46bfac1c Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/jupyter-notebook-screenshot.png differ diff --git a/python/python-oracledb/sample_notebooks/images/lob-benchmark.png b/python/python-oracledb/sample_notebooks/images/lob-benchmark.png new file mode 100644 index 00000000..ba760d32 Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/lob-benchmark.png differ diff --git a/python/python-oracledb/sample_notebooks/images/pooled-connection.png b/python/python-oracledb/sample_notebooks/images/pooled-connection.png new file mode 100644 index 00000000..4dffb185 Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/pooled-connection.png differ diff --git a/python/python-oracledb/sample_notebooks/images/roundtrip.png b/python/python-oracledb/sample_notebooks/images/roundtrip.png new file mode 100644 index 00000000..39fb4418 Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/roundtrip.png differ diff --git a/python/python-oracledb/sample_notebooks/images/standalone-connection.png b/python/python-oracledb/sample_notebooks/images/standalone-connection.png new file mode 100644 index 00000000..0c600704 Binary files /dev/null and b/python/python-oracledb/sample_notebooks/images/standalone-connection.png differ diff --git a/python/python-oracledb/scrollable_cursors.py b/python/python-oracledb/scrollable_cursors.py index a333e9f2..90699842 100644 --- a/python/python-oracledb/scrollable_cursors.py +++ b/python/python-oracledb/scrollable_cursors.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,15 +25,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # scrollable_cursors.py # # Demonstrates how to use scrollable cursors. These allow moving forward and # backward in the result set but incur additional overhead on the server to # retain this information. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -41,9 +41,11 @@ # this script is currently only supported in python-oracledb thick mode oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # show all of the rows available in the table with connection.cursor() as cursor: @@ -54,7 +56,7 @@ print() # create a scrollable cursor -with connection.cursor(scrollable = True) as cursor: +with connection.cursor(scrollable=True) as cursor: # set array size smaller than the default (100) to force scrolling by the # database; otherwise, scrolling occurs directly within the buffers cursor.arraysize = 3 @@ -62,20 +64,20 @@ # scroll to last row in the result set; the first parameter is not needed # and is ignored) - cursor.scroll(mode = "last") + cursor.scroll(mode="last") print("LAST ROW") print(cursor.fetchone()) print() # scroll to the first row in the result set; the first parameter not needed # and is ignored - cursor.scroll(mode = "first") + cursor.scroll(mode="first") print("FIRST ROW") print(cursor.fetchone()) print() # scroll to an absolute row number - cursor.scroll(5, mode = "absolute") + cursor.scroll(5, mode="absolute") print("ROW 5") print(cursor.fetchone()) print() diff --git a/python/python-oracledb/session_callback.py b/python/python-oracledb/session_callback.py index 70efd72a..43b221b9 100644 --- a/python/python-oracledb/session_callback.py +++ b/python/python-oracledb/session_callback.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # session_callback.py # # Demonstrates how to use a connection pool session callback written in @@ -54,7 +54,7 @@ # The application console output will show that queries are executed multiple # times for each session created, but the initialization function for each # session is invoked only once. -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import os import sys @@ -65,13 +65,14 @@ import sample_env # Port to listen on -port = int(os.environ.get('PORT', '8080')) +port = int(os.environ.get("PORT", "8080")) # determine whether to use python-oracledb thin mode or thick mode if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- + # init_session(): The session callback. The requested_tag parameter is not used # in this example. @@ -79,44 +80,54 @@ def init_session(conn, requested_tag): # Your session initialization code would be here. This example just # queries the session id to show that the callback is invoked once per # session. - for r, in conn.cursor().execute("SELECT SYS_CONTEXT('USERENV','SID') FROM DUAL"): + for (r,) in conn.cursor().execute( + "SELECT SYS_CONTEXT('USERENV','SID') FROM DUAL" + ): print(f"init_session() invoked for session {r}") + # start_pool(): starts the connection pool with a session callback defined def start_pool(): - - pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), - min=4, max=4, increment=0, - session_callback=init_session) + pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + min=4, + max=4, + increment=0, + session_callback=init_session, + ) return pool -#------------------------------------------------------------------------------ + +# ----------------------------------------------------------------------------- app = Flask(__name__) -@app.route('/') + +@app.route("/") def index(): with pool.acquire() as connection: with connection.cursor() as cursor: - sql = "SELECT CURRENT_TIMESTAMP, SYS_CONTEXT('USERENV','SID') FROM DUAL" + sql = """ + SELECT CURRENT_TIMESTAMP, SYS_CONTEXT('USERENV','SID') + FROM DUAL""" cursor.execute(sql) - t,s = cursor.fetchone() + t, s = cursor.fetchone() r = f"Query at time {t} used session {s}" print(r) return r -#------------------------------------------------------------------------------ -if __name__ == '__main__': +# ----------------------------------------------------------------------------- +if __name__ == "__main__": # Start a pool of connections pool = start_pool() m = f"\nTry loading http://127.0.0.1:{port}/ in a browser\n" - sys.modules['flask.cli'].show_server_banner = lambda *x: print(m) + sys.modules["flask.cli"].show_server_banner = lambda *x: print(m) # Start a webserver app.run(port=port) diff --git a/python/python-oracledb/session_callback_plsql.py b/python/python-oracledb/session_callback_plsql.py index 96fcfd8e..fe4f45df 100644 --- a/python/python-oracledb/session_callback_plsql.py +++ b/python/python-oracledb/session_callback_plsql.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # session_callback_plsql.py # # Demonstrates how to use a connection pool session callback written in @@ -38,7 +38,7 @@ # state. # # Also see session_callback.py and session_callback_tagging.py -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -47,11 +47,15 @@ oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) # create pool with session callback defined -pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), min=2, max=5, - increment=1, - session_callback="pkg_SessionCallback.TheCallback") +pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + min=2, + max=5, + increment=1, + session_callback="pkg_SessionCallback.TheCallback", +) # truncate table logging calls to PL/SQL session callback with pool.acquire() as conn: @@ -64,7 +68,7 @@ with pool.acquire() as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying a tag; since the session returned has no tag, @@ -74,7 +78,7 @@ with pool.acquire(tag="NLS_DATE_FORMAT=SIMPLE") as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying the same tag; since a session exists in the pool @@ -84,7 +88,7 @@ with pool.acquire(tag="NLS_DATE_FORMAT=SIMPLE") as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying a different tag; since no session exists in the @@ -95,7 +99,7 @@ with pool.acquire(tag="NLS_DATE_FORMAT=FULL;TIME_ZONE=UTC") as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying a different tag but also specifying that a @@ -104,20 +108,24 @@ # session state will be changed and the tag will be saved when the connection # is closed print("(4) acquire session with different tag but match any also specified") -with pool.acquire(tag="NLS_DATE_FORMAT=FULL;TIME_ZONE=MST", matchanytag=True) \ - as conn: +with pool.acquire( + tag="NLS_DATE_FORMAT=FULL;TIME_ZONE=MST", matchanytag=True +) as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session and display results from PL/SQL session logs with pool.acquire() as conn: cursor = conn.cursor() - cursor.execute(""" - select RequestedTag, ActualTag - from PLSQLSessionCallbacks - order by FixupTimestamp""") + cursor.execute( + """ + select RequestedTag, ActualTag + from PLSQLSessionCallbacks + order by FixupTimestamp + """ + ) print("(5) PL/SQL session callbacks") for requestedTag, actualTag in cursor: print("Requested:", requestedTag, "Actual:", actualTag) diff --git a/python/python-oracledb/session_callback_tagging.py b/python/python-oracledb/session_callback_tagging.py index ee4012c1..3e31d0fe 100644 --- a/python/python-oracledb/session_callback_tagging.py +++ b/python/python-oracledb/session_callback_tagging.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # session_callback_tagging.py # # Demonstrates how to use a connection pool session callback written in @@ -35,7 +35,7 @@ # session callback by removing the tagging logic. # # Also see session_callback.py and session_callback_plsql.py -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -45,28 +45,27 @@ # define a dictionary of NLS_DATE_FORMAT formats supported by this sample SUPPORTED_FORMATS = { - "SIMPLE" : "'YYYY-MM-DD HH24:MI'", - "FULL" : "'YYYY-MM-DD HH24:MI:SS'" + "SIMPLE": "'YYYY-MM-DD HH24:MI'", + "FULL": "'YYYY-MM-DD HH24:MI:SS'", } # define a dictionary of TIME_ZONE values supported by this sample -SUPPORTED_TIME_ZONES = { - "UTC" : "'UTC'", - "MST" : "'-07:00'" -} +SUPPORTED_TIME_ZONES = {"UTC": "'UTC'", "MST": "'-07:00'"} # define a dictionary of keys that are supported by this sample SUPPORTED_KEYS = { - "NLS_DATE_FORMAT" : SUPPORTED_FORMATS, - "TIME_ZONE" : SUPPORTED_TIME_ZONES + "NLS_DATE_FORMAT": SUPPORTED_FORMATS, + "TIME_ZONE": SUPPORTED_TIME_ZONES, } + # define session callback def init_session(conn, requested_tag): - # display the requested and actual tags - print("init_session(): requested tag=%r, actual tag=%r" % \ - (requested_tag, conn.tag)) + print( + "init_session(): requested tag=%r, actual tag=%r" + % (requested_tag, conn.tag) + ) # tags are expected to be in the form "key1=value1;key2=value2" # in this example, they are used to set NLS parameters and the tag is @@ -80,12 +79,15 @@ def init_session(conn, requested_tag): key, value = parts value_dict = SUPPORTED_KEYS.get(key) if value_dict is None: - raise ValueError("Tag only supports keys: %s" % \ - (", ".join(SUPPORTED_KEYS))) + raise ValueError( + "Tag only supports keys: %s" % (", ".join(SUPPORTED_KEYS)) + ) actual_value = value_dict.get(value) if actual_value is None: - raise ValueError("Key %s only supports values: %s" % \ - (key, ", ".join(value_dict))) + raise ValueError( + "Key %s only supports values: %s" + % (key, ", ".join(value_dict)) + ) state_parts.append("%s = %s" % (key, actual_value)) sql = "alter session set %s" % " ".join(state_parts) cursor = conn.cursor() @@ -98,10 +100,15 @@ def init_session(conn, requested_tag): # create pool with session callback defined -pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), min=2, max=5, - increment=1, session_callback=init_session) +pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + min=2, + max=5, + increment=1, + session_callback=init_session, +) # acquire session without specifying a tag; since the session returned is # newly created, the callback will be invoked but since there is no tag @@ -110,7 +117,7 @@ def init_session(conn, requested_tag): with pool.acquire() as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying a tag; since the session returned has no tag, @@ -120,7 +127,7 @@ def init_session(conn, requested_tag): with pool.acquire(tag="NLS_DATE_FORMAT=SIMPLE") as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying the same tag; since a session exists in the pool @@ -130,7 +137,7 @@ def init_session(conn, requested_tag): with pool.acquire(tag="NLS_DATE_FORMAT=SIMPLE") as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying a different tag; since no session exists in the @@ -141,7 +148,7 @@ def init_session(conn, requested_tag): with pool.acquire(tag="NLS_DATE_FORMAT=FULL;TIME_ZONE=UTC") as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) # acquire session, specifying a different tag but also specifying that a @@ -150,9 +157,10 @@ def init_session(conn, requested_tag): # session state will be changed and the tag will be saved when the connection # is closed print("(4) acquire session with different tag but match any also specified") -with pool.acquire(tag="NLS_DATE_FORMAT=FULL;TIME_ZONE=MST", matchanytag=True) \ - as conn: +with pool.acquire( + tag="NLS_DATE_FORMAT=FULL;TIME_ZONE=MST", matchanytag=True +) as conn: cursor = conn.cursor() cursor.execute("select to_char(current_date) from dual") - result, = cursor.fetchone() + (result,) = cursor.fetchone() print("main(): result is", repr(result)) diff --git a/python/python-oracledb/sharding_number_key.py b/python/python-oracledb/sharding_number_key.py index ef8ac9bf..67238bc0 100644 --- a/python/python-oracledb/sharding_number_key.py +++ b/python/python-oracledb/sharding_number_key.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2020, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # sharding_number_key.py # # Demonstrates how to use sharding keys with a sharded database. @@ -30,7 +30,7 @@ # sharded database must first be created. Information on how to create a # sharded database can be found in the documentation: # https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=SHARD -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -38,18 +38,24 @@ # this script is currently only supported in python-oracledb thick mode oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string(), min=1, max=5, - increment=1) +pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + min=1, + max=5, + increment=1, +) + def connect_and_display(sharding_key): print("Connecting with sharding key:", sharding_key) with pool.acquire(shardingkey=[sharding_key]) as conn: cursor = conn.cursor() cursor.execute("select sys_context('userenv', 'db_name') from dual") - name, = cursor.fetchone() + (name,) = cursor.fetchone() print("--> connected to database", name) + connect_and_display(100) connect_and_display(167) diff --git a/python/python-oracledb/soda_basic.py b/python/python-oracledb/soda_basic.py index 53f01fbb..3aa9f66d 100644 --- a/python/python-oracledb/soda_basic.py +++ b/python/python-oracledb/soda_basic.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2018, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2018, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # soda_basic.py # # A basic Simple Oracle Document Access (SODA) example. @@ -30,7 +30,7 @@ # Oracle Client must be at 18.3 or higher. # Oracle Database must be at 18.1 or higher. # The user must have been granted the SODA_APP privilege. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -38,9 +38,11 @@ # this script is currently only supported in python-oracledb thick mode oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # The general recommendation for simple SODA usage is to enable autocommit connection.autocommit = True @@ -57,23 +59,11 @@ # Explicit metadata is used for maximum version portability. # Refer to the documentation. metadata = { - "keyColumn": { - "name": "ID" - }, - "contentColumn": { - "name": "JSON_DOCUMENT", - "sqlType": "BLOB" - }, - "versionColumn": { - "name": "VERSION", - "method": "UUID" - }, - "lastModifiedColumn": { - "name": "LAST_MODIFIED" - }, - "creationTimeColumn": { - "name": "CREATED_ON" - } + "keyColumn": {"name": "ID"}, + "contentColumn": {"name": "JSON_DOCUMENT", "sqlType": "BLOB"}, + "versionColumn": {"name": "VERSION", "method": "UUID"}, + "lastModifiedColumn": {"name": "LAST_MODIFIED"}, + "creationTimeColumn": {"name": "CREATED_ON"}, } # Create a new SODA collection and index @@ -81,65 +71,59 @@ collection = soda.createCollection("mycollection", metadata) index_spec = { - 'name': 'CITY_IDX', - 'fields': [ - { - 'path': 'address.city', - 'datatype': 'string', - 'order': 'asc' - } - ] + "name": "CITY_IDX", + "fields": [{"path": "address.city", "datatype": "string", "order": "asc"}], } collection.createIndex(index_spec) # Insert a document. # A system generated key is created by default. -content = {'name': 'Matilda', 'address': {'city': 'Melbourne'}} +content = {"name": "Matilda", "address": {"city": "Melbourne"}} doc = collection.insertOneAndGet(content) key = doc.key -print('The key of the new SODA document is: ', key) +print("The key of the new SODA document is: ", key) # Fetch the document back -doc = collection.find().key(key).getOne() # A SodaDocument -content = doc.getContent() # A JavaScript object -print('Retrieved SODA document dictionary is:') +doc = collection.find().key(key).getOne() # A SodaDocument +content = doc.getContent() # A JavaScript object +print("Retrieved SODA document dictionary is:") print(content) -content = doc.getContentAsString() # A JSON string -print('Retrieved SODA document string is:') +content = doc.getContentAsString() # A JSON string +print("Retrieved SODA document string is:") print(content) # Replace document contents -content = {'name': 'Matilda', 'address': {'city': 'Sydney'}} +content = {"name": "Matilda", "address": {"city": "Sydney"}} collection.find().key(key).replaceOne(content) # Insert some more documents without caring about their keys -content = {'name': 'Venkat', 'address': {'city': 'Bengaluru'}} +content = {"name": "Venkat", "address": {"city": "Bengaluru"}} collection.insertOne(content) -content = {'name': 'May', 'address': {'city': 'London'}} +content = {"name": "May", "address": {"city": "London"}} collection.insertOne(content) -content = {'name': 'Sally-Ann', 'address': {'city': 'San Francisco'}} +content = {"name": "Sally-Ann", "address": {"city": "San Francisco"}} collection.insertOne(content) # Find all documents with names like 'Ma%' print("Names matching 'Ma%'") -documents = collection.find().filter({'name': {'$like': 'Ma%'}}).getDocuments() +documents = collection.find().filter({"name": {"$like": "Ma%"}}).getDocuments() for d in documents: content = d.getContent() print(content["name"]) # Count all documents c = collection.find().count() -print('Collection has', c, 'documents') +print("Collection has", c, "documents") # Remove documents with cities containing 'o' -print('Removing documents') -c = collection.find().filter({'address.city': {'$regex': '.*o.*'}}).remove() -print('Dropped', c, 'documents') +print("Removing documents") +c = collection.find().filter({"address.city": {"$regex": ".*o.*"}}).remove() +print("Dropped", c, "documents") # Count all documents c = collection.find().count() -print('Collection has', c, 'documents') +print("Collection has", c, "documents") # Drop the collection if collection.drop(): - print('Collection was dropped') + print("Collection was dropped") diff --git a/python/python-oracledb/soda_bulk_insert.py b/python/python-oracledb/soda_bulk_insert.py index e9774502..f7ee8b93 100644 --- a/python/python-oracledb/soda_bulk_insert.py +++ b/python/python-oracledb/soda_bulk_insert.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2019, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2019, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # soda_bulk_insert.py # # Demonstrates the use of SODA bulk insert. @@ -30,7 +30,7 @@ # Oracle Client must be at 18.5 or higher. # Oracle Database must be at 18.1 or higher. # The user must have been granted the SODA_APP privilege. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -38,9 +38,11 @@ # this script is currently only supported in python-oracledb thick mode oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) # the general recommendation for simple SODA usage is to enable autocommit connection.autocommit = True @@ -57,23 +59,11 @@ # Explicit metadata is used for maximum version portability. # Refer to the documentation. metadata = { - "keyColumn": { - "name": "ID" - }, - "contentColumn": { - "name": "JSON_DOCUMENT", - "sqlType": "BLOB" - }, - "versionColumn": { - "name": "VERSION", - "method": "UUID" - }, - "lastModifiedColumn": { - "name": "LAST_MODIFIED" - }, - "creationTimeColumn": { - "name": "CREATED_ON" - } + "keyColumn": {"name": "ID"}, + "contentColumn": {"name": "JSON_DOCUMENT", "sqlType": "BLOB"}, + "versionColumn": {"name": "VERSION", "method": "UUID"}, + "lastModifiedColumn": {"name": "LAST_MODIFIED"}, + "creationTimeColumn": {"name": "CREATED_ON"}, } # create a new (or open an existing) SODA collection @@ -89,7 +79,7 @@ dict(name="Bill", age=35), dict(name="Sally", age=43), dict(name="Jill", age=28), - dict(name="Cynthia", age=12) + dict(name="Cynthia", age=12), ] # perform bulk insert @@ -100,5 +90,5 @@ # perform search of all persons under the age of 40 print("Persons under the age of 40:") -for doc in collection.find().filter({'age': {'$lt': 40}}).getDocuments(): +for doc in collection.find().filter({"age": {"$lt": 40}}).getDocuments(): print(doc.getContent()["name"] + ",", "key", doc.key) diff --git a/python/python-oracledb/spatial_to_geopandas.py b/python/python-oracledb/spatial_to_geopandas.py index 16f5f577..d4e634ff 100644 --- a/python/python-oracledb/spatial_to_geopandas.py +++ b/python/python-oracledb/spatial_to_geopandas.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2018, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # spatial_to_geopandas.py # # GeoPandas is a popular python library for working with geospatial data. @@ -41,7 +41,7 @@ # # This script requires GeoPandas and its dependencies (see # https://geopandas.org/en/stable/getting_started/install.html). -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- from shapely.wkb import loads import geopandas as gpd @@ -54,9 +54,11 @@ oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) # create Oracle connection and cursor objects -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) cursor = connection.cursor() @@ -71,25 +73,32 @@ # drop and create table print("Dropping and creating table...") -cursor.execute(""" - begin - execute immediate 'drop table TestStates'; - exception when others then - if sqlcode <> -942 then - raise; - end if; - end;""") -cursor.execute(""" - create table TestStates ( - state VARCHAR2(30) not null, - geometry SDO_GEOMETRY not null - )""") +cursor.execute( + """ + begin + execute immediate 'drop table TestStates'; + exception when others then + if sqlcode <> -942 then + raise; + end if; + end; + """ +) +cursor.execute( + """ + create table TestStates ( + state VARCHAR2(30) not null, + geometry SDO_GEOMETRY not null + ) + """ +) # acquire types used for creating SDO_GEOMETRY objects type_obj = connection.gettype("MDSYS.SDO_GEOMETRY") element_info_type_obj = connection.gettype("MDSYS.SDO_ELEM_INFO_ARRAY") ordinate_type_obj = connection.gettype("MDSYS.SDO_ORDINATE_ARRAY") + # define function for creating an SDO_GEOMETRY object def create_geometry_obj(*ordinates): geometry = type_obj.newobject() @@ -101,75 +110,404 @@ def create_geometry_obj(*ordinates): geometry.SDO_ORDINATES.extend(ordinates) return geometry + # create SDO_GEOMETRY objects for three adjacent states in the USA -geometry_nevada = create_geometry_obj(-114.052025, 37.103989, -114.049797, - 37.000423, -113.484375, 37, -112.898598, 37.000401,-112.539604, - 37.000683, -112, 37.000977, -111.412048, 37.001514, -111.133018, - 37.00079,-110.75, 37.003201, -110.5, 37.004265, -110.469505, 36.998001, - -110, 36.997967, -109.044571,36.999088, -109.045143, 37.375, - -109.042824, 37.484692, -109.040848, 37.881176, -109.041405,38.153027, - -109.041107, 38.1647, -109.059402, 38.275501, -109.059296, 38.5, - -109.058868, 38.719906,-109.051765, 39, -109.050095, 39.366699, - -109.050697, 39.4977, -109.050499, 39.6605, -109.050156,40.222694, - -109.047577, 40.653641, -109.0494, 41.000702, -109.2313, 41.002102, - -109.534233,40.998184, -110, 40.997398, -110.047768, 40.997696, -110.5, - 40.994801, -111.045982, 40.998013,-111.045815, 41.251774, -111.045097, - 41.579899, -111.045944, 42.001633, -111.506493, 41.999588,-112.108742, - 41.997677, -112.16317, 41.996784, -112.172562, 41.996643, -112.192184, - 42.001244,-113, 41.998314, -113.875, 41.988091, -114.040871, 41.993805, - -114.038803, 41.884899, -114.041306,41, -114.04586, 40.116997, - -114.046295, 39.906101, -114.046898, 39.542801, -114.049026, 38.67741, - -114.049339, 38.572968, -114.049095, 38.14864, -114.0476, - 37.80946,-114.05098, 37.746284, -114.051666, 37.604805, -114.052025, - 37.103989) -geometry_wyoming = create_geometry_obj(-111.045815, 41.251774, -111.045982, - 40.998013, -110.5, 40.994801, -110.047768, 40.997696, -110, 40.997398, - -109.534233, 40.998184, -109.2313, 41.002102, -109.0494, 41.000702, - -108.525368, 40.999634, -107.917793, 41.002071, -107.317177, 41.002956, - -106.857178, 41.002697, -106.455704, 41.002167, -106.320587, 40.999153, - -106.189987, 40.997604, -105.729874, 40.996906, -105.276604, 40.998188, - -104.942848, 40.998226, -104.625, 41.00145, -104.052742, 41.001423, - -104.051781, 41.39333, -104.052032, 41.564301, -104.052185, 41.697983, - -104.052109, 42.001736, -104.052277, 42.611626, -104.052643, 43.000614, - -104.054337, 43.47784, -104.054298, 43.503101, -104.055, 43.8535, - -104.054108, 44.141102, -104.054001, 44.180401, -104.055458, 44.570877, - -104.057205, 44.997444, -104.664658, 44.998631, -105.037872, 45.000359, - -105.088867, 45.000462, -105.912819, 45.000957, -105.927612, 44.99366, - -106.024239, 44.993591, -106.263, 44.993801, -107.054871, 44.996384, - -107.133545, 45.000141, -107.911095, 45.001343, -108.248672, 44.999504, - -108.620628, 45.000328, -109.082314, 44.999664, -109.102745, 45.005955, - -109.797951, 45.002247, -110.000771, 45.003502, -110.10936, 45.003967, - -110.198761, 44.99625, -110.286026, 44.99691, -110.361946, 45.000656, - -110.402176, 44.993874, -110.5, 44.992355, -110.704506, 44.99239, - -110.784241, 45.003021, -111.05442, 45.001392, -111.054558, 44.666336, - -111.048203, 44.474144, -111.046272, 43.983456, -111.044724, 43.501213, - -111.043846, 43.3158, -111.043381, 43.02013, -111.042786, 42.719578, - -111.045967, 42.513187, -111.045944, 42.001633, -111.045097, 41.579899, - -111.045815, 41.251774) -geometry_colorado = create_geometry_obj(-109.045143, 37.375, -109.044571, - 36.999088, -108.378571, 36.999516, -107.481133, 37, -107.420311, 37, - -106.876701, 37.00013, -106.869209, 36.992416, -106.475639, 36.993748, - -106.006058, 36.995327, -105.717834, 36.995823, -105.220055, 36.995144, - -105.154488, 36.995239, -105.028671, 36.992702, -104.407616, 36.993446, - -104.007324, 36.996216, -103.085617, 37.000244, -103.001709, 37.000084, - -102.986488, 36.998505, -102.759384, 37, -102.69767, 36.995132, - -102.041794, 36.993061, -102.041191, 37.389172, -102.04113, 37.644268, - -102.041695, 37.738529, -102.043938, 38.262466, -102.044113, 38.268803, - -102.04483, 38.615234, -102.044762, 38.697556, -102.046112, 39.047035, - -102.046707, 39.133144, -102.049301, 39.568176, -102.049347, 39.574062, - -102.051277, 40.00309, -102.051117, 40.34922, -102.051003, 40.440018, - -102.050873, 40.697556, -102.050835, 40.749596, -102.051155, 41.002384, - -102.620567, 41.002609, -102.652992, 41.002342, -103.382011, 41.00227, - -103.574036, 41.001736, -104.052742, 41.001423, -104.625, 41.00145, - -104.942848, 40.998226, -105.276604, 40.998188, -105.729874, 40.996906, - -106.189987, 40.997604, -106.320587, 40.999153, -106.455704, 41.002167, - -106.857178, 41.002697, -107.317177, 41.002956, -107.917793, 41.002071, - -108.525368, 40.999634, -109.0494, 41.000702, -109.047577, 40.653641, - -109.050156, 40.222694, -109.050499, 39.6605, -109.050697, 39.4977, - -109.050095, 39.366699, -109.051765, 39, -109.058868, 38.719906, - -109.059296, 38.5, -109.059402, 38.275501, -109.041107, 38.1647, - -109.041405, 38.153027, -109.040848, 37.881176, -109.042824, 37.484692, - -109.045143, 37.375) +geometry_nevada = create_geometry_obj( + -114.052025, + 37.103989, + -114.049797, + 37.000423, + -113.484375, + 37, + -112.898598, + 37.000401, + -112.539604, + 37.000683, + -112, + 37.000977, + -111.412048, + 37.001514, + -111.133018, + 37.00079, + -110.75, + 37.003201, + -110.5, + 37.004265, + -110.469505, + 36.998001, + -110, + 36.997967, + -109.044571, + 36.999088, + -109.045143, + 37.375, + -109.042824, + 37.484692, + -109.040848, + 37.881176, + -109.041405, + 38.153027, + -109.041107, + 38.1647, + -109.059402, + 38.275501, + -109.059296, + 38.5, + -109.058868, + 38.719906, + -109.051765, + 39, + -109.050095, + 39.366699, + -109.050697, + 39.4977, + -109.050499, + 39.6605, + -109.050156, + 40.222694, + -109.047577, + 40.653641, + -109.0494, + 41.000702, + -109.2313, + 41.002102, + -109.534233, + 40.998184, + -110, + 40.997398, + -110.047768, + 40.997696, + -110.5, + 40.994801, + -111.045982, + 40.998013, + -111.045815, + 41.251774, + -111.045097, + 41.579899, + -111.045944, + 42.001633, + -111.506493, + 41.999588, + -112.108742, + 41.997677, + -112.16317, + 41.996784, + -112.172562, + 41.996643, + -112.192184, + 42.001244, + -113, + 41.998314, + -113.875, + 41.988091, + -114.040871, + 41.993805, + -114.038803, + 41.884899, + -114.041306, + 41, + -114.04586, + 40.116997, + -114.046295, + 39.906101, + -114.046898, + 39.542801, + -114.049026, + 38.67741, + -114.049339, + 38.572968, + -114.049095, + 38.14864, + -114.0476, + 37.80946, + -114.05098, + 37.746284, + -114.051666, + 37.604805, + -114.052025, + 37.103989, +) +geometry_wyoming = create_geometry_obj( + -111.045815, + 41.251774, + -111.045982, + 40.998013, + -110.5, + 40.994801, + -110.047768, + 40.997696, + -110, + 40.997398, + -109.534233, + 40.998184, + -109.2313, + 41.002102, + -109.0494, + 41.000702, + -108.525368, + 40.999634, + -107.917793, + 41.002071, + -107.317177, + 41.002956, + -106.857178, + 41.002697, + -106.455704, + 41.002167, + -106.320587, + 40.999153, + -106.189987, + 40.997604, + -105.729874, + 40.996906, + -105.276604, + 40.998188, + -104.942848, + 40.998226, + -104.625, + 41.00145, + -104.052742, + 41.001423, + -104.051781, + 41.39333, + -104.052032, + 41.564301, + -104.052185, + 41.697983, + -104.052109, + 42.001736, + -104.052277, + 42.611626, + -104.052643, + 43.000614, + -104.054337, + 43.47784, + -104.054298, + 43.503101, + -104.055, + 43.8535, + -104.054108, + 44.141102, + -104.054001, + 44.180401, + -104.055458, + 44.570877, + -104.057205, + 44.997444, + -104.664658, + 44.998631, + -105.037872, + 45.000359, + -105.088867, + 45.000462, + -105.912819, + 45.000957, + -105.927612, + 44.99366, + -106.024239, + 44.993591, + -106.263, + 44.993801, + -107.054871, + 44.996384, + -107.133545, + 45.000141, + -107.911095, + 45.001343, + -108.248672, + 44.999504, + -108.620628, + 45.000328, + -109.082314, + 44.999664, + -109.102745, + 45.005955, + -109.797951, + 45.002247, + -110.000771, + 45.003502, + -110.10936, + 45.003967, + -110.198761, + 44.99625, + -110.286026, + 44.99691, + -110.361946, + 45.000656, + -110.402176, + 44.993874, + -110.5, + 44.992355, + -110.704506, + 44.99239, + -110.784241, + 45.003021, + -111.05442, + 45.001392, + -111.054558, + 44.666336, + -111.048203, + 44.474144, + -111.046272, + 43.983456, + -111.044724, + 43.501213, + -111.043846, + 43.3158, + -111.043381, + 43.02013, + -111.042786, + 42.719578, + -111.045967, + 42.513187, + -111.045944, + 42.001633, + -111.045097, + 41.579899, + -111.045815, + 41.251774, +) +geometry_colorado = create_geometry_obj( + -109.045143, + 37.375, + -109.044571, + 36.999088, + -108.378571, + 36.999516, + -107.481133, + 37, + -107.420311, + 37, + -106.876701, + 37.00013, + -106.869209, + 36.992416, + -106.475639, + 36.993748, + -106.006058, + 36.995327, + -105.717834, + 36.995823, + -105.220055, + 36.995144, + -105.154488, + 36.995239, + -105.028671, + 36.992702, + -104.407616, + 36.993446, + -104.007324, + 36.996216, + -103.085617, + 37.000244, + -103.001709, + 37.000084, + -102.986488, + 36.998505, + -102.759384, + 37, + -102.69767, + 36.995132, + -102.041794, + 36.993061, + -102.041191, + 37.389172, + -102.04113, + 37.644268, + -102.041695, + 37.738529, + -102.043938, + 38.262466, + -102.044113, + 38.268803, + -102.04483, + 38.615234, + -102.044762, + 38.697556, + -102.046112, + 39.047035, + -102.046707, + 39.133144, + -102.049301, + 39.568176, + -102.049347, + 39.574062, + -102.051277, + 40.00309, + -102.051117, + 40.34922, + -102.051003, + 40.440018, + -102.050873, + 40.697556, + -102.050835, + 40.749596, + -102.051155, + 41.002384, + -102.620567, + 41.002609, + -102.652992, + 41.002342, + -103.382011, + 41.00227, + -103.574036, + 41.001736, + -104.052742, + 41.001423, + -104.625, + 41.00145, + -104.942848, + 40.998226, + -105.276604, + 40.998188, + -105.729874, + 40.996906, + -106.189987, + 40.997604, + -106.320587, + 40.999153, + -106.455704, + 41.002167, + -106.857178, + 41.002697, + -107.317177, + 41.002956, + -107.917793, + 41.002071, + -108.525368, + 40.999634, + -109.0494, + 41.000702, + -109.047577, + 40.653641, + -109.050156, + 40.222694, + -109.050499, + 39.6605, + -109.050697, + 39.4977, + -109.050095, + 39.366699, + -109.051765, + 39, + -109.058868, + 38.719906, + -109.059296, + 38.5, + -109.059402, + 38.275501, + -109.041107, + 38.1647, + -109.041405, + 38.153027, + -109.040848, + 37.881176, + -109.042824, + 37.484692, + -109.045143, + 37.375, +) # Insert rows for test states. If we were analyzing these geometries in Oracle # we would also add Spatial metadata and indexes. However in this example we @@ -177,11 +515,11 @@ def create_geometry_obj(*ordinates): # will skip the metadata and indexes. print("Adding rows to table...") data = [ - ('Nevada', geometry_nevada), - ('Colorado', geometry_colorado), - ('Wyoming', geometry_wyoming) + ("Nevada", geometry_nevada), + ("Colorado", geometry_colorado), + ("Wyoming", geometry_wyoming), ] -cursor.executemany('insert into TestStates values (:state, :obj)', data) +cursor.executemany("insert into TestStates values (:state, :obj)", data) # We now have test geometries in Oracle Spatial (SDO_GEOMETRY) and will next # bring them back into Python to analyze with GeoPandas. GeoPandas is able to @@ -191,14 +529,14 @@ def create_geometry_obj(*ordinates): # to provide results in a format readily consumable by GeoPandas. These utility # functions were introduced in Oracle 10g. We use WKB here; however the same # process applies for WKT. -cursor.execute(""" - SELECT state, sdo_util.to_wkbgeometry(geometry) - FROM TestStates""") -gdf = gpd.GeoDataFrame(cursor.fetchall(), columns=['state', 'wkbgeometry']) +cursor.execute( + "SELECT state, sdo_util.to_wkbgeometry(geometry) FROM TestStates" +) +gdf = gpd.GeoDataFrame(cursor.fetchall(), columns=["state", "wkbgeometry"]) # create GeoSeries to replace the WKB geometry column -gdf['geometry'] = gpd.GeoSeries(gdf['wkbgeometry'].apply(lambda x: loads(x))) -del gdf['wkbgeometry'] +gdf["geometry"] = gpd.GeoSeries(gdf["wkbgeometry"].apply(lambda x: loads(x))) +del gdf["wkbgeometry"] # display the GeoDataFrame print() diff --git a/python/python-oracledb/sql/create_schema_21.sql b/python/python-oracledb/sql/create_schema_21.sql index e18bfe0c..0131f5f1 100644 --- a/python/python-oracledb/sql/create_schema_21.sql +++ b/python/python-oracledb/sql/create_schema_21.sql @@ -23,9 +23,12 @@ *---------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------- - * create_schema.sql + * create_schema_21.sql * - * Creates a table with the 21c JSON datatype + * Performs the actual work of creating and populating the schemas with the + * database objects used by the python-oracledb samples that require Oracle + * Database 21c or higher. It is executed by the Python script + * create_schema.py. *---------------------------------------------------------------------------*/ create table &main_user..CustomersAsJson ( diff --git a/python/python-oracledb/sqlp.py b/python/python-oracledb/sqlp.py index 75b86f8e..6b2933b8 100755 --- a/python/python-oracledb/sqlp.py +++ b/python/python-oracledb/sqlp.py @@ -1,7 +1,7 @@ #! /usr/bin/env python -#------------------------------------------------------------------------------ -# Copyright (c) 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2022, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -22,9 +22,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # sqlp.py # # USAGE @@ -42,50 +42,53 @@ # - Statements like "CREATE OR REPLACE" must have all keywords on the same # (first) line # - it has very limited error handling -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import getpass import sys import re -import os import signal # Statement types STMT_TYPE_UNKNOWN = 0 -STMT_TYPE_SQL = 1 # Like SELECT or INSERT -STMT_TYPE_PLSQL = 2 # Like BEGIN or CREATE FUNCTION -STMT_TYPE_SQLPLUS = 3 # Like SET or DESC +STMT_TYPE_SQL = 1 # Like SELECT or INSERT +STMT_TYPE_PLSQL = 2 # Like BEGIN or CREATE FUNCTION +STMT_TYPE_SQLPLUS = 3 # Like SET or DESC # Simple regexps for statement type identification SQL_PATTERN = re.compile( - r'^(administer|alter|analyze|associate|audit|call|comment|commit|create' - '|delete|disassociate|drop|explain|flashback|grant|insert|lock|merge' - '|noaudit|purge|rename|revoke|rollback|savepoint|select|truncate|update' - '|with|set\s+constraint[s*]|set\s+role|set\s+transaction)(\s|$|;)', - re.IGNORECASE) + "^(administer|alter|analyze|associate|audit|call|comment|commit|create" + "|delete|disassociate|drop|explain|flashback|grant|insert|lock|merge" + "|noaudit|purge|rename|revoke|rollback|savepoint|select|truncate|update" + r"|with|set\s+constraint[s*]|set\s+role|set\s+transaction)(\s|$|;)", + re.IGNORECASE, +) PLSQL_PATTERN = re.compile( - r'^(begin|declare|create\s+or\s+replace|create\s+function' - '|create\s+procedure|create\s+package|create\s+type)(\s|$)', - re.IGNORECASE) + r"^(begin|declare|create\s+or\s+replace|create\s+function" + r"|create\s+procedure|create\s+package|create\s+type)(\s|$)", + re.IGNORECASE, +) SQLPLUS_PATTERN = re.compile( - r'^(@|@@|(acc(e?|ep?|ept?))|(a(p?|pp?|ppe?|ppen?|ppend?))|(archive\s+log)' - '|(attr(i?|ib?|ibu?|ibut?|ibute?))|(bre(a?|ak?))|(bti(t?|tl?|tle?))' - '|(c(h?|ha?|han?|hang?|hange?))|(cl(e?|ea?|ear?))|(col(u?|um?|umn?))' - '|(comp(u?|ut?|ute?))|(conn(e?|ec?|ect?))|copy|(def(i?|in?|ine?))|del' - '|(desc(r?|ri?|rib?|ribe?))|(disc(o?|on?|onn?|onne?|onnec?|onnect?))' - '|(ed(i?|it?))|(exec(u?|ut?|ute?))|exit|get|help|history|host' - '|(i(n?|np?|npu?|nput?))|(l(i?|is?|ist?))|(passw(o?|or?|ord?))' - '|(pau(s?|se?))|print|(pro(m?|mp?|mpt?))|quit|recover|(rem(a?|ar?|ark?))' - '|(repf(o?|oo?|oot?|oote?|ooter?))|(reph(e?|ea?|ead?|eade?|eader?))' - '|(r(u?|un?))|(sav(e?))|set|(sho(w?))|shutdown|(spo(o?|ol?))|(sta(r?|rt?))' - '|startup|store|(timi(n?|ng?))|(tti(t?|tl?|tle?))|(undef(i?|in?|ine?))' - '|(var(i?|ia?|iab?|iabl?|iable?))|whenever|xquery|--.*)(\s|$)', - re.IGNORECASE) - -QUERY_PATTERN = re.compile(r'(select|with)\s*', re.IGNORECASE) + r"^(@|@@|(acc(e?|ep?|ept?))|(a(p?|pp?|ppe?|ppen?|ppend?))|(archive\s+log)" + "|(attr(i?|ib?|ibu?|ibut?|ibute?))|(bre(a?|ak?))|(bti(t?|tl?|tle?))" + "|(c(h?|ha?|han?|hang?|hange?))|(cl(e?|ea?|ear?))|(col(u?|um?|umn?))" + "|(comp(u?|ut?|ute?))|(conn(e?|ec?|ect?))|copy|(def(i?|in?|ine?))|del" + "|(desc(r?|ri?|rib?|ribe?))|(disc(o?|on?|onn?|onne?|onnec?|onnect?))" + "|(ed(i?|it?))|(exec(u?|ut?|ute?))|exit|get|help|history|host" + "|(i(n?|np?|npu?|nput?))|(l(i?|is?|ist?))|(passw(o?|or?|ord?))" + "|(pau(s?|se?))|print|(pro(m?|mp?|mpt?))|quit|recover|(rem(a?|ar?|ark?))" + "|(repf(o?|oo?|oot?|oote?|ooter?))|(reph(e?|ea?|ead?|eade?|eader?))" + "|(r(u?|un?))|(sav(e?))|set|(sho(w?))|shutdown|(spo(o?|ol?))|(sta(r?|rt?))" + "|startup|store|(timi(n?|ng?))|(tti(t?|tl?|tle?))|(undef(i?|in?|ine?))" + r"|(var(i?|ia?|iab?|iabl?|iable?))|whenever|xquery|--.*)(\s|$)", + re.IGNORECASE, +) + +QUERY_PATTERN = re.compile(r"(select|with)\s*", re.IGNORECASE) + # Look up the first keywords to find the statement type def detect_statement_type(s): @@ -98,6 +101,7 @@ def detect_statement_type(s): else: return STMT_TYPE_UNKNOWN + # Read text until the expected end-of-statement terminator is seen. # # - SQL*Plus commands like SET or DESC can have an optional semi-colon @@ -110,10 +114,10 @@ def detect_statement_type(s): # new line. # def read_statement(): - statement = '' + statement = "" statement_type = STMT_TYPE_UNKNOWN line_number = 1 - print('SQLP> ', end='') + print("SQLP> ", end="") while True: try: line = input().strip() @@ -121,73 +125,89 @@ def read_statement(): sys.exit(0) line_number += 1 if len(line) == 0 and statement_type != STMT_TYPE_PLSQL: - statement = '' + statement = "" break if statement_type == STMT_TYPE_UNKNOWN: statement_type = detect_statement_type(line) if statement_type == STMT_TYPE_UNKNOWN: - return(line, STMT_TYPE_UNKNOWN) - elif (line == '/' - and (statement_type == STMT_TYPE_SQL - or statement_type == STMT_TYPE_PLSQL)): + return (line, STMT_TYPE_UNKNOWN) + elif line == "/" and ( + statement_type == STMT_TYPE_SQL + or statement_type == STMT_TYPE_PLSQL + ): break - elif ((statement_type == STMT_TYPE_SQL - or statement_type == STMT_TYPE_SQLPLUS) - and line[-1] == ';'): - statement = f'{statement} {line[:-1]}' if statement else line[:-1] + elif ( + statement_type == STMT_TYPE_SQL + or statement_type == STMT_TYPE_SQLPLUS + ) and line[-1] == ";": + statement = f"{statement} {line[:-1]}" if statement else line[:-1] break elif statement_type == STMT_TYPE_SQLPLUS: statement = line break else: - statement = f'{statement} {line}' if statement else line + statement = f"{statement} {line}" if statement else line - print('{0:3} '.format(line_number), end='') + print("{0:3} ".format(line_number), end="") + + return (statement, statement_type) - return(statement, statement_type) # Execute a statement that needs to be sent to the database def execute_db_statement(connection, statement, statement_type): if not connection: - print('Not connected') + print("Not connected") else: with connection.cursor() as cursor: try: cursor.execute(statement) - if (statement_type == STMT_TYPE_SQL - and QUERY_PATTERN.match(statement)): + if cursor.warning: + print(cursor.warning) + if statement_type == STMT_TYPE_SQL and QUERY_PATTERN.match( + statement + ): fetch_rows(cursor) + except oracledb.Error as e: - error, = e.args + (error,) = e.args print(statement) - print('*'.rjust(error.offset+1, ' ')) + print("*".rjust(error.offset + 1, " ")) print(error.message) + # Handle "local" SQL*Plus commands def execute_sqlplus_statement(connection, statement): - if re.match(r'(conn(e?|ec?|ect?))(\s|$)', statement): - a = re.split(r'\s+', statement) + if re.match(r"(conn(e?|ec?|ect?))(\s|$)", statement): + a = re.split(r"\s+", statement) dsn = None if len(a) <= 1 else a[1] connection = get_connection(dsn) - elif (statement.lower().strip() == 'exit' - or statement.lower().strip() == 'quit'): + elif ( + statement.lower().strip() == "exit" + or statement.lower().strip() == "quit" + ): sys.exit(0) - elif (re.match(r'(rem(a?|ar?|ark?))(\s|$)', statement) - or statement[:2] == '--'): + elif ( + re.match(r"(rem(a?|ar?|ark?))(\s|$)", statement) + or statement[:2] == "--" + ): return connection - #elif ... + # elif ... # This is where you can extend keyword support else: - print('Unsupported SQL*Plus command "{}"'. - format(re.split(r'\s+', statement)[0])) + print( + 'Unsupported SQL*Plus command "{}"'.format( + re.split(r"\s+", statement)[0] + ) + ) return connection + # Fetch and display query rows def fetch_rows(cursor): try: rows = cursor.fetchmany() if not rows: - print('no rows selected') + print("no rows selected") else: col_formats = get_col_formats(cursor.description) print_headings(col_formats) @@ -196,39 +216,43 @@ def fetch_rows(cursor): print_row(col_formats, row) rows = cursor.fetchmany() except oracledb.Error as e: - error, = e.args + (error,) = e.args print(error.message) + # Naive logic to choose column display widths def get_col_formats(description): col_formats = [] for col in description: - if col[2] == None: # no width, e.g. a LOB - w = len(col[0]) # use heading length + if col[2] is None: # no width, e.g. a LOB + w = len(col[0]) # use heading length elif col[1] == oracledb.DB_TYPE_NUMBER: w = max(40, len(col[0])) else: w = max(col[2], len(col[0])) - col_formats.append({'heading': col[0], 'type': col[1], 'width': w}) + col_formats.append({"heading": col[0], "type": col[1], "width": w}) return col_formats + # Print query column headings and separator def print_headings(col_formats): for col in col_formats: - print('{h:{w}s}'.format(h=col['heading'], w=col['width']), end=' ') + print("{h:{w}s}".format(h=col["heading"], w=col["width"]), end=" ") print() for col in col_formats: - print('-'.rjust(col['width'], '-'), end=' ') + print("-".rjust(col["width"], "-"), end=" ") print() + # Print a row of query data # No column wrapping occurs def print_row(col_formats, row): for i, v in enumerate(row): - v = ' ' if v == None else v - print('{v:{w}s}'.format(v=str(v), w=col_formats[i]['width']), end=' ') + v = " " if v is None else v + print("{v:{w}s}".format(v=str(v), w=col_formats[i]["width"]), end=" ") print() + # Connect def get_connection(dsn=None): connection = None @@ -242,34 +266,38 @@ def get_connection(dsn=None): if un and pw and cs: connection = oracledb.connect(user=un, password=pw, dsn=cs) else: - raise ValueError('Invalid credentials entered') + raise ValueError("Invalid credentials entered") except ValueError as e: print(e) except oracledb.Error as e: - error, = e.args - print('Failed to connect') + (error,) = e.args + print("Failed to connect") print(error.message) finally: return connection + # Signal handler for graceful interrupts def signal_handler(sig, frame): print() sys.exit(0) + # Connection helper functions def get_user(): - return input('Enter username: ').strip() + return input("Enter username: ").strip() + def get_password(): - return getpass.getpass('Enter password: ') + return getpass.getpass("Enter password: ") + def get_connect_string(): - return input('Enter connection string: ') + return input("Enter connection string: ") -# Main body -if __name__ == '__main__': +# Main body +if __name__ == "__main__": # Allow graceful interrupts signal.signal(signal.SIGINT, signal_handler) @@ -288,7 +316,9 @@ def get_connect_string(): if len(statement) == 0: continue elif statement_type == STMT_TYPE_UNKNOWN: - print('Unknown command "{}"'. format(re.split(r'\s+', statement)[0])) + print( + 'Unknown command "{}"'.format(re.split(r"\s+", statement)[0]) + ) elif statement_type == STMT_TYPE_SQLPLUS: connection = execute_sqlplus_statement(connection, statement) else: diff --git a/python/python-oracledb/subclassing.py b/python/python-oracledb/subclassing.py index 77053c71..4ebccbde 100644 --- a/python/python-oracledb/subclassing.py +++ b/python/python-oracledb/subclassing.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # This software is dual-licensed to you under the Universal Permissive License # (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License @@ -20,15 +20,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # subclassing.py # # Demonstrates how to subclass connections and cursors in order to add # additional functionality (like logging) or create specialized interfaces for # particular applications. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import oracledb import sample_env @@ -37,16 +37,19 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) + # sample subclassed Connection which overrides the constructor (so no # parameters are required) and the cursor() method (so that the subclassed # cursor is returned instead of the default cursor implementation) class Connection(oracledb.Connection): - def __init__(self): print("CONNECT", sample_env.get_connect_string()) - super().__init__(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) + super().__init__( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + def cursor(self): return Cursor(self) @@ -54,7 +57,6 @@ def cursor(self): # sample subclassed cursor which overrides the execute() and fetchone() # methods in order to perform some simple logging class Cursor(oracledb.Cursor): - def execute(self, statement, args): print("EXECUTE", statement) print("ARGS:") @@ -71,8 +73,9 @@ def fetchone(self): connection = Connection() with connection.cursor() as cursor: - # demonstrate that the subclassed connection and cursor are being used - cursor.execute("select count(*) from ChildTable where ParentId = :1", (30,)) - count, = cursor.fetchone() + cursor.execute( + "select count(*) from ChildTable where ParentId = :1", (30,) + ) + (count,) = cursor.fetchone() print("COUNT:", int(count)) diff --git a/python/python-oracledb/subclassing_async.py b/python/python-oracledb/subclassing_async.py new file mode 100644 index 00000000..94f2a7ef --- /dev/null +++ b/python/python-oracledb/subclassing_async.py @@ -0,0 +1,82 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# subclassing_async.py +# +# An asynchronous version of subclassing.py +# +# Demonstrates how to subclass connections and cursors in order to add +# additional functionality (like logging) or create specialized interfaces for +# particular applications. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + + +# sample subclassed Connection which overrides the constructor (so no +# parameters are required) and the cursor() method (so that the subclassed +# cursor is returned instead of the default cursor implementation) +class Connection(oracledb.AsyncConnection): + def cursor(self): + return Cursor(self) + + +# sample subclassed cursor which overrides the execute() and fetchone() +# methods in order to perform some simple logging +class Cursor(oracledb.AsyncCursor): + async def execute(self, statement, args): + print("EXECUTE", statement) + print("ARGS:") + for arg_index, arg in enumerate(args): + print(" ", arg_index + 1, "=>", repr(arg)) + return await super().execute(statement, args) + + async def fetchone(self): + print("FETCHONE") + return await super().fetchone() + + +async def main(): + # create instances of the subclassed Connection and cursor + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + conn_class=Connection, + ) + + with connection.cursor() as cursor: + # demonstrate that the subclassed connection and cursor are being used + await cursor.execute( + "select count(*) from ChildTable where ParentId = :1", (30,) + ) + (count,) = await cursor.fetchone() + print("COUNT:", int(count)) + + +asyncio.run(main()) diff --git a/python/python-oracledb/transaction_guard.py b/python/python-oracledb/transaction_guard.py index 880e4c68..a473ef8a 100644 --- a/python/python-oracledb/transaction_guard.py +++ b/python/python-oracledb/transaction_guard.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2016, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -25,9 +25,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # transaction_guard.py # # Demonstrates the use of Transaction Guard to verify if a transaction has @@ -50,7 +50,7 @@ # dbms_service.start_service('orcl-tg'); # end; # / -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import sys @@ -64,24 +64,27 @@ CONNECT_STRING = "localhost/orcl-tg" # create transaction and generate a recoverable error -pool = oracledb.create_pool(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=CONNECT_STRING, min=1, max=9, increment=2) +pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=CONNECT_STRING, + min=1, + max=9, + increment=2, +) connection = pool.acquire() cursor = connection.cursor() -cursor.execute(""" - delete from TestTempTable - where IntCol = 1""") -cursor.execute(""" - insert into TestTempTable - values (1, null)""") -input("Please kill %s session now. Press ENTER when complete." % \ - sample_env.get_main_user()) +cursor.execute("delete from TestTempTable where IntCol = 1") +cursor.execute("insert into TestTempTable values (1, null)") +input( + "Please kill %s session now. Press ENTER when complete." + % sample_env.get_main_user() +) try: - connection.commit() # this should fail + connection.commit() # this should fail sys.exit("Session was not killed. Terminating.") except oracledb.DatabaseError as e: - error_obj, = e.args + (error_obj,) = e.args if not error_obj.isrecoverable: sys.exit("Session is not recoverable. Terminating.") ltxid = connection.ltxid @@ -93,7 +96,8 @@ connection = pool.acquire() cursor = connection.cursor() args = (oracledb.Binary(ltxid), cursor.var(bool), cursor.var(bool)) -_, committed, completed = cursor.callproc("dbms_app_cont.get_ltxid_outcome", - args) +_, committed, completed = cursor.callproc( + "dbms_app_cont.get_ltxid_outcome", args +) print("Failed transaction was committed:", committed) print("Failed call was completed:", completed) diff --git a/python/python-oracledb/type_handlers_json_strings.py b/python/python-oracledb/type_handlers_json_strings.py index 9e495714..c5fb48cc 100644 --- a/python/python-oracledb/type_handlers_json_strings.py +++ b/python/python-oracledb/type_handlers_json_strings.py @@ -20,9 +20,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # type_handlers_json_strings.py # # Demonstrates the use of input and output type handlers as well as variable @@ -32,7 +32,7 @@ # This script differs from type_handlers_objects.py in that it shows the # binding and querying of JSON strings as Python objects for both # python-oracledb thin and thick mode. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import json @@ -43,8 +43,8 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -class Building: +class Building: def __init__(self, building_id, description, num_floors): self.building_id = building_id self.description = description @@ -55,9 +55,11 @@ def __repr__(self): def __eq__(self, other): if isinstance(other, Building): - return other.building_id == self.building_id \ - and other.description == self.description \ - and other.num_floors == self.num_floors + return ( + other.building_id == self.building_id + and other.description == self.description + and other.num_floors == self.num_floors + ) return NotImplemented def to_json(self): @@ -70,53 +72,62 @@ def from_json(cls, value): def building_in_converter(value): - return value.to_json() + return value.to_json() def input_type_handler(cursor, value, num_elements): if isinstance(value, Building): - return cursor.var(oracledb.STRING, arraysize=num_elements, - inconverter=building_in_converter) + return cursor.var( + oracledb.STRING, + arraysize=num_elements, + inconverter=building_in_converter, + ) def output_type_handler(cursor, metadata): if metadata.type_code is oracledb.STRING: - return cursor.var(metadata.type_code, arraysize=cursor.arraysize, - outconverter=Building.from_json) + return cursor.var( + metadata.type_code, + arraysize=cursor.arraysize, + outconverter=Building.from_json, + ) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) -with connection.cursor() as cursor: +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) +with connection.cursor() as cursor: buildings = [ Building(1, "The First Building", 5), Building(2, "The Second Building", 87), - Building(3, "The Third Building", 12) + Building(3, "The Third Building", 12), ] # Insert building data (python object) as a JSON string cursor.inputtypehandler = input_type_handler for building in buildings: - cursor.execute("insert into BuildingsAsJsonStrings values (:1, :2)", - (building.building_id, building)) + cursor.execute( + "insert into BuildingsAsJsonStrings values (:1, :2)", + (building.building_id, building), + ) # fetch the building data as a JSON string print("NO OUTPUT TYPE HANDLER:") - for row in cursor.execute(""" - select * from BuildingsAsJsonStrings - order by BuildingId"""): + for row in cursor.execute( + "select * from BuildingsAsJsonStrings order by BuildingId" + ): print(row) print() with connection.cursor() as cursor: - # fetch the building data as python objects cursor.outputtypehandler = output_type_handler print("WITH OUTPUT TYPE HANDLER:") - for row in cursor.execute(""" - select * from BuildingsAsJsonStrings - order by BuildingId"""): + for row in cursor.execute( + "select * from BuildingsAsJsonStrings order by BuildingId" + ): print(row) print() diff --git a/python/python-oracledb/type_handlers_json_strings_async.py b/python/python-oracledb/type_handlers_json_strings_async.py new file mode 100644 index 00000000..e0788900 --- /dev/null +++ b/python/python-oracledb/type_handlers_json_strings_async.py @@ -0,0 +1,135 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# type_handlers_json_strings_async.py +# +# An asynchronous version of type_handlers_json_strings.py +# +# Demonstrates the use of input and output type handlers as well as variable +# input and output converters. These methods can be used to extend +# python-oracledb in many ways. +# +# This script differs from type_handlers_objects.py in that it shows the +# binding and querying of JSON strings as Python objects for both +# python-oracledb thin and thick mode. +# ----------------------------------------------------------------------------- + +import asyncio +import json + +import oracledb +import sample_env + + +class Building: + def __init__(self, building_id, description, num_floors): + self.building_id = building_id + self.description = description + self.num_floors = num_floors + + def __repr__(self): + return "" % (self.building_id, self.description) + + def __eq__(self, other): + if isinstance(other, Building): + return ( + other.building_id == self.building_id + and other.description == self.description + and other.num_floors == self.num_floors + ) + return NotImplemented + + def to_json(self): + return json.dumps(self.__dict__) + + @classmethod + def from_json(cls, value): + result = json.loads(value) + return cls(**result) + + +def building_in_converter(value): + return value.to_json() + + +def input_type_handler(cursor, value, num_elements): + if isinstance(value, Building): + return cursor.var( + oracledb.STRING, + arraysize=num_elements, + inconverter=building_in_converter, + ) + + +def output_type_handler(cursor, metadata): + if metadata.type_code is oracledb.STRING: + return cursor.var( + metadata.type_code, + arraysize=cursor.arraysize, + outconverter=Building.from_json, + ) + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + buildings = [ + Building(1, "The First Building", 5), + Building(2, "The Second Building", 87), + Building(3, "The Third Building", 12), + ] + + # Insert building data (python object) as a JSON string + cursor.inputtypehandler = input_type_handler + for building in buildings: + await cursor.execute( + "insert into BuildingsAsJsonStrings values (:1, :2)", + (building.building_id, building), + ) + + # fetch the building data as a JSON string + query = "select * from BuildingsAsJsonStrings order by BuildingId" + print("NO OUTPUT TYPE HANDLER:") + await cursor.execute(query) + async for row in cursor: + print(row) + print() + + with connection.cursor() as cursor: + # fetch the building data as python objects + cursor.outputtypehandler = output_type_handler + print("WITH OUTPUT TYPE HANDLER:") + await cursor.execute(query) + async for row in cursor: + print(row) + print() + + +asyncio.run(main()) diff --git a/python/python-oracledb/type_handlers_objects.py b/python/python-oracledb/type_handlers_objects.py index 88eb9dca..1b5af01b 100644 --- a/python/python-oracledb/type_handlers_objects.py +++ b/python/python-oracledb/type_handlers_objects.py @@ -1,4 +1,4 @@ -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Copyright (c) 2016, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. @@ -25,16 +25,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # type_handlers_objects.py # # Demonstrates the use of input and output type handlers as well as variable # input and output converters. These methods can be used to extend # python-oracledb in many ways. This script demonstrates the binding and # querying of SQL objects as Python objects. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import datetime @@ -45,14 +45,16 @@ if not sample_env.get_is_thin(): oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) obj_type = connection.gettype("UDT_BUILDING") -class Building: +class Building: def __init__(self, building_id, description, num_floors, date_built): self.building_id = building_id self.description = description @@ -73,19 +75,26 @@ def building_in_converter(value): def building_out_converter(obj): - return Building(int(obj.BUILDINGID), obj.DESCRIPTION, int(obj.NUMFLOORS), - obj.DATEBUILT) + return Building( + int(obj.BUILDINGID), obj.DESCRIPTION, int(obj.NUMFLOORS), obj.DATEBUILT + ) def input_type_handler(cursor, value, num_elements): if isinstance(value, Building): - return cursor.var(obj_type, arraysize=num_elements, - inconverter=building_in_converter) + return cursor.var( + obj_type, arraysize=num_elements, inconverter=building_in_converter + ) + def output_type_handler(cursor, metadata): if metadata.type_code is oracledb.DB_TYPE_OBJECT: - return cursor.var(metadata.type, arraysize=cursor.arraysize, - outconverter=building_out_converter) + return cursor.var( + metadata.type, + arraysize=cursor.arraysize, + outconverter=building_out_converter, + ) + buildings = [ Building(1, "The First Building", 5, datetime.date(2007, 5, 18)), @@ -96,23 +105,31 @@ def output_type_handler(cursor, metadata): with connection.cursor() as cursor: cursor.inputtypehandler = input_type_handler for building in buildings: - cursor.execute("insert into BuildingsAsObjects values (:1, :2)", - (building.building_id, building)) + cursor.execute( + "insert into BuildingsAsObjects values (:1, :2)", + (building.building_id, building), + ) print("NO OUTPUT TYPE HANDLER:") - for row in cursor.execute(""" - select * - from BuildingsAsObjects - order by BuildingId"""): + for row in cursor.execute( + """ + select * + from BuildingsAsObjects + order by BuildingId + """ + ): print(row) print() with connection.cursor() as cursor: cursor.outputtypehandler = output_type_handler print("WITH OUTPUT TYPE HANDLER:") - for row in cursor.execute(""" - select * - from BuildingsAsObjects - order by BuildingId"""): + for row in cursor.execute( + """ + select * + from BuildingsAsObjects + order by BuildingId + """ + ): print(row) print() diff --git a/python/python-oracledb/type_handlers_objects_async.py b/python/python-oracledb/type_handlers_objects_async.py new file mode 100644 index 00000000..86924fd0 --- /dev/null +++ b/python/python-oracledb/type_handlers_objects_async.py @@ -0,0 +1,137 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# type_handlers_objects_async.py +# +# An asynchronous version of type_handlers_objects.py +# +# Demonstrates the use of input and output type handlers as well as variable +# input and output converters. These methods can be used to extend +# python-oracledb in many ways. This script demonstrates the binding and +# querying of SQL objects as Python objects. +# ----------------------------------------------------------------------------- + +import asyncio +import datetime + +import oracledb +import sample_env + + +class Building: + def __init__(self, building_id, description, num_floors, date_built): + self.building_id = building_id + self.description = description + self.num_floors = num_floors + self.date_built = date_built + + def __repr__(self): + return "" % (self.building_id, self.description) + + +buildings = [ + Building(1, "The First Building", 5, datetime.date(2007, 5, 18)), + Building(2, "The Second Building", 87, datetime.date(2010, 2, 7)), + Building(3, "The Third Building", 12, datetime.date(2005, 6, 19)), +] + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + obj_type = await connection.gettype("UDT_BUILDING") + + def building_in_converter(value): + obj = obj_type.newobject() + obj.BUILDINGID = value.building_id + obj.DESCRIPTION = value.description + obj.NUMFLOORS = value.num_floors + obj.DATEBUILT = value.date_built + return obj + + def building_out_converter(obj): + return Building( + int(obj.BUILDINGID), + obj.DESCRIPTION, + int(obj.NUMFLOORS), + obj.DATEBUILT, + ) + + def input_type_handler(cursor, value, num_elements): + if isinstance(value, Building): + return cursor.var( + obj_type, + arraysize=num_elements, + inconverter=building_in_converter, + ) + + def output_type_handler(cursor, metadata): + if metadata.type_code is oracledb.DB_TYPE_OBJECT: + return cursor.var( + metadata.type, + arraysize=cursor.arraysize, + outconverter=building_out_converter, + ) + + with connection.cursor() as cursor: + cursor.inputtypehandler = input_type_handler + for building in buildings: + await cursor.execute( + "insert into BuildingsAsObjects values (:1, :2)", + (building.building_id, building), + ) + + print("NO OUTPUT TYPE HANDLER:") + await cursor.execute( + """ + select * + from BuildingsAsObjects + order by BuildingId + """ + ) + async for row in cursor: + print(row) + print() + + with connection.cursor() as cursor: + cursor.outputtypehandler = output_type_handler + print("WITH OUTPUT TYPE HANDLER:") + await cursor.execute( + """ + select * + from BuildingsAsObjects + order by BuildingId + """ + ) + async for row in cursor: + print(row) + print() + + +asyncio.run(main()) diff --git a/python/python-oracledb/universal_rowids.py b/python/python-oracledb/universal_rowids.py index 5d74db78..f4c486c1 100644 --- a/python/python-oracledb/universal_rowids.py +++ b/python/python-oracledb/universal_rowids.py @@ -1,5 +1,5 @@ -#------------------------------------------------------------------------------ -# Copyright (c) 2017, 2022, Oracle and/or its affiliates. +# ----------------------------------------------------------------------------- +# Copyright (c) 2017, 2023, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -26,14 +26,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # universal_rowids.py # # Demonstrates the use of universal rowids. Universal rowids are used to # identify rows in index organized tables. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import datetime @@ -47,15 +47,16 @@ DATA = [ (1, "String #1", datetime.datetime(2017, 4, 4)), (2, "String #2", datetime.datetime(2017, 4, 5)), - (3, "A" * 250, datetime.datetime(2017, 4, 6)) + (3, "A" * 250, datetime.datetime(2017, 4, 6)), ] -connection = oracledb.connect(user=sample_env.get_main_user(), - password=sample_env.get_main_password(), - dsn=sample_env.get_connect_string()) +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), +) with connection.cursor() as cursor: - # truncate table so sample can be rerun print("Truncating table...") cursor.execute("truncate table TestUniversalRowids") @@ -64,8 +65,9 @@ print("Populating table...") for row in DATA: print("Inserting", row) - cursor.execute("insert into TestUniversalRowids values (:1, :2, :3)", - row) + cursor.execute( + "insert into TestUniversalRowids values (:1, :2, :3)", row + ) connection.commit() # fetch the rowids from the table @@ -76,11 +78,14 @@ for rowid in rowids: print("-" * 79) print("Rowid:", rowid) - cursor.execute(""" - select IntCol, StringCol, DateCol - from TestUniversalRowids - where rowid = :rid""", - {"rid": rowid}) + cursor.execute( + """ + select IntCol, StringCol, DateCol + from TestUniversalRowids + where rowid = :rid + """, + {"rid": rowid}, + ) int_col, string_col, dateCol = cursor.fetchone() print("IntCol:", int_col) print("StringCol:", string_col) diff --git a/python/python-oracledb/universal_rowids_async.py b/python/python-oracledb/universal_rowids_async.py new file mode 100644 index 00000000..7181519c --- /dev/null +++ b/python/python-oracledb/universal_rowids_async.py @@ -0,0 +1,90 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2023, Oracle and/or its affiliates. +# +# This software is dual-licensed to you under the Universal Permissive License +# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License +# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose +# either license. +# +# If you elect to accept the software under the Apache License, Version 2.0, +# the following applies: +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# universal_rowids_async.py +# +# An asynchronous version of universal_rowids.py +# +# Demonstrates the use of universal rowids. Universal rowids are used to +# identify rows in index organized tables. +# ----------------------------------------------------------------------------- + +import asyncio +import datetime + +import oracledb +import sample_env + +DATA = [ + (1, "String #1", datetime.datetime(2017, 4, 4)), + (2, "String #2", datetime.datetime(2017, 4, 5)), + (3, "A" * 250, datetime.datetime(2017, 4, 6)), +] + + +async def main(): + connection = await oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) + + with connection.cursor() as cursor: + # truncate table so sample can be rerun + print("Truncating table...") + await cursor.execute("truncate table TestUniversalRowids") + + # populate table with a few rows + print("Populating table...") + for row in DATA: + print("Inserting", row) + await cursor.execute( + "insert into TestUniversalRowids values (:1, :2, :3)", row + ) + await connection.commit() + + # fetch the rowids from the table + await cursor.execute("select rowid from TestUniversalRowids") + rowids = [r async for r, in cursor] + + # fetch each of the rows given the rowid + for rowid in rowids: + print("-" * 79) + print("Rowid:", rowid) + await cursor.execute( + """ + select IntCol, StringCol, DateCol + from TestUniversalRowids + where rowid = :rid + """, + {"rid": rowid}, + ) + int_col, string_col, dateCol = await cursor.fetchone() + print("IntCol:", int_col) + print("StringCol:", string_col) + print("DateCol:", dateCol) + + +asyncio.run(main())