Skip to content

Commit 12040fb

Browse files
Added support for asyncio (#6).
1 parent bce81f2 commit 12040fb

File tree

75 files changed

+15264
-3359
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+15264
-3359
lines changed

Diff for: doc/src/release_notes.rst

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ oracledb 2.0.0 (TBD)
1313
Thin Mode Changes
1414
+++++++++++++++++
1515

16+
#) Added support for asyncio
17+
(`issue 6 <https://github.com/oracle/python-oracledb/issues/6>`__).
1618
#) Added support for an Oracle Database 23c JSON feature allowing for field
1719
names with more than 255 UTF-8 encoded bytes.
1820
#) Added support for an Oracle Database 23c JSON feature improving JSON

Diff for: samples/array_dml_rowcounts_async.py

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# -----------------------------------------------------------------------------
2+
# Copyright (c) 2023, Oracle and/or its affiliates.
3+
#
4+
# This software is dual-licensed to you under the Universal Permissive License
5+
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
6+
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
7+
# either license.
8+
#
9+
# If you elect to accept the software under the Apache License, Version 2.0,
10+
# the following applies:
11+
#
12+
# Licensed under the Apache License, Version 2.0 (the "License");
13+
# you may not use this file except in compliance with the License.
14+
# You may obtain a copy of the License at
15+
#
16+
# https://www.apache.org/licenses/LICENSE-2.0
17+
#
18+
# Unless required by applicable law or agreed to in writing, software
19+
# distributed under the License is distributed on an "AS IS" BASIS,
20+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
# See the License for the specific language governing permissions and
22+
# limitations under the License.
23+
# -----------------------------------------------------------------------------
24+
25+
# -----------------------------------------------------------------------------
26+
# array_dml_rowcounts_async.py
27+
#
28+
# An asynchronous version of array_dml_rowcounts.py
29+
#
30+
# Demonstrates the use of the 12.1 feature that allows cursor.executemany()
31+
# to return the number of rows affected by each individual execution as a list.
32+
# The parameter "arraydmlrowcounts" must be set to True in the call to
33+
# cursor.executemany() after which cursor.getarraydmlrowcounts() can be called.
34+
# -----------------------------------------------------------------------------
35+
36+
import asyncio
37+
38+
import oracledb
39+
import sample_env
40+
41+
42+
async def main():
43+
connection = await oracledb.connect_async(
44+
user=sample_env.get_main_user(),
45+
password=sample_env.get_main_password(),
46+
dsn=sample_env.get_connect_string(),
47+
)
48+
49+
with connection.cursor() as cursor:
50+
# show the number of rows for each parent ID as a means of verifying
51+
# the output from the delete statement
52+
await cursor.execute(
53+
"""
54+
select ParentId, count(*)
55+
from ChildTable
56+
group by ParentId
57+
order by ParentId
58+
"""
59+
)
60+
async for parent_id, count in cursor:
61+
print("Parent ID:", parent_id, "has", int(count), "rows.")
62+
print()
63+
64+
# delete the following parent IDs only
65+
parent_ids_to_delete = [20, 30, 50]
66+
67+
print("Deleting Parent IDs:", parent_ids_to_delete)
68+
print()
69+
70+
# enable array DML row counts for each iteration executed in
71+
# executemany()
72+
await cursor.executemany(
73+
"delete from ChildTable where ParentId = :1",
74+
[(i,) for i in parent_ids_to_delete],
75+
arraydmlrowcounts=True,
76+
)
77+
78+
# display the number of rows deleted for each parent ID
79+
row_counts = cursor.getarraydmlrowcounts()
80+
for parent_id, count in zip(parent_ids_to_delete, row_counts):
81+
print("Parent ID:", parent_id, "deleted", count, "rows.")
82+
83+
84+
asyncio.run(main())

Diff for: samples/async_gather.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# -----------------------------------------------------------------------------
2+
# Copyright (c) 2023, Oracle and/or its affiliates.
3+
#
4+
# This software is dual-licensed to you under the Universal Permissive License
5+
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
6+
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
7+
# either license.
8+
#
9+
# If you elect to accept the software under the Apache License, Version 2.0,
10+
# the following applies:
11+
#
12+
# Licensed under the Apache License, Version 2.0 (the "License");
13+
# you may not use this file except in compliance with the License.
14+
# You may obtain a copy of the License at
15+
#
16+
# https://www.apache.org/licenses/LICENSE-2.0
17+
#
18+
# Unless required by applicable law or agreed to in writing, software
19+
# distributed under the License is distributed on an "AS IS" BASIS,
20+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
# See the License for the specific language governing permissions and
22+
# limitations under the License.
23+
# -----------------------------------------------------------------------------
24+
25+
# -----------------------------------------------------------------------------
26+
# async_gather.py
27+
#
28+
# Demonstrates using a connection pool with asyncio and gather().
29+
#
30+
# Multiple database sessions will be opened and used by each coroutine. The
31+
# number of connections opened can depend on the speed of your environment.
32+
# -----------------------------------------------------------------------------
33+
34+
import asyncio
35+
36+
import oracledb
37+
import sample_env
38+
39+
# Number of coroutines to run
40+
CONCURRENCY = 5
41+
42+
# Query the unique session identifier/serial number combination of a connection
43+
SQL = """SELECT UNIQUE CURRENT_TIMESTAMP AS CT, sid||'-'||serial# AS SIDSER
44+
FROM v$session_connect_info
45+
WHERE sid = SYS_CONTEXT('USERENV', 'SID')"""
46+
47+
48+
# Show the unique session identifier/serial number of each connection that the
49+
# pool opens
50+
async def init_session(connection, requested_tag):
51+
res = await connection.fetchone(SQL)
52+
print(res[0].strftime("%H:%M:%S.%f"), "- init_session SID-SERIAL#", res[1])
53+
54+
55+
# The coroutine simply shows the session identifier/serial number of the
56+
# conneciton returned by the pool.acquire() call
57+
async def query(pool):
58+
async with pool.acquire() as connection:
59+
await connection.callproc("dbms_session.sleep", [1])
60+
res = await connection.fetchone(SQL)
61+
print(res[0].strftime("%H:%M:%S.%f"), "- query SID-SERIAL#", res[1])
62+
63+
64+
async def main():
65+
pool = oracledb.create_pool_async(
66+
user=sample_env.get_main_user(),
67+
password=sample_env.get_main_password(),
68+
dsn=sample_env.get_connect_string(),
69+
min=1,
70+
max=CONCURRENCY,
71+
session_callback=init_session,
72+
)
73+
74+
coroutines = [query(pool) for i in range(CONCURRENCY)]
75+
76+
await asyncio.gather(*coroutines)
77+
78+
await pool.close()
79+
80+
81+
asyncio.run(main())

Diff for: samples/batch_errors_async.py

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# -----------------------------------------------------------------------------
2+
# Copyright (c) 2023, Oracle and/or its affiliates.
3+
#
4+
# This software is dual-licensed to you under the Universal Permissive License
5+
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
6+
# 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
7+
# either license.
8+
#
9+
# If you elect to accept the software under the Apache License, Version 2.0,
10+
# the following applies:
11+
#
12+
# Licensed under the Apache License, Version 2.0 (the "License");
13+
# you may not use this file except in compliance with the License.
14+
# You may obtain a copy of the License at
15+
#
16+
# https://www.apache.org/licenses/LICENSE-2.0
17+
#
18+
# Unless required by applicable law or agreed to in writing, software
19+
# distributed under the License is distributed on an "AS IS" BASIS,
20+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
# See the License for the specific language governing permissions and
22+
# limitations under the License.
23+
# -----------------------------------------------------------------------------
24+
25+
# -----------------------------------------------------------------------------
26+
# batch_errors_async.py
27+
#
28+
# An asynchronous version of batch_errors.py
29+
#
30+
# Demonstrates the use of the Oracle Database 12.1 feature that allows
31+
# cursor.executemany() to complete successfully, even if errors take
32+
# place during the execution of one or more of the individual
33+
# executions. The parameter "batcherrors" must be set to True in the
34+
# call to cursor.executemany() after which cursor.getbatcherrors() can
35+
# be called, which will return a list of error objects.
36+
# -----------------------------------------------------------------------------
37+
38+
import asyncio
39+
40+
import oracledb
41+
import sample_env
42+
43+
44+
async def main():
45+
connection = await oracledb.connect_async(
46+
user=sample_env.get_main_user(),
47+
password=sample_env.get_main_password(),
48+
dsn=sample_env.get_connect_string(),
49+
)
50+
51+
with connection.cursor() as cursor:
52+
# retrieve the number of rows in the table
53+
await cursor.execute("select count(*) from ChildTable")
54+
(count,) = await cursor.fetchone()
55+
print("Number of rows in child table:", int(count))
56+
57+
# define data to insert
58+
data_to_insert = [
59+
(1016, 10, "Child B of Parent 10"),
60+
(1017, 10, "Child C of Parent 10"),
61+
(1018, 20, "Child D of Parent 20"),
62+
(1018, 20, "Child D of Parent 20"), # duplicate key
63+
(1019, 30, "Child C of Parent 30"),
64+
(1020, 30, "Child D of Parent 40"),
65+
(1021, 60, "Child A of Parent 60"), # parent does not exist
66+
(1022, 40, "Child F of Parent 40"),
67+
]
68+
print("Number of rows to insert:", len(data_to_insert))
69+
70+
# old method: executemany() with data errors results in stoppage after
71+
# the first error takes place; the row count is updated to show how
72+
# many rows actually succeeded
73+
try:
74+
await cursor.executemany(
75+
"insert into ChildTable values (:1, :2, :3)", data_to_insert
76+
)
77+
except oracledb.DatabaseError as e:
78+
(error,) = e.args
79+
print("Failure with error:", error.message)
80+
print("Number of rows successfully inserted:", cursor.rowcount)
81+
82+
# demonstrate that the row count is accurate
83+
await cursor.execute("select count(*) from ChildTable")
84+
(count,) = await cursor.fetchone()
85+
print("Number of rows in child table after failed insert:", int(count))
86+
87+
# roll back so we can perform the same work using the new method
88+
await connection.rollback()
89+
90+
# new method: executemany() with batch errors enabled (and array DML
91+
# row counts also enabled) results in no immediate error being raised
92+
await cursor.executemany(
93+
"insert into ChildTable values (:1, :2, :3)",
94+
data_to_insert,
95+
batcherrors=True,
96+
arraydmlrowcounts=True,
97+
)
98+
99+
# display the errors that have taken place
100+
errors = cursor.getbatcherrors()
101+
print("Number of rows with bad values:", len(errors))
102+
for error in errors:
103+
print(
104+
"Error", error.message.rstrip(), "at row offset", error.offset
105+
)
106+
107+
# arraydmlrowcounts also shows rows with invalid data: they have a row
108+
# count of 0; otherwise 1 is shown
109+
row_counts = cursor.getarraydmlrowcounts()
110+
print("Array DML row counts:", row_counts)
111+
112+
# demonstrate that all of the rows without errors have been
113+
# successfully inserted
114+
await cursor.execute("select count(*) from ChildTable")
115+
(count,) = await cursor.fetchone()
116+
print(
117+
"Number of rows in child table after insert with batcherrors "
118+
"enabled:",
119+
int(count),
120+
)
121+
122+
123+
asyncio.run(main())

0 commit comments

Comments
 (0)