diff --git a/python/python-oracledb/create_schema.py b/python/python-oracledb/create_schema.py index d2bc0997..409b367e 100644 --- a/python/python-oracledb/create_schema.py +++ b/python/python-oracledb/create_schema.py @@ -54,7 +54,7 @@ sample_env.run_sql_script( conn, "create_schema_21", main_user=sample_env.get_main_user() ) -if sample_env.get_server_version() >= (23, 5): +if sample_env.get_server_version() >= (23, 7): sample_env.run_sql_script( conn, "create_schema_23", main_user=sample_env.get_main_user() ) diff --git a/python/python-oracledb/dataframe_numpy.py b/python/python-oracledb/dataframe_numpy.py new file mode 100644 index 00000000..8bc7a476 --- /dev/null +++ b/python/python-oracledb/dataframe_numpy.py @@ -0,0 +1,71 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dataframe_numpy.py +# +# Shows how to use connection.fetch_df_all() to efficiently put data into a +# NumPy ndarray via the DLPack standard memory layout. +# ----------------------------------------------------------------------------- + +import pyarrow +import numpy + +import oracledb +import sample_env + +# 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()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_connect_params(), +) + +SQL = "select id from SampleQueryTab order by id" + +# Get an OracleDataFrame +# Adjust arraysize to tune the query fetch performance +odf = connection.fetch_df_all(statement=SQL, arraysize=100) + +# Convert to an ndarray via the Python DLPack specification +pyarrow_array = pyarrow.array(odf.get_column_by_name("ID")) +np = numpy.from_dlpack(pyarrow_array) + +# If the array has nulls, an alternative is: +# np = pyarrow_array.to_numpy(zero_copy_only=False) + +print("Type:") +print(type(np)) # + +# Perform various numpy operations on the ndarray + +print("\nSum:") +print(numpy.sum(np)) + +print("\nLog10:") +print(numpy.log10(np)) diff --git a/python/python-oracledb/dataframe_pandas.py b/python/python-oracledb/dataframe_pandas.py new file mode 100644 index 00000000..f233703f --- /dev/null +++ b/python/python-oracledb/dataframe_pandas.py @@ -0,0 +1,102 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dataframe_pandas.py +# +# Shows how to use connection.fetch_df_all() and connection.fetch_df_batches() +# to create Pandas dataframes. +# ----------------------------------------------------------------------------- + +import pandas +import pyarrow + +import oracledb +import sample_env + +# 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()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_connect_params(), +) + +SQL = "select id, name from SampleQueryTab order by id" + +# ----------------------------------------------------------------------------- +# +# Fetching all records + +# Get an OracleDataFrame. +# Adjust arraysize to tune the query fetch performance +odf = connection.fetch_df_all(statement=SQL, arraysize=100) + +# Get a Pandas DataFrame from the data +df = pyarrow.Table.from_arrays( + odf.column_arrays(), names=odf.column_names() +).to_pandas() + +# Perform various Pandas operations on the DataFrame + +print("Columns:") +print(df.columns) + +print("\nDataframe description:") +print(df.describe()) + +print("\nLast three rows:") +print(df.tail(3)) + +print("\nTransform:") +print(df.T) + +# ----------------------------------------------------------------------------- +# +# Batch record fetching +# +# Note that since this particular example ends up with all query rows being +# held in memory, it would be more efficient to use fetch_df_all() as shown +# above. + +print("\nFetching in batches:") +df = pandas.DataFrame() + +# Tune 'size' for your data set. Here it is small to show the batch fetch +# behavior on the sample table. +for odf in connection.fetch_df_batches(statement=SQL, size=10): + df_b = pyarrow.Table.from_arrays( + odf.column_arrays(), names=odf.column_names() + ).to_pandas() + print(f"Appending {df_b.shape[0]} rows") + df = pandas.concat([df, df_b], ignore_index=True) + +r, c = df.shape +print(f"{r} rows, {c} columns") + +print("\nLast three rows:") +print(df.tail(3)) diff --git a/python/python-oracledb/dataframe_pandas_async.py b/python/python-oracledb/dataframe_pandas_async.py new file mode 100644 index 00000000..796da794 --- /dev/null +++ b/python/python-oracledb/dataframe_pandas_async.py @@ -0,0 +1,109 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dataframe_pandas_async.py +# +# An asynchronous version of dataframe_pandas.py +# +# Shows how to use AsyncConnection.fetch_df_all() and +# AsyncConnection.fetch_df_batches(). This example then creates Pandas +# dataframes. Alternative dataframe libraries could be used similar to the +# other, synchronous, data frame samples. +# ----------------------------------------------------------------------------- + +import asyncio + +import pandas +import pyarrow + +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(), + params=sample_env.get_connect_params(), + ) + + SQL = "select id, name from SampleQueryTab order by id" + + # ------------------------------------------------------------------------- + # + # Fetching all records + + # Get an OracleDataFrame. + # Adjust arraysize to tune the query fetch performance + odf = await connection.fetch_df_all(statement=SQL, arraysize=100) + + # Get a Pandas DataFrame from the data + df = pyarrow.Table.from_arrays( + odf.column_arrays(), names=odf.column_names() + ).to_pandas() + + # Perform various Pandas operations on the DataFrame + + print("Columns:") + print(df.columns) + + print("\nDataframe description:") + print(df.describe()) + + print("\nLast three rows:") + print(df.tail(3)) + + print("\nTransform:") + print(df.T) + + # ------------------------------------------------------------------------- + # + # Batch record fetching + # + # Note that since this particular example ends up with all query rows being + # held in memory, it would be more efficient to use fetch_df_all() as shown + # above. + + print("\nFetching in batches:") + df = pandas.DataFrame() + + # Tune 'size' for your data set. Here it is small to show the batch fetch + # behavior on the sample table. + async for odf in connection.fetch_df_batches(statement=SQL, size=10): + df_b = pyarrow.Table.from_arrays( + odf.column_arrays(), names=odf.column_names() + ).to_pandas() + print(f"Appending {df_b.shape[0]} rows") + df = pandas.concat([df, df_b], ignore_index=True) + + r, c = df.shape + print(f"{r} rows, {c} columns") + + print("\nLast three rows:") + print(df.tail(3)) + + +asyncio.run(main()) diff --git a/python/python-oracledb/dataframe_parquet_write.py b/python/python-oracledb/dataframe_parquet_write.py new file mode 100644 index 00000000..02a7d93f --- /dev/null +++ b/python/python-oracledb/dataframe_parquet_write.py @@ -0,0 +1,87 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dataframe_parquet_write.py +# +# Shows how to use connection.fetch_df_batches() to write files in Parquet +# format. +# ----------------------------------------------------------------------------- + +import os + +import pyarrow +import pyarrow.parquet as pq + +import oracledb +import sample_env + +# 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()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_connect_params(), +) + +PARQUET_FILE_NAME = "sample.parquet" + +if os.path.isfile(PARQUET_FILE_NAME): + os.remove(PARQUET_FILE_NAME) + +# Tune this for your query +FETCH_BATCH_SIZE = 10 + +SQL = "select id, name from SampleQueryTab order by id" +pqwriter = None + +for odf in connection.fetch_df_batches(statement=SQL, size=FETCH_BATCH_SIZE): + + pyarrow_table = pyarrow.Table.from_arrays( + arrays=odf.column_arrays(), names=odf.column_names() + ) + + if not pqwriter: + pqwriter = pq.ParquetWriter(PARQUET_FILE_NAME, pyarrow_table.schema) + + print(f"Writing a batch of {odf.num_rows()} rows") + pqwriter.write_table(pyarrow_table) + +pqwriter.close() + +# ----------------------------------------------------------------------------- +# Check the file was created + +print("\nParquet file metadata:") +print(pq.read_metadata(PARQUET_FILE_NAME)) + +# ----------------------------------------------------------------------------- +# Read the file + +print("\nParquet file data:") +t = pq.read_table(PARQUET_FILE_NAME, columns=["ID", "NAME"]) +print(t) diff --git a/python/python-oracledb/dataframe_polars.py b/python/python-oracledb/dataframe_polars.py new file mode 100644 index 00000000..7b91ced7 --- /dev/null +++ b/python/python-oracledb/dataframe_polars.py @@ -0,0 +1,96 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dataframe_polars.py +# +# Shows how to use connection.fetch_df_all() to efficiently put data into +# Polars DataFrames and Series. +# ----------------------------------------------------------------------------- + +import pyarrow +import polars + +import oracledb +import sample_env + +# 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()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_connect_params(), +) + +# ----------------------------------------------------------------------------- +# +# Polars DataFrame + +SQL1 = "select * from SampleQueryTab order by id" + +# Get an OracleDataFrame +# Adjust arraysize to tune the query fetch performance +odf = connection.fetch_df_all(statement=SQL1, arraysize=100) + +# Convert to a Polars DataFrame +pyarrow_table = pyarrow.Table.from_arrays( + odf.column_arrays(), names=odf.column_names() +) +p = polars.from_arrow(pyarrow_table) + +print(type(p)) # + +r, c = p.shape +print(f"{r} rows, {c} columns") + +print("\nSum:") +print(p.sum()) + +# ----------------------------------------------------------------------------- +# +# Polars Series + +SQL2 = "select id from SampleQueryTab order by id" + +# Get an OracleDataFrame +# Adjust arraysize to tune the query fetch performance +odf = connection.fetch_df_all(statement=SQL2, arraysize=100) + +# Convert to a Polars Series +pyarrow_array = pyarrow.array(odf.get_column_by_name("ID")) +p = polars.from_arrow(pyarrow_array) + +print(type(p)) # + +(r,) = p.shape +print(f"{r} rows") + +print("\nSum:") +print(p.sum()) + +print("\nLog10:") +print(p.log10()) diff --git a/python/python-oracledb/dataframe_pyarrow.py b/python/python-oracledb/dataframe_pyarrow.py new file mode 100644 index 00000000..d666f62b --- /dev/null +++ b/python/python-oracledb/dataframe_pyarrow.py @@ -0,0 +1,97 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dataframe_pyarrow.py +# +# Shows how to use connection.fetch_df_all() to create PyArrow tables and +# arrays. +# ----------------------------------------------------------------------------- + +import pyarrow + +import oracledb +import sample_env + +# 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()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_connect_params(), +) + +# ----------------------------------------------------------------------------- +# +# Creating a PyArrow table + +SQL1 = "select id, name from SampleQueryTab order by id" + +# Get an OracleDataFrame +# Adjust arraysize to tune the query fetch performance +odf = connection.fetch_df_all(statement=SQL1, arraysize=100) + +# Create a PyArrow table +pyarrow_table = pyarrow.Table.from_arrays( + arrays=odf.column_arrays(), names=odf.column_names() +) + +print("Type:") +print(type(pyarrow_table)) # + +# Perform various PyArrow operations + +print("\nColumn names:") +print(pyarrow_table.column_names) + +print("\nNumber of rows and columns:") +(r, c) = pyarrow_table.shape +print(f"{r} rows, {c} columns") + +# ----------------------------------------------------------------------------- +# +# Creating a PyArrow array + +SQL2 = "select id from SampleQueryTab order by id" + +# Get an OracleDataFrame +# Adjust arraysize to tune the query fetch performance +odf = connection.fetch_df_all(statement=SQL2, arraysize=100) + +# Create a PyArrow array +pyarrow_array = pyarrow.array(odf.get_column_by_name("ID")) + +print("Type:") +print(type(pyarrow_array)) # + +# Perform various PyArrow operations + +print("\nSum:") +print(pyarrow_array.sum()) + +print("\nFirst three elements:") +print(pyarrow_array.slice(0, 3)) diff --git a/python/python-oracledb/dataframe_torch.py b/python/python-oracledb/dataframe_torch.py new file mode 100644 index 00000000..e45d1940 --- /dev/null +++ b/python/python-oracledb/dataframe_torch.py @@ -0,0 +1,67 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# dataframe_torch.py +# +# Shows how to use connection.fetch_df_all() to efficiently put data into a +# Torch tensor via the DLPack standard memory layout. +# ----------------------------------------------------------------------------- + +import pyarrow +import torch + +import oracledb +import sample_env + +# 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()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_connect_params(), +) + +SQL = "select id from SampleQueryTab order by id" + +# Get an OracleDataFrame +# Adjust arraysize to tune the query fetch performance +odf = connection.fetch_df_all(statement=SQL, arraysize=100) + +# Convert to a Torch tensor via the Python DLPack specification +pyarrow_array = pyarrow.array(odf.get_column_by_name("ID")) +tt = torch.from_dlpack(pyarrow_array) + +print(type(tt)) # + +# Perform various Torch operations on the tensor + +print("\nSum:") +print(torch.sum(tt)) + +print("\nLog10:") +print(torch.log10(tt)) diff --git a/python/python-oracledb/multi_consumer_aq.py b/python/python-oracledb/multi_consumer_aq.py index c0744d0d..4fe62fa6 100644 --- a/python/python-oracledb/multi_consumer_aq.py +++ b/python/python-oracledb/multi_consumer_aq.py @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# Copyright (c) 2020, 2023, Oracle and/or its affiliates. +# Copyright (c) 2020, 2025, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -37,8 +37,9 @@ import oracledb import sample_env -# this script is currently only supported in python-oracledb thick mode -oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) +# 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()) QUEUE_NAME = "DEMO_RAW_QUEUE_MULTI" PAYLOAD_DATA = [ @@ -61,35 +62,32 @@ queue.deqoptions.navigation = oracledb.DEQ_FIRST_MSG # enqueue a few messages -with connection.cursor() as cursor: - print("Enqueuing messages...") - for data in PAYLOAD_DATA: - print(data) - queue.enqone(connection.msgproperties(payload=data)) - connection.commit() - print() +print("Enqueuing messages...") +for data in PAYLOAD_DATA: + print(data) + queue.enqone(connection.msgproperties(payload=data)) +connection.commit() +print() # dequeue the messages for consumer A -with connection.cursor() as cursor: - print("Dequeuing the messages for consumer A...") - queue.deqoptions.consumername = "SUBSCRIBER_A" - while True: - props = queue.deqone() - if not props: - break - print(props.payload.decode()) - connection.commit() - print() +print("Dequeuing the messages for consumer A...") +queue.deqoptions.consumername = "SUBSCRIBER_A" +while True: + props = queue.deqone() + if not props: + break + print(props.payload.decode()) +connection.commit() +print() # dequeue the message for consumer B -with connection.cursor() as cursor: - print("Dequeuing the messages for consumer B...") - queue.deqoptions.consumername = "SUBSCRIBER_B" - while True: - props = queue.deqone() - if not props: - break - print(props.payload.decode()) - connection.commit() +print("Dequeuing the messages for consumer B...") +queue.deqoptions.consumername = "SUBSCRIBER_B" +while True: + props = queue.deqone() + if not props: + break + print(props.payload.decode()) +connection.commit() print("\nDone.") diff --git a/python/python-oracledb/object_aq.py b/python/python-oracledb/object_aq.py index eaf0803c..233ba012 100644 --- a/python/python-oracledb/object_aq.py +++ b/python/python-oracledb/object_aq.py @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# Copyright (c) 2016, 2023, Oracle and/or its affiliates. +# Copyright (c) 2016, 2025, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -39,8 +39,9 @@ import oracledb import sample_env -# this script is currently only supported in python-oracledb thick mode -oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) +# 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()) BOOK_TYPE_NAME = "UDT_BOOK" QUEUE_NAME = "DEMO_BOOK_QUEUE" diff --git a/python/python-oracledb/pipelining_basic.py b/python/python-oracledb/pipelining_basic.py index 76bb1786..62be2609 100644 --- a/python/python-oracledb/pipelining_basic.py +++ b/python/python-oracledb/pipelining_basic.py @@ -66,24 +66,91 @@ async def main(): # pipeline.add_commit() # uncomment to persist data + pipeline.add_execute( + """create or replace procedure myprocerr as + begin + bogus; + end;""" + ) + + pipeline.add_execute( + """create or replace procedure myproc2 (p in number) as + begin + null; + end;""" + ) + + pipeline.add_execute( + """create or replace function myfunc (p in number) return number as + begin + return p; + end;""" + ) + + pipeline.add_callproc("myproc2", [123]) + + pipeline.add_callfunc("myfunc", oracledb.DB_TYPE_NUMBER, [456]) + + pipeline.add_fetchall("select 3 from does_not_exist") + pipeline.add_fetchall("select * from mytab") # Run the operations in the pipeline. # Note although the database receives all the operations at the same time, # it will execute each operation sequentially - results = await connection.run_pipeline(pipeline) + results = await connection.run_pipeline(pipeline, continue_on_error=True) # Print the query results for i, result in enumerate(results): - if result.rows: - statement = pipeline.operations[i].statement - print(f"\nRows from operation {i+1} '{statement}':\n") + statement = result.operation.statement + op_type = result.operation.op_type + + if result.warning: + print(f"\n-> OPERATION {i+1}: WARNING\n") + print(statement) + print(f"{result.warning}\n") + + elif result.error: + # This will only be invoked if the pipeline is run with + # continue_on_error=True + offset = result.error.offset + print(f"\n-> OPERATION {i+1}: ERROR AT POSITION {offset}:\n") + print(statement, "\n") + print(f"{result.error}\n") + + elif op_type == oracledb.PipelineOpType.EXECUTE: + print(f"\n-> OPERATION {i+1}: EXECUTE\n") + print(statement) + + elif op_type == oracledb.PipelineOpType.EXECUTE_MANY: + print(f"\n-> OPERATION {i+1}: EXECUTE_MANY\n") + print(statement) + + elif result.rows: + print(f"\n-> OPERATION {i+1}: ROWS\n") + print(statement, "\n") headings = [col.name for col in result.columns] print(*headings, sep="\t") print("--") for row in result.rows: print(*row, sep="\t") + elif op_type == oracledb.PipelineOpType.CALL_PROC: + print(f"\n-> OPERATION {i+1}: CALL_PROC\n") + print(result.operation.name) + + elif op_type == oracledb.PipelineOpType.CALL_FUNC: + print(f"\n-> OPERATION {i+1}: CALL_FUNC\n") + print(result.operation.name) + print(result.return_value) + + elif op_type == oracledb.PipelineOpType.COMMIT: + print(f"\n-> OPERATION {i+1}: COMMIT") + + else: + print(f"\n-> OPERATION {i+1}: Unknown\n") + print(f"Operation type: {op_type}") + # ------------------------------------------------------------ await connection.close() diff --git a/python/python-oracledb/pipelining_error.py b/python/python-oracledb/pipelining_error.py index 92190083..196ea8ae 100644 --- a/python/python-oracledb/pipelining_error.py +++ b/python/python-oracledb/pipelining_error.py @@ -76,7 +76,7 @@ async def main(): results = await connection.run_pipeline(pipeline, continue_on_error=True) for i, result in enumerate(results): - statement = pipeline.operations[i].statement + statement = result.operation.statement if result.warning: print( f"Warning {result.warning.full_code} " f"in operation {i+1}:\n" diff --git a/python/python-oracledb/plsql_batch.py b/python/python-oracledb/plsql_batch.py new file mode 100644 index 00000000..6cb0e8de --- /dev/null +++ b/python/python-oracledb/plsql_batch.py @@ -0,0 +1,193 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2024, 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_batch.py +# +# Demonstrates using executemany() to make repeated calls to a PL/SQL procedure +# +# Note in python-oracledb Thick mode, when cursor.executemany() is used for +# PL/SQL code that returns OUT binds, it will have the same performance +# characteristics as repeated calls to cursor.execute(). +# ----------------------------------------------------------------------------- + +import oracledb +import sample_env + +# 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()) + +connection = oracledb.connect( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_connect_params(), +) + +# ----------------------------------------------------------------------------- +# IN and OUT PL/SQL parameter examples +# Also shows passing in an object +# ----------------------------------------------------------------------------- + +# Setup + +with connection.cursor() as cursor: + stmts = [ + """create or replace type my_varchar_list as table of varchar2(100)""", + """create or replace procedure myproc + (p in number, names in my_varchar_list, count out number) as + begin + count := p + names.count; + end;""", + ] + for s in stmts: + cursor.execute(s) + if cursor.warning: + print(cursor.warning) + print(s) + +type_obj = connection.gettype("MY_VARCHAR_LIST") + +# Example 1: positional binds + +with connection.cursor() as cursor: + + obj1 = type_obj.newobject() + obj1.extend(["Alex", "Bobbie"]) + obj2 = type_obj.newobject() + obj2.extend(["Charlie", "Dave", "Eric"]) + obj3 = type_obj.newobject() + obj3.extend(["Fred", "Georgia", "Helen", "Ian"]) + + data = [ + (1, obj1), + (2, obj2), + (3, obj3), + ] + + count = cursor.var(oracledb.DB_TYPE_NUMBER, arraysize=len(data)) + cursor.setinputsizes(None, type_obj, count) + + cursor.executemany("begin myproc(:1, :2, :3); end;", data) + print(count.values) # [3, 5, 7] + +# Example 2: named binds + +with connection.cursor() as cursor: + + obj1 = type_obj.newobject() + obj1.extend(["Alex", "Bobbie"]) + obj2 = type_obj.newobject() + obj2.extend(["Charlie", "Dave", "Eric"]) + obj3 = type_obj.newobject() + obj3.extend(["Fred", "Georgia", "Helen", "Ian"]) + + data = [ + {"p": 100, "names": obj1}, + {"p": 200, "names": obj2}, + {"p": 300, "names": obj3}, + ] + + count = cursor.var(oracledb.DB_TYPE_NUMBER, arraysize=len(data)) + cursor.setinputsizes(p=None, names=type_obj, count=count) + + cursor.executemany("begin myproc(:p, :names, :count); end;", data) + print(count.values) # [102, 203, 304] + +# ----------------------------------------------------------------------------- +# IN/OUT PL/SQL parameter examples +# ----------------------------------------------------------------------------- + +# Setup + +with connection.cursor() as cursor: + stmt = """create or replace procedure myproc2 + (p1 in number, p2 in out varchar2) as + begin + p2 := p2 || ' ' || p1; + end;""" + cursor.execute(stmt) + if cursor.warning: + print(cursor.warning) + print(stmt) + +# Example 3: positional binds + +with connection.cursor() as cursor: + data = [(440, "Gregory"), (550, "Haley"), (660, "Ian")] + outvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + cursor.setinputsizes(None, outvals) + + cursor.executemany("begin myproc2(:1, :2); end;", data) + print(outvals.values) # ['Gregory 440', 'Haley 550', 'Ian 660'] + +# Example 4: positional binds, utilizing setvalue() + +with connection.cursor() as cursor: + data = [(777,), (888,), (999,)] + + inoutvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + inoutvals.setvalue(0, "Roger") + inoutvals.setvalue(1, "Sally") + inoutvals.setvalue(2, "Tom") + cursor.setinputsizes(None, inoutvals) + + cursor.executemany("begin myproc2(:1, :2); end;", data) + print(inoutvals.values) # ['Roger 777', 'Sally 888', 'Tom 999'] + +# Example 5: named binds + +with connection.cursor() as cursor: + data = [ + {"p1bv": 100, "p2bv": "Alfie"}, + {"p1bv": 200, "p2bv": "Brian"}, + {"p1bv": 300, "p2bv": "Cooper"}, + ] + outvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + cursor.setinputsizes(p1bv=None, p2bv=outvals) + + cursor.executemany("begin myproc2(:p1bv, :p2bv); end;", data) + print(outvals.values) # ['Alfie 100', 'Brian 200', 'Cooper 300'] + +# Example 6: named binds, utilizing setvalue() + +with connection.cursor() as cursor: + inoutvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + inoutvals.setvalue(0, "Dean") + inoutvals.setvalue(1, "Elsa") + inoutvals.setvalue(2, "Felix") + data = [{"p1bv": 101}, {"p1bv": 202}, {"p1bv": 303}] + cursor.setinputsizes(p1bv=None, p2bv=inoutvals) + + cursor.executemany("begin myproc2(:p1bv, :p2bv); end;", data) + print(inoutvals.values) # ['Dean 101', 'Elsa 202', 'Felix 303'] diff --git a/python/python-oracledb/plsql_batch_async.py b/python/python-oracledb/plsql_batch_async.py new file mode 100644 index 00000000..196d95cc --- /dev/null +++ b/python/python-oracledb/plsql_batch_async.py @@ -0,0 +1,201 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2024, 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_batch_async.py +# +# An asynchronous version of plsql_batch.py +# +# Demonstrates using executemany() to make repeated calls to a PL/SQL procedure +# ----------------------------------------------------------------------------- + +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(), + params=sample_env.get_connect_params(), + ) + + # ------------------------------------------------------------------------- + # IN and OUT PL/SQL parameter examples + # Also shows passing in an object + # ------------------------------------------------------------------------- + + # Setup + + pipeline = oracledb.create_pipeline() + + pipeline.add_execute( + """create or replace type my_varchar_list + as table of varchar2(100)""" + ) + + pipeline.add_execute( + """create or replace procedure myproc + (p in number, names in my_varchar_list, count out number) as + begin + count := p + names.count; + end;""" + ) + + await connection.run_pipeline(pipeline) + + # Example 1: positional binds + + type_obj = await connection.gettype("MY_VARCHAR_LIST") + + with connection.cursor() as cursor: + + obj1 = type_obj.newobject() + obj1.extend(["Alex", "Bobbie"]) + obj2 = type_obj.newobject() + obj2.extend(["Charlie", "Dave", "Eric"]) + obj3 = type_obj.newobject() + obj3.extend(["Fred", "Georgia", "Helen", "Ian"]) + + data = [ + (1, obj1), + (2, obj2), + (3, obj3), + ] + + count = cursor.var(oracledb.DB_TYPE_NUMBER, arraysize=len(data)) + cursor.setinputsizes(None, type_obj, count) + + await cursor.executemany("begin myproc(:1, :2, :3); end;", data) + print(count.values) # [3, 5, 7] + + # Example 2: named binds + + with connection.cursor() as cursor: + + obj1 = type_obj.newobject() + obj1.extend(["Alex", "Bobbie"]) + obj2 = type_obj.newobject() + obj2.extend(["Charlie", "Dave", "Eric"]) + obj3 = type_obj.newobject() + obj3.extend(["Fred", "Georgia", "Helen", "Ian"]) + + data = [ + {"p": 100, "names": obj1}, + {"p": 200, "names": obj2}, + {"p": 300, "names": obj3}, + ] + + count = cursor.var(oracledb.DB_TYPE_NUMBER, arraysize=len(data)) + cursor.setinputsizes(p=None, names=type_obj, count=count) + + await cursor.executemany( + "begin myproc(:p, :names, :count); end;", data + ) + + print(count.values) # [102, 203, 304] + + # ------------------------------------------------------------------------- + # IN/OUT PL/SQL parameter examples + # ------------------------------------------------------------------------- + + # Setup + + pipeline = oracledb.create_pipeline() + + pipeline.add_execute( + """create or replace procedure myproc2 + (p1 in number, p2 in out varchar2) as + begin + p2 := p2 || ' ' || p1; + end;""" + ) + + await connection.run_pipeline(pipeline) + + # Example 3: positional binds + + with connection.cursor() as cursor: + data = [(440, "Gregory"), (550, "Haley"), (660, "Ian")] + outvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + cursor.setinputsizes(None, outvals) + + await cursor.executemany("begin myproc2(:1, :2); end;", data) + print(outvals.values) # ['Gregory 440', 'Haley 550', 'Ian 660'] + + # Example 4: positional binds, utilizing setvalue() + + with connection.cursor() as cursor: + data = [(777,), (888,), (999,)] + + inoutvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + inoutvals.setvalue(0, "Roger") + inoutvals.setvalue(1, "Sally") + inoutvals.setvalue(2, "Tom") + cursor.setinputsizes(None, inoutvals) + + await cursor.executemany("begin myproc2(:1, :2); end;", data) + print(inoutvals.values) # ['Roger 777', 'Sally 888', 'Tom 999'] + + # Example 5: named binds + + with connection.cursor() as cursor: + data = [ + {"p1bv": 100, "p2bv": "Alfie"}, + {"p1bv": 200, "p2bv": "Brian"}, + {"p1bv": 300, "p2bv": "Cooper"}, + ] + outvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + cursor.setinputsizes(p1bv=None, p2bv=outvals) + + await cursor.executemany("begin myproc2(:p1bv, :p2bv); end;", data) + print(outvals.values) # ['Alfie 100', 'Brian 200', 'Cooper 300'] + + # Example 6: named binds, utilizing setvalue() + + with connection.cursor() as cursor: + inoutvals = cursor.var( + oracledb.DB_TYPE_VARCHAR, size=100, arraysize=len(data) + ) + inoutvals.setvalue(0, "Dean") + inoutvals.setvalue(1, "Elsa") + inoutvals.setvalue(2, "Felix") + data = [{"p1bv": 101}, {"p1bv": 202}, {"p1bv": 303}] + cursor.setinputsizes(p1bv=None, p2bv=inoutvals) + + await cursor.executemany("begin myproc2(:p1bv, :p2bv); end;", data) + print(inoutvals.values) # ['Dean 101', 'Elsa 202', 'Felix 303'] + + +asyncio.run(main()) diff --git a/python/python-oracledb/raw_aq.py b/python/python-oracledb/raw_aq.py index 526a00e7..67c96557 100644 --- a/python/python-oracledb/raw_aq.py +++ b/python/python-oracledb/raw_aq.py @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# Copyright (c) 2019, 2023, Oracle and/or its affiliates. +# Copyright (c) 2019, 2025, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -37,8 +37,9 @@ import oracledb import sample_env -# this script is currently only supported in python-oracledb thick mode -oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) +# 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()) QUEUE_NAME = "DEMO_RAW_QUEUE" PAYLOAD_DATA = [ @@ -55,31 +56,28 @@ ) # 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 +queue = connection.queue(QUEUE_NAME) +queue.deqoptions.wait = oracledb.DEQ_NO_WAIT +queue.deqoptions.navigation = oracledb.DEQ_FIRST_MSG - # dequeue all existing messages to ensure the queue is empty, just so that - # the results are consistent - while queue.deqone(): - pass +# dequeue all existing messages to ensure the queue is empty, just so that +# the results are consistent +while queue.deqone(): + pass # 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)) - connection.commit() +for data in PAYLOAD_DATA: + print(data) + queue.enqone(connection.msgproperties(payload=data)) +connection.commit() # dequeue the messages print("\nDequeuing messages...") -with connection.cursor() as cursor: - while True: - props = queue.deqone() - if not props: - break - print(props.payload.decode()) - connection.commit() - print("\nDone.") +while True: + props = queue.deqone() + if not props: + break + print(props.payload.decode()) +connection.commit() +print("\nDone.") diff --git a/python/python-oracledb/sql/create_schema.sql b/python/python-oracledb/sql/create_schema.sql index 7a7c816f..d160e984 100644 --- a/python/python-oracledb/sql/create_schema.sql +++ b/python/python-oracledb/sql/create_schema.sql @@ -391,6 +391,44 @@ insert into &main_user..SampleQueryTab values (6, 'Frankie') / insert into &main_user..SampleQueryTab values (7, 'Gerri') / +insert into &main_user..SampleQueryTab values (8, 'Harriet') +/ +insert into &main_user..SampleQueryTab values (9, 'Isabelle') +/ +insert into &main_user..SampleQueryTab values (10, 'Jarek') +/ +insert into &main_user..SampleQueryTab values (11, 'Krishna') +/ +insert into &main_user..SampleQueryTab values (12, 'Leo') +/ +insert into &main_user..SampleQueryTab values (13, 'Mia') +/ +insert into &main_user..SampleQueryTab values (14, 'Nathalie') +/ +insert into &main_user..SampleQueryTab values (15, 'Oscar') +/ +insert into &main_user..SampleQueryTab values (16, 'Pia') +/ +insert into &main_user..SampleQueryTab values (17, 'Quentin') +/ +insert into &main_user..SampleQueryTab values (18, 'Roger') +/ +insert into &main_user..SampleQueryTab values (19, 'Sally') +/ +insert into &main_user..SampleQueryTab values (20, 'Tully') +/ +insert into &main_user..SampleQueryTab values (21, 'Una') +/ +insert into &main_user..SampleQueryTab values (22, 'Valerie') +/ +insert into &main_user..SampleQueryTab values (23, 'William') +/ +insert into &main_user..SampleQueryTab values (24, 'Xavier') +/ +insert into &main_user..SampleQueryTab values (25, 'Yasmin') +/ +insert into &main_user..SampleQueryTab values (26, 'Zach') +/ commit / diff --git a/python/python-oracledb/sql/create_schema_23.sql b/python/python-oracledb/sql/create_schema_23.sql index e96e7f86..30daef5f 100644 --- a/python/python-oracledb/sql/create_schema_23.sql +++ b/python/python-oracledb/sql/create_schema_23.sql @@ -27,15 +27,16 @@ * * Performs the actual work of creating and populating the schemas with the * database objects used by the python-oracledb samples that require Oracle - * Database 23.5 or higher. It is executed by the Python script + * Database 23.7 or higher. It is executed by the Python script * create_schema.py. *---------------------------------------------------------------------------*/ create table &main_user..SampleVectorTab ( - v32 vector(3, float32), - v64 vector(3, float64), - v8 vector(3, int8), - vbin vector(24, binary) + v32 vector(3, float32), + v64 vector(3, float64), + v8 vector(3, int8), + vbin vector(24, binary), + v64sparse vector(30, float64, sparse) ) / diff --git a/python/python-oracledb/transaction_guard.py b/python/python-oracledb/transaction_guard.py index a473ef8a..a9f8fdf5 100644 --- a/python/python-oracledb/transaction_guard.py +++ b/python/python-oracledb/transaction_guard.py @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# Copyright (c) 2016, 2023, Oracle and/or its affiliates. +# Copyright (c) 2016, 2025, Oracle and/or its affiliates. # # Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. # @@ -57,8 +57,9 @@ import oracledb import sample_env -# this script is currently only supported in python-oracledb thick mode -oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client()) +# 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()) # constants CONNECT_STRING = "localhost/orcl-tg" @@ -76,15 +77,28 @@ 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() -) + +try: + sql = """select unique + 'alter system kill session '''||sid||','||serial#||''';' + from v$session_connect_info + where sid = sys_context('USERENV', 'SID')""" + (killsql,) = connection.cursor().execute(sql).fetchone() + print(f"Execute this SQL statement as a DBA user in SQL*Plus:\n {killsql}") +except Exception: + print( + "As a DBA user in SQL*Plus, use ALTER SYSTEM KILL SESSION " + f"to terminate the {sample_env.get_main_user()} session now." + ) + +input("Press ENTER when complete.") + try: connection.commit() # this should fail - sys.exit("Session was not killed. Terminating.") + sys.exit("Session was not killed. Sample cannot continue.") except oracledb.DatabaseError as e: (error_obj,) = e.args + print("Session is recoverable:", error_obj.isrecoverable) if not error_obj.isrecoverable: sys.exit("Session is not recoverable. Terminating.") ltxid = connection.ltxid diff --git a/python/python-oracledb/vector.py b/python/python-oracledb/vector.py index 49f90bbf..eb38af06 100644 --- a/python/python-oracledb/vector.py +++ b/python/python-oracledb/vector.py @@ -45,16 +45,21 @@ params=sample_env.get_connect_params(), ) -# this script only works with Oracle Database 23.5 or later -if sample_env.get_server_version() < (23, 5): - sys.exit("This example requires Oracle Database 23.5 or later.") +# this script only works with Oracle Database 23.7 or later +# +# The VECTOR datatype was initially introduced in Oracle Database 23.4. +# The BINARY vector format was introduced in Oracle Database 23.5. +# The SPARSE vector format was introduced in Oracle Database 23.7. + +if sample_env.get_server_version() < (23, 7): + sys.exit("This example requires Oracle Database 23.7 or later.") -# this script works with thin mode, or with thick mode using Oracle Client 23.5 +# this script works with thin mode, or with thick mode using Oracle Client 23.7 # or later -if not connection.thin and oracledb.clientversion()[:2] < (23, 5): +if not connection.thin and oracledb.clientversion()[:2] < (23, 7): sys.exit( "This example requires python-oracledb thin mode, or Oracle Client" - " 23.5 or later" + " 23.7 or later" ) with connection.cursor() as cursor: @@ -63,11 +68,20 @@ vector1_data_64 = array.array("d", [11.25, 11.75, 11.5]) vector1_data_8 = array.array("b", [1, 2, 3]) vector1_data_bin = array.array("B", [180, 150, 100]) + vector1_data_sparse64 = oracledb.SparseVector( + 30, [9, 16, 24], array.array("d", [19.125, 78.5, 977.375]) + ) cursor.execute( - """insert into SampleVectorTab (v32, v64, v8, vbin) - values (:1, :2, :3, :4)""", - [vector1_data_32, vector1_data_64, vector1_data_8, vector1_data_bin], + """insert into SampleVectorTab (v32, v64, v8, vbin, v64sparse) + values (:1, :2, :3, :4, :5)""", + [ + vector1_data_32, + vector1_data_64, + vector1_data_8, + vector1_data_bin, + vector1_data_sparse64, + ], ) # Multi-row insert @@ -75,26 +89,45 @@ vector2_data_64 = array.array("d", [22.25, 22.75, 22.5]) vector2_data_8 = array.array("b", [4, 5, 6]) vector2_data_bin = array.array("B", [40, 15, 255]) + vector2_data_sparse64 = oracledb.SparseVector( + 30, [3, 10, 12], array.array("d", [2.5, 2.5, 1.0]) + ) vector3_data_32 = array.array("f", [3.625, 3.5, 3.0]) vector3_data_64 = array.array("d", [33.25, 33.75, 33.5]) vector3_data_8 = array.array("b", [7, 8, 9]) vector3_data_bin = array.array("B", [0, 17, 101]) + vector3_data_sparse64 = oracledb.SparseVector( + 30, [8, 15, 29], array.array("d", [1.125, 200.5, 100.0]) + ) rows = [ - (vector2_data_32, vector2_data_64, vector2_data_8, vector2_data_bin), - (vector3_data_32, vector3_data_64, vector3_data_8, vector3_data_bin), + ( + vector2_data_32, + vector2_data_64, + vector2_data_8, + vector2_data_bin, + vector2_data_sparse64, + ), + ( + vector3_data_32, + vector3_data_64, + vector3_data_8, + vector3_data_bin, + vector3_data_sparse64, + ), ] cursor.executemany( - """insert into SampleVectorTab (v32, v64, v8, vbin) - values (:1, :2, :3, :4)""", + """insert into SampleVectorTab (v32, v64, v8, vbin, v64sparse) + values (:1, :2, :3, :4, :5)""", rows, ) # Query cursor.execute("select * from SampleVectorTab") - # Each vector is represented as an array.array type + # Each non-sparse vector is represented as an array.array type. + # Sparse vectors are represented as oracledb.SparseVector() instances for row in cursor: print(row) diff --git a/python/python-oracledb/vector_async.py b/python/python-oracledb/vector_async.py index 1f349e70..47b11f65 100644 --- a/python/python-oracledb/vector_async.py +++ b/python/python-oracledb/vector_async.py @@ -46,9 +46,14 @@ async def main(): params=sample_env.get_connect_params(), ) - # this script only works with Oracle Database 23.5 or later - if sample_env.get_server_version() < (23, 5): - sys.exit("This example requires Oracle Database 23.5 or later.") + # this script only works with Oracle Database 23.7 or later + # + # The VECTOR datatype was initially introduced in Oracle Database 23.4. + # The BINARY vector format was introduced in Oracle Database 23.5. + # The SPARSE vector format was introduced in Oracle Database 23.7. + + if sample_env.get_server_version() < (23, 7): + sys.exit("This example requires Oracle Database 23.7 or later.") with connection.cursor() as cursor: # Single-row insert @@ -56,15 +61,19 @@ async def main(): vector1_data_64 = array.array("d", [11.25, 11.75, 11.5]) vector1_data_8 = array.array("b", [1, 2, 3]) vector1_data_bin = array.array("B", [180, 150, 100]) + vector1_data_sparse64 = oracledb.SparseVector( + 30, [9, 16, 24], array.array("d", [19.125, 78.5, 977.375]) + ) await cursor.execute( - """insert into SampleVectorTab (v32, v64, v8, vbin) - values (:1, :2, :3, :4)""", + """insert into SampleVectorTab (v32, v64, v8, vbin, v64sparse) + values (:1, :2, :3, :4, :5)""", [ vector1_data_32, vector1_data_64, vector1_data_8, vector1_data_bin, + vector1_data_sparse64, ], ) @@ -73,11 +82,17 @@ async def main(): vector2_data_64 = array.array("d", [22.25, 22.75, 22.5]) vector2_data_8 = array.array("b", [4, 5, 6]) vector2_data_bin = array.array("B", [40, 15, 255]) + vector2_data_sparse64 = oracledb.SparseVector( + 30, [3, 10, 12], array.array("d", [2.5, 2.5, 1.0]) + ) vector3_data_32 = array.array("f", [3.625, 3.5, 3.0]) vector3_data_64 = array.array("d", [33.25, 33.75, 33.5]) vector3_data_8 = array.array("b", [7, 8, 9]) vector3_data_bin = array.array("B", [0, 17, 101]) + vector3_data_sparse64 = oracledb.SparseVector( + 30, [8, 15, 29], array.array("d", [1.125, 200.5, 100.0]) + ) rows = [ ( @@ -85,25 +100,28 @@ async def main(): vector2_data_64, vector2_data_8, vector2_data_bin, + vector2_data_sparse64, ), ( vector3_data_32, vector3_data_64, vector3_data_8, vector3_data_bin, + vector3_data_sparse64, ), ] await cursor.executemany( - """insert into SampleVectorTab (v32, v64, v8, vbin) - values (:1, :2, :3, :4)""", + """insert into SampleVectorTab (v32, v64, v8, vbin, v64sparse) + values (:1, :2, :3, :4, :5)""", rows, ) # Query await cursor.execute("select * from SampleVectorTab") - # Each vector is represented as an array.array type + # Each non-sparse vector is represented as an array.array type. + # Sparse vectors are represented as oracledb.SparseVector() instances async for row in cursor: print(row)