From 8342caf69a127e2b80768eeb3b55690a8d08d84a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:34:29 -0700 Subject: [PATCH 1/8] Bump org.apache.commons:commons-lang3 in /java/autonomous-db-parurl (#448) Bumps org.apache.commons:commons-lang3 from 3.12.0 to 3.18.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-version: 3.18.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- java/autonomous-db-parurl/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/autonomous-db-parurl/pom.xml b/java/autonomous-db-parurl/pom.xml index d414e8d7..4df62e12 100644 --- a/java/autonomous-db-parurl/pom.xml +++ b/java/autonomous-db-parurl/pom.xml @@ -31,7 +31,7 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.18.0 com.fasterxml.jackson.core From dcc388cbd27c0d55164df97456734626b7986913 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:34:49 -0700 Subject: [PATCH 2/8] Bump org.apache.kafka:kafka-clients (#446) Bumps org.apache.kafka:kafka-clients from 3.7.1 to 3.9.1. --- updated-dependencies: - dependency-name: org.apache.kafka:kafka-clients dependency-version: 3.9.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- txeventq/kafka-connector-example/kafka_client/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/txeventq/kafka-connector-example/kafka_client/pom.xml b/txeventq/kafka-connector-example/kafka_client/pom.xml index fce62ae2..f51dcd27 100644 --- a/txeventq/kafka-connector-example/kafka_client/pom.xml +++ b/txeventq/kafka-connector-example/kafka_client/pom.xml @@ -23,7 +23,7 @@ org.apache.kafka kafka-clients - 3.7.1 + 3.9.1 From 2b0ef0794c93dcab5a561e0057741363078cbce0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:25:04 -0700 Subject: [PATCH 3/8] Bump on-headers and morgan (#449) Bumps [on-headers](https://github.com/jshttp/on-headers) to 1.1.0 and updates ancestor dependency [morgan](https://github.com/expressjs/morgan). These dependencies need to be updated together. Updates `on-headers` from 1.0.2 to 1.1.0 - [Release notes](https://github.com/jshttp/on-headers/releases) - [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md) - [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0) Updates `morgan` from 1.10.0 to 1.10.1 - [Release notes](https://github.com/expressjs/morgan/releases) - [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/morgan/compare/1.10.0...1.10.1) --- updated-dependencies: - dependency-name: on-headers dependency-version: 1.1.0 dependency-type: indirect - dependency-name: morgan dependency-version: 1.10.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../hr_app/package-lock.json | 26 +++++++------------ .../hr_app/package.json | 2 +- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/javascript/rest-api/part-1-web-server-basics/hr_app/package-lock.json b/javascript/rest-api/part-1-web-server-basics/hr_app/package-lock.json index 90c0ff2e..06818e9c 100644 --- a/javascript/rest-api/part-1-web-server-basics/hr_app/package-lock.json +++ b/javascript/rest-api/part-1-web-server-basics/hr_app/package-lock.json @@ -9,7 +9,7 @@ "license": "Apache-2.0", "dependencies": { "express": "^4.21.2", - "morgan": "^1.9.1" + "morgan": "^1.10.1" } }, "node_modules/accepts": { @@ -532,28 +532,21 @@ } }, "node_modules/morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", - "on-headers": "~1.0.2" + "on-headers": "~1.1.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/morgan/node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -590,9 +583,10 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { "node": ">= 0.8" } diff --git a/javascript/rest-api/part-1-web-server-basics/hr_app/package.json b/javascript/rest-api/part-1-web-server-basics/hr_app/package.json index a2788ff6..4c2f2a14 100644 --- a/javascript/rest-api/part-1-web-server-basics/hr_app/package.json +++ b/javascript/rest-api/part-1-web-server-basics/hr_app/package.json @@ -10,6 +10,6 @@ "license": "Apache-2.0", "dependencies": { "express": "^4.21.2", - "morgan": "^1.9.1" + "morgan": "^1.10.1" } } From dc4111c5a8927273d305f28736ba70d0bcbca014 Mon Sep 17 00:00:00 2001 From: Christopher Jones Date: Wed, 6 Aug 2025 03:04:34 +1000 Subject: [PATCH 4/8] Update python-oracledb examples (#451) Signed-off-by: Christopher Jones --- python/python-oracledb/README.md | 5 +- python/python-oracledb/async_gather.py | 36 ++-- python/python-oracledb/bind_insert.py | 2 +- python/python-oracledb/bind_insert_async.py | 2 +- python/python-oracledb/bulk_aq.py | 66 ++++---- python/python-oracledb/bulk_aq_async.py | 107 ++++++++++++ python/python-oracledb/connection_pool.py | 58 ++++--- .../Dockerfile | 4 +- .../README.md | 0 .../{sample_container => containers}/setup.py | 0 python/python-oracledb/cqn.py | 2 +- .../database_change_notification.py | 2 +- python/python-oracledb/dataframe_insert.py | 107 ++++++++++++ python/python-oracledb/dataframe_numpy.py | 65 +++++++- python/python-oracledb/dataframe_pandas.py | 97 +++++++++-- .../python-oracledb/dataframe_pandas_async.py | 89 +++++++++- .../dataframe_parquet_write.py | 4 +- python/python-oracledb/dataframe_polars.py | 9 +- python/python-oracledb/dataframe_pyarrow.py | 8 +- python/python-oracledb/dataframe_torch.py | 2 +- python/python-oracledb/json_blob.py | 2 +- python/python-oracledb/json_blob_async.py | 2 +- .../multi_consumer_aq_async.py | 96 +++++++++++ python/python-oracledb/object_aq_async.py | 101 +++++++++++ python/python-oracledb/raw_aq_async.py | 86 ++++++++++ python/python-oracledb/scrollable_cursors.py | 7 +- .../sessionless_transactions.py | 157 ++++++++++++++++++ .../python-oracledb/sql/create_schema_23.sql | 1 + python/python-oracledb/sql/drop_schema.sql | 6 +- python/python-oracledb/transaction_guard.py | 6 +- 30 files changed, 1000 insertions(+), 129 deletions(-) create mode 100644 python/python-oracledb/bulk_aq_async.py rename python/python-oracledb/{sample_container => containers}/Dockerfile (95%) rename python/python-oracledb/{sample_container => containers}/README.md (100%) rename python/python-oracledb/{sample_container => containers}/setup.py (100%) create mode 100644 python/python-oracledb/dataframe_insert.py create mode 100644 python/python-oracledb/multi_consumer_aq_async.py create mode 100644 python/python-oracledb/object_aq_async.py create mode 100644 python/python-oracledb/raw_aq_async.py create mode 100644 python/python-oracledb/sessionless_transactions.py diff --git a/python/python-oracledb/README.md b/python/python-oracledb/README.md index 380ce058..d96a450e 100644 --- a/python/python-oracledb/README.md +++ b/python/python-oracledb/README.md @@ -29,8 +29,9 @@ Oracle Database. ### 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. +The [containers/samples_and_db](./containers/samples_and_db) directory has a +Dockerfile for building a container with the samples and a running Oracle +Database. ### Notebooks diff --git a/python/python-oracledb/async_gather.py b/python/python-oracledb/async_gather.py index 78b12937..81f45487 100644 --- a/python/python-oracledb/async_gather.py +++ b/python/python-oracledb/async_gather.py @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# Copyright (c) 2023, 2024, Oracle and/or its affiliates. +# Copyright (c) 2023, 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 @@ -27,8 +27,13 @@ # # 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. +# This also shows the use of pool_alias for connection pool caching, so the +# pool handle does not need to passed through the app. +# +# Each coroutine invocation will acquire a connection from the connection pool. +# The number of connections in the pool will depend on the speed of your +# environment. In some cases existing connections will get reused. In other +# cases up to CONCURRENCY connections will be created by the pool. # ----------------------------------------------------------------------------- import asyncio @@ -36,33 +41,38 @@ import oracledb import sample_env +# Pool name for the connection pool cache +POOL_ALIAS_NAME = "mypool" + # 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')""" +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 +# pool creates 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: +# connection returned from the pool +async def query(): + async with oracledb.connect_async( + pool_alias=POOL_ALIAS_NAME + ) 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( + oracledb.create_pool_async( user=sample_env.get_main_user(), password=sample_env.get_main_password(), dsn=sample_env.get_connect_string(), @@ -70,12 +80,14 @@ async def main(): min=1, max=CONCURRENCY, session_callback=init_session, + pool_alias=POOL_ALIAS_NAME, ) - coroutines = [query(pool) for i in range(CONCURRENCY)] + coroutines = [query() for i in range(CONCURRENCY)] await asyncio.gather(*coroutines) + pool = oracledb.get_pool(POOL_ALIAS_NAME) await pool.close() diff --git a/python/python-oracledb/bind_insert.py b/python/python-oracledb/bind_insert.py index abd7750f..712e858e 100644 --- a/python/python-oracledb/bind_insert.py +++ b/python/python-oracledb/bind_insert.py @@ -86,7 +86,7 @@ # Inserting a single bind still needs tuples # ----------------------------------------------------------------------------- -rows = [("Eleventh",), ("Twelth",)] +rows = [("Eleventh",), ("Twelfth",)] with connection.cursor() as cursor: cursor.executemany("insert into mytab(id, data) values (12, :1)", rows) diff --git a/python/python-oracledb/bind_insert_async.py b/python/python-oracledb/bind_insert_async.py index 2e3a3660..37f0acfb 100644 --- a/python/python-oracledb/bind_insert_async.py +++ b/python/python-oracledb/bind_insert_async.py @@ -92,7 +92,7 @@ async def main(): # Inserting a single bind still needs tuples # ------------------------------------------------------------------------- - rows = [("Eleventh",), ("Twelth",)] + rows = [("Eleventh",), ("Twelfth",)] await connection.executemany( "insert into mytab(id, data) values (12, :1)", rows diff --git a/python/python-oracledb/bulk_aq.py b/python/python-oracledb/bulk_aq.py index 7c0a1e40..8ea983b1 100644 --- a/python/python-oracledb/bulk_aq.py +++ b/python/python-oracledb/bulk_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. # @@ -53,8 +53,9 @@ "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()) +# 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()) # connect to database connection = oracledb.connect( @@ -64,39 +65,36 @@ ) # 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 -with connection.cursor() as cursor: - print("Enqueuing messages...") - batch_size = 6 - data_to_enqueue = PAYLOAD_DATA - while data_to_enqueue: - batch_data = data_to_enqueue[:batch_size] - data_to_enqueue = data_to_enqueue[batch_size:] - messages = [connection.msgproperties(payload=d) for d in batch_data] - for data in batch_data: - print(data) - queue.enqmany(messages) - connection.commit() +print("Enqueuing messages...") +batch_size = 6 +data_to_enqueue = PAYLOAD_DATA +while data_to_enqueue: + batch_data = data_to_enqueue[:batch_size] + data_to_enqueue = data_to_enqueue[batch_size:] + messages = [connection.msgproperties(payload=d) for d in batch_data] + for data in batch_data: + print(data) + queue.enqmany(messages) +connection.commit() # dequeue the messages -with connection.cursor() as cursor: - print("\nDequeuing messages...") - batch_size = 8 - while True: - messages = queue.deqmany(batch_size) - if not messages: - break - for props in messages: - print(props.payload.decode()) - connection.commit() - print("\nDone.") +print("\nDequeuing messages...") +batch_size = 8 +while True: + messages = queue.deqmany(batch_size) + if not messages: + break + for props in messages: + print(props.payload.decode()) +connection.commit() +print("\nDone.") diff --git a/python/python-oracledb/bulk_aq_async.py b/python/python-oracledb/bulk_aq_async.py new file mode 100644 index 00000000..539a6206 --- /dev/null +++ b/python/python-oracledb/bulk_aq_async.py @@ -0,0 +1,107 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. +# +# Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, +# Canada. All rights reserved. +# +# 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# bulk_aq_async.py +# +# Demonstrates how to use bulk enqueuing and dequeuing of messages with +# advanced queuing using asyncio. It makes use of a RAW queue created in the +# sample setup. +# ----------------------------------------------------------------------------- + +import asyncio + +import oracledb +import sample_env + +QUEUE_NAME = "DEMO_RAW_QUEUE" +PAYLOAD_DATA = [ + "The first message", + "The second message", + "The third message", + "The fourth message", + "The fifth message", + "The sixth message", + "The seventh message", + "The eighth message", + "The ninth message", + "The tenth message", + "The eleventh message", + "The twelfth and final message", +] + + +async def main(): + + # connect to database + async with oracledb.connect_async( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + ) as connection: + + # create a queue + 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 await queue.deqone(): + pass + + # enqueue a few messages + print("Enqueuing messages...") + batch_size = 6 + data_to_enqueue = PAYLOAD_DATA + while data_to_enqueue: + batch_data = data_to_enqueue[:batch_size] + data_to_enqueue = data_to_enqueue[batch_size:] + messages = [ + connection.msgproperties(payload=d) for d in batch_data + ] + for data in batch_data: + print(data) + await queue.enqmany(messages) + await connection.commit() + + # dequeue the messages + print("\nDequeuing messages...") + batch_size = 8 + while True: + messages = await queue.deqmany(batch_size) + if not messages: + break + for props in messages: + print(props.payload.decode()) + await connection.commit() + print("\nDone.") + + +asyncio.run(main()) diff --git a/python/python-oracledb/connection_pool.py b/python/python-oracledb/connection_pool.py index dd76c05e..6a7647ea 100644 --- a/python/python-oracledb/connection_pool.py +++ b/python/python-oracledb/connection_pool.py @@ -1,5 +1,5 @@ # ----------------------------------------------------------------------------- -# Copyright (c) 2022, 2024, Oracle and/or its affiliates. +# Copyright (c) 2022, 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 @@ -27,6 +27,9 @@ # # Demonstrates the use of connection pooling using a Flask web application. # +# This also shows the use of pool_alias for connection pool caching, so the +# pool handle does not need to passed through the app. +# # Connection Pools can significantly reduce connection times for long running # applications that repeatedly open and close connections. Connection pools # allow multiple, concurrent web requests to be efficiently handled. Internal @@ -37,7 +40,7 @@ # # 1. Install Flask, for example like: # -# python -m pip install Flask +# python -m pip install flask # # 2. (Optional) Set environment variables referenced in sample_env.py # @@ -67,37 +70,42 @@ import sample_env # Port to listen on -port = int(os.environ.get("PORT", "8080")) +PORT = int(os.environ.get("PORT", "8080")) + +# 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. +POOL_MIN = 4 +POOL_MAX = 4 +POOL_INC = 0 + +# Pool name for the connection pool cache +POOL_ALIAS_NAME = "mypool" # 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 +# +# The pool is stored in the pool cache. Connections can later be acquired from +# the pool by passing the same pool_alias value to oracledb.connect() 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. - - pool_min = 4 - pool_max = 4 - pool_inc = 0 - - pool = oracledb.create_pool( + oracledb.create_pool( user=sample_env.get_main_user(), password=sample_env.get_main_password(), dsn=sample_env.get_connect_string(), params=sample_env.get_pool_params(), - min=pool_min, - max=pool_max, - increment=pool_inc, + min=POOL_MIN, + max=POOL_MAX, + increment=POOL_INC, session_callback=init_session, + pool_alias=POOL_ALIAS_NAME, ) - return pool - # init_session(): a 'session callback' to efficiently set any initial state # that each connection should have. @@ -105,9 +113,9 @@ def start_pool(): # This particular demo doesn't use dates, so sessionCallback could be omitted, # but it does show the kinds of settings many apps would use. # -# If you have multiple SQL statements, then call them all in a PL/SQL anonymous -# block with BEGIN/END so you only use execute() once. This is shown later in -# create_schema(). +# If you have multiple SQL statements, an optimization is to call them all in a +# PL/SQL anonymous block with BEGIN/END so you only use cursor.execute() once. +# This is shown later in create_schema(). # def init_session(connection, requestedTag_ignored): with connection.cursor() as cursor: @@ -125,7 +133,7 @@ def init_session(connection, requestedTag_ignored): # create_schema(): drop and create the demo table, and add a row def create_schema(): - with pool.acquire() as connection: + with oracledb.connect(pool_alias=POOL_ALIAS_NAME) as connection: with connection.cursor() as cursor: cursor.execute( """ @@ -169,7 +177,7 @@ def index(): # variable 'idbv'. @app.route("/post/") def post(username): - with pool.acquire() as connection: + with oracledb.connect(pool_alias=POOL_ALIAS_NAME) as connection: with connection.cursor() as cursor: connection.autocommit = True idbv = cursor.var(int) @@ -187,7 +195,7 @@ def post(username): # Show the username for a given id @app.route("/user/") def show_username(id): - with pool.acquire() as connection: + with oracledb.connect(pool_alias=POOL_ALIAS_NAME) as connection: with connection.cursor() as cursor: cursor.execute("select username from demo where id = :idbv", [id]) r = cursor.fetchone() @@ -198,13 +206,13 @@ def show_username(id): if __name__ == "__main__": # Start a pool of connections - pool = start_pool() + start_pool() # Create a demo table create_schema() - m = f"\nTry loading http://127.0.0.1:{port}/user/1 in a browser\n" + 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) # Start a webserver - app.run(port=port) + app.run(port=PORT) diff --git a/python/python-oracledb/sample_container/Dockerfile b/python/python-oracledb/containers/Dockerfile similarity index 95% rename from python/python-oracledb/sample_container/Dockerfile rename to python/python-oracledb/containers/Dockerfile index abae563c..0d612b8f 100644 --- a/python/python-oracledb/sample_container/Dockerfile +++ b/python/python-oracledb/containers/Dockerfile @@ -85,8 +85,8 @@ WORKDIR /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/notebooks/ && \ + cp python-oracledb-main/samples/containers/samples_and_db/setup.py . && \ + /bin/rm -rf python-oracledb-main/samples/containers/ python-oracledb-main/samples/notebooks/ && \ mv python-oracledb-main/samples/* . && \ /bin/rm -rf python-oracledb-main samples main.zip && \ cat create_schema.py >> /samples/setup.py && \ diff --git a/python/python-oracledb/sample_container/README.md b/python/python-oracledb/containers/README.md similarity index 100% rename from python/python-oracledb/sample_container/README.md rename to python/python-oracledb/containers/README.md diff --git a/python/python-oracledb/sample_container/setup.py b/python/python-oracledb/containers/setup.py similarity index 100% rename from python/python-oracledb/sample_container/setup.py rename to python/python-oracledb/containers/setup.py diff --git a/python/python-oracledb/cqn.py b/python/python-oracledb/cqn.py index c5c6516a..d6399f75 100644 --- a/python/python-oracledb/cqn.py +++ b/python/python-oracledb/cqn.py @@ -55,7 +55,7 @@ def callback(message): registered = False return print("Message database name:", message.dbname) - print("Message tranasction id:", message.txid) + print("Message transaction id:", message.txid) print("Message queries:") for query in message.queries: print("--> Query ID:", query.id) diff --git a/python/python-oracledb/database_change_notification.py b/python/python-oracledb/database_change_notification.py index 7861ab2c..385e73bf 100644 --- a/python/python-oracledb/database_change_notification.py +++ b/python/python-oracledb/database_change_notification.py @@ -55,7 +55,7 @@ def callback(message): registered = False return print("Message database name:", message.dbname) - print("Message tranasction id:", message.txid) + print("Message transaction id:", message.txid) print("Message tables:") for table in message.tables: print("--> Table Name:", table.name) diff --git a/python/python-oracledb/dataframe_insert.py b/python/python-oracledb/dataframe_insert.py new file mode 100644 index 00000000..0a1e89d6 --- /dev/null +++ b/python/python-oracledb/dataframe_insert.py @@ -0,0 +1,107 @@ +# ----------------------------------------------------------------------------- +# 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_insert.py +# +# Shows how executemany() can be used to insert a Pandas dataframe directly +# into Oracle Database. The same technique can be used with data frames from +# many other libraries. +# ----------------------------------------------------------------------------- + +import sys +import pandas + +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(), +) + +# ----------------------------------------------------------------------------- +# +# Inserting a simple DataFrame + +with connection.cursor() as cursor: + + # Create a Pandas DataFrame + print("Pandas Dataframe 1:") + d = {"A": [101, 213, 394], "B": ["Christie", "Cindy", "Kate"]} + pdf = pandas.DataFrame(data=d) + print(pdf) + + # Insert data into NUMBER and VARCHAR2(20) columns using Oracle Database's + # efficient "Array DML" method + cursor.executemany("insert into mytab (id, data) values (:1, :2)", pdf) + + # Check data + print("\nOracle Database Query:") + cursor.execute("select * from mytab order by id") + columns = [col.name for col in cursor.description] + print(columns) + for r in cursor: + print(r) + +# ----------------------------------------------------------------------------- +# +# Inserting VECTORs + +# The VECTOR example only works with Oracle Database 23.4 or later +if sample_env.get_server_version() < (23, 4): + sys.exit("This example requires Oracle Database 23.4 or later.") + +# The VECTOR example works with thin mode, or with thick mode using Oracle +# Client 23.4 or later +if not connection.thin and oracledb.clientversion()[:2] < (23, 4): + sys.exit( + "This example requires python-oracledb thin mode, or Oracle Client" + " 23.4 or later" + ) + +with connection.cursor() as cursor: + + # Create a Pandas DataFrame + print("\nPandas Dataframe 2:") + d = {"v": [[3.3, 1.32, 5.0], [2.2, 2.32, 2.0]]} + pdf = pandas.DataFrame(data=d) + print(pdf) + + # Insert data into a VECTOR column using Oracle Database's + # efficient "Array DML" method + cursor.executemany("insert into SampleVectorTab (v64) values (:1)", pdf) + + # Check data + print("\nOracle Database Query:") + cursor.execute("select v64 from SampleVectorTab order by id") + for (r,) in cursor: + print(r) diff --git a/python/python-oracledb/dataframe_numpy.py b/python/python-oracledb/dataframe_numpy.py index 8bc7a476..ad5f9bad 100644 --- a/python/python-oracledb/dataframe_numpy.py +++ b/python/python-oracledb/dataframe_numpy.py @@ -25,12 +25,14 @@ # ----------------------------------------------------------------------------- # 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. +# Shows how to use connection.fetch_df_all() to put data into a NumPy ndarray # ----------------------------------------------------------------------------- -import pyarrow +import array +import sys + import numpy +import pyarrow import oracledb import sample_env @@ -46,11 +48,14 @@ params=sample_env.get_connect_params(), ) -SQL = "select id from SampleQueryTab order by id" +# ----------------------------------------------------------------------------- +# +# Fetching all records -# Get an OracleDataFrame +# Get a python-oracledb DataFrame # Adjust arraysize to tune the query fetch performance -odf = connection.fetch_df_all(statement=SQL, arraysize=100) +sql = "select id from SampleQueryTab order by id" +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")) @@ -62,10 +67,56 @@ print("Type:") print(type(np)) # -# Perform various numpy operations on the ndarray +print("Values:") +print(np) + +# Perform various NumPy operations on the ndarray print("\nSum:") print(numpy.sum(np)) print("\nLog10:") print(numpy.log10(np)) + +# ----------------------------------------------------------------------------- +# +# Fetching VECTORs + +# The VECTOR example only works with Oracle Database 23.4 or later +if sample_env.get_server_version() < (23, 4): + sys.exit("This example requires Oracle Database 23.4 or later.") + +# The VECTOR example works with thin mode, or with thick mode using Oracle +# Client 23.4 or later +if not connection.thin and oracledb.clientversion()[:2] < (23, 4): + sys.exit( + "This example requires python-oracledb thin mode, or Oracle Client" + " 23.4 or later" + ) + +# Insert sample data +rows = [ + (array.array("d", [11.25, 11.75, 11.5]),), + (array.array("d", [12.25, 12.75, 12.5]),), +] + +with connection.cursor() as cursor: + cursor.executemany("insert into SampleVectorTab (v64) values (:1)", rows) + +# Get a python-oracledb DataFrame +# Adjust arraysize to tune the query fetch performance +sql = "select v64 from SampleVectorTab order by id" +odf = connection.fetch_df_all(statement=sql, arraysize=100) + +# Convert to a NumPy ndarray +pyarrow_array = pyarrow.array(odf.get_column_by_name("V64")) +np = pyarrow_array.to_numpy(zero_copy_only=False) + +print("Type:") +print(type(np)) # + +print("Values:") +print(np) + +print("\nSum:") +print(numpy.sum(np)) diff --git a/python/python-oracledb/dataframe_pandas.py b/python/python-oracledb/dataframe_pandas.py index f233703f..49c13be0 100644 --- a/python/python-oracledb/dataframe_pandas.py +++ b/python/python-oracledb/dataframe_pandas.py @@ -29,6 +29,10 @@ # to create Pandas dataframes. # ----------------------------------------------------------------------------- +import array +import sys + +import numpy import pandas import pyarrow @@ -46,20 +50,17 @@ params=sample_env.get_connect_params(), ) -SQL = "select id, name from SampleQueryTab order by id" - # ----------------------------------------------------------------------------- # # Fetching all records -# Get an OracleDataFrame. +# Get a python-oracledb DataFrame. # Adjust arraysize to tune the query fetch performance -odf = connection.fetch_df_all(statement=SQL, arraysize=100) +sql = "select id, name from SampleQueryTab order by id" +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() +df = pyarrow.table(odf).to_pandas() # Perform various Pandas operations on the DataFrame @@ -88,10 +89,9 @@ # 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() +sql = "select id, name from SampleQueryTab order by id" +for odf in connection.fetch_df_batches(statement=sql, size=10): + df_b = pyarrow.table(odf).to_pandas() print(f"Appending {df_b.shape[0]} rows") df = pandas.concat([df, df_b], ignore_index=True) @@ -100,3 +100,78 @@ print("\nLast three rows:") print(df.tail(3)) + +# ----------------------------------------------------------------------------- +# +# Fetching VECTORs + +# The VECTOR example only works with Oracle Database 23.4 or later +if sample_env.get_server_version() < (23, 4): + sys.exit("This example requires Oracle Database 23.4 or later.") + +# The VECTOR example works with thin mode, or with thick mode using Oracle +# Client 23.4 or later +if not connection.thin and oracledb.clientversion()[:2] < (23, 4): + sys.exit( + "This example requires python-oracledb thin mode, or Oracle Client" + " 23.4 or later" + ) + +# Insert sample data +rows = [ + (array.array("d", [11.25, 11.75, 11.5]),), + (array.array("d", [12.25, 12.75, 12.5]),), +] + +with connection.cursor() as cursor: + cursor.executemany("insert into SampleVectorTab (v64) values (:1)", rows) + + +# Get a python-oracledb DataFrame. +# Adjust arraysize to tune the query fetch performance +sql = "select id, v64 from SampleVectorTab order by id" +odf = connection.fetch_df_all(statement=sql, arraysize=100) + +# Get a Pandas DataFrame from the data +df = pyarrow.table(odf).to_pandas() + +# Perform various Pandas operations on the DataFrame + +print("\nDataFrame:") +print(df) + +print("\nMean:") +print(pandas.DataFrame(df["V64"].tolist()).mean()) + +print("\nList:") +df2 = pandas.DataFrame(df["V64"].tolist()).T +print(df2) +print(df2.sum()) + +# You can manipulate vectors using Pandas's apply or list comprehension with +# NumPy for efficient array operations. + +# Scaling all vectors by a factor of two +print("\nScaled:") +df["SCALED_V64_COL"] = df["V64"] * 2 +print(df) + +# Calculating vector norms +# +# L2_NORM = Straight line distance from the origin to vector's endpoint +# L1_NORM = Sum of absolute values of the vector's components +# Linf_NORM = Largest absolute component of the vector; useful in scenarios +# where maximum deviation matters +print("\nNorms:") +df["L2_NORM"] = df["V64"].apply(lambda x: numpy.linalg.norm(x, ord=2)) +df["L1_NORM"] = df["V64"].apply(lambda x: numpy.linalg.norm(x, ord=1)) +df["Linf_NORM"] = df["V64"].apply( + lambda x: numpy.linalg.norm(x, ord=numpy.inf) +) +print(df) + +# Calculating the vector dot product with a reference vector +print("\nDot product:") +ref_vector = numpy.array([1, 10, 10]) +df["DOT_PRODUCT"] = df["V64"].apply(lambda x: numpy.dot(x, ref_vector)) +print(df) diff --git a/python/python-oracledb/dataframe_pandas_async.py b/python/python-oracledb/dataframe_pandas_async.py index 796da794..eaadead2 100644 --- a/python/python-oracledb/dataframe_pandas_async.py +++ b/python/python-oracledb/dataframe_pandas_async.py @@ -33,8 +33,11 @@ # other, synchronous, data frame samples. # ----------------------------------------------------------------------------- +import array import asyncio +import sys +import numpy import pandas import pyarrow @@ -56,14 +59,12 @@ async def main(): # # Fetching all records - # Get an OracleDataFrame. + # Get a python-oracledb DataFrame. # 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() + df = pyarrow.table(odf).to_pandas() # Perform various Pandas operations on the DataFrame @@ -93,9 +94,7 @@ async def main(): # 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() + df_b = pyarrow.table(odf).to_pandas() print(f"Appending {df_b.shape[0]} rows") df = pandas.concat([df, df_b], ignore_index=True) @@ -105,5 +104,81 @@ async def main(): print("\nLast three rows:") print(df.tail(3)) + # ------------------------------------------------------------------------- + # + # Fetching VECTORs + + # The VECTOR example only works with Oracle Database 23.4 or later + if sample_env.get_server_version() < (23, 4): + sys.exit("This example requires Oracle Database 23.4 or later.") + + # The VECTOR example works with thin mode, or with thick mode using Oracle + # Client 23.4 or later + if not connection.thin and oracledb.clientversion()[:2] < (23, 4): + sys.exit( + "This example requires python-oracledb thin mode, or Oracle Client" + " 23.4 or later" + ) + + # Insert sample data + rows = [ + (array.array("d", [11.25, 11.75, 11.5]),), + (array.array("d", [12.25, 12.75, 12.5]),), + ] + + with connection.cursor() as cursor: + await cursor.executemany( + "insert into SampleVectorTab (v64) values (:1)", rows + ) + + # Get a python-oracledb DataFrame. + # Adjust arraysize to tune the query fetch performance + sql = "select id, v64 from SampleVectorTab order by id" + odf = await connection.fetch_df_all(statement=sql, arraysize=100) + + # Get a Pandas DataFrame from the data + df = pyarrow.table(odf).to_pandas() + + # Perform various Pandas operations on the DataFrame + + print("\nDataFrame:") + print(df) + + print("\nMean:") + print(pandas.DataFrame(df["V64"].tolist()).mean()) + + print("\nList:") + df2 = pandas.DataFrame(df["V64"].tolist()).T + print(df2) + print(df2.sum()) + + # You can manipulate vectors using Pandas's apply or list comprehension + # with NumPy for efficient array operations. + + # Scaling all vectors by a factor of two + print("\nScaled:") + df["SCALED_V64_COL"] = df["V64"].apply(lambda x: numpy.array(x) * 2) + print(df) + + # Calculating vector norms + # + # L2_NORM = Straight line distance from the origin to vector's endpoint + # L1_NORM = Sum of absolute values of the vector's components + # Linf_NORM = Largest absolute component of the vector; useful in scenarios + # where maximum deviation matters + print("\nNorms:") + df["L2_NORM"] = df["V64"].apply(lambda x: numpy.linalg.norm(x, ord=2)) + df["L1_NORM"] = df["V64"].apply(lambda x: numpy.linalg.norm(x, ord=1)) + df["Linf_NORM"] = df["V64"].apply( + lambda x: numpy.linalg.norm(x, ord=numpy.inf) + ) + print(df) + + # Calculating the vector dot product with a reference vector + print("\nDot product:") + ref_vector = numpy.array([1, 10, 10]) + df["DOT_PRODUCT"] = df["V64"].apply(lambda x: numpy.dot(x, ref_vector)) + print(df) + asyncio.run(main()) diff --git a/python/python-oracledb/dataframe_parquet_write.py b/python/python-oracledb/dataframe_parquet_write.py index 02a7d93f..7a023859 100644 --- a/python/python-oracledb/dataframe_parquet_write.py +++ b/python/python-oracledb/dataframe_parquet_write.py @@ -61,9 +61,7 @@ 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() - ) + pyarrow_table = pyarrow.table(odf) if not pqwriter: pqwriter = pq.ParquetWriter(PARQUET_FILE_NAME, pyarrow_table.schema) diff --git a/python/python-oracledb/dataframe_polars.py b/python/python-oracledb/dataframe_polars.py index 7b91ced7..5d1aed22 100644 --- a/python/python-oracledb/dataframe_polars.py +++ b/python/python-oracledb/dataframe_polars.py @@ -52,15 +52,12 @@ SQL1 = "select * from SampleQueryTab order by id" -# Get an OracleDataFrame +# Get a python-oracledb DataFrame # 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) +p = polars.from_arrow(odf) print(type(p)) # @@ -76,7 +73,7 @@ SQL2 = "select id from SampleQueryTab order by id" -# Get an OracleDataFrame +# Get a python-oracledb DataFrame # Adjust arraysize to tune the query fetch performance odf = connection.fetch_df_all(statement=SQL2, arraysize=100) diff --git a/python/python-oracledb/dataframe_pyarrow.py b/python/python-oracledb/dataframe_pyarrow.py index d666f62b..1cc56dab 100644 --- a/python/python-oracledb/dataframe_pyarrow.py +++ b/python/python-oracledb/dataframe_pyarrow.py @@ -51,14 +51,12 @@ SQL1 = "select id, name from SampleQueryTab order by id" -# Get an OracleDataFrame +# Get a python-oracledb DataFrame # 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() -) +pyarrow_table = pyarrow.table(odf) print("Type:") print(type(pyarrow_table)) # @@ -78,7 +76,7 @@ SQL2 = "select id from SampleQueryTab order by id" -# Get an OracleDataFrame +# Get a python-oracledb DataFrame # Adjust arraysize to tune the query fetch performance odf = connection.fetch_df_all(statement=SQL2, arraysize=100) diff --git a/python/python-oracledb/dataframe_torch.py b/python/python-oracledb/dataframe_torch.py index e45d1940..de2d0113 100644 --- a/python/python-oracledb/dataframe_torch.py +++ b/python/python-oracledb/dataframe_torch.py @@ -48,7 +48,7 @@ SQL = "select id from SampleQueryTab order by id" -# Get an OracleDataFrame +# Get a python-oracledb DataFrame # Adjust arraysize to tune the query fetch performance odf = connection.fetch_df_all(statement=SQL, arraysize=100) diff --git a/python/python-oracledb/json_blob.py b/python/python-oracledb/json_blob.py index 61b8576f..e0d06658 100644 --- a/python/python-oracledb/json_blob.py +++ b/python/python-oracledb/json_blob.py @@ -57,7 +57,7 @@ client_version = oracledb.clientversion()[0] db_version = int(connection.version.split(".")[0]) -# Minimum database vesion is 12 +# Minimum database version is 12 if db_version < 12: sys.exit("This example requires Oracle Database 12.1.0.2 or later") diff --git a/python/python-oracledb/json_blob_async.py b/python/python-oracledb/json_blob_async.py index d8b9221d..5c9fb45c 100644 --- a/python/python-oracledb/json_blob_async.py +++ b/python/python-oracledb/json_blob_async.py @@ -54,7 +54,7 @@ async def main(): params=sample_env.get_connect_params(), ) - # Minimum database vesion is 12 + # Minimum database version 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") diff --git a/python/python-oracledb/multi_consumer_aq_async.py b/python/python-oracledb/multi_consumer_aq_async.py new file mode 100644 index 00000000..12c405e9 --- /dev/null +++ b/python/python-oracledb/multi_consumer_aq_async.py @@ -0,0 +1,96 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. +# +# Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, +# Canada. All rights reserved. +# +# 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# 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 asyncio + +import oracledb +import sample_env + +QUEUE_NAME = "DEMO_RAW_QUEUE_MULTI" +PAYLOAD_DATA = [ + "The first message", + "The second message", + "The third message", + "The fourth and final message", +] + + +async def main(): + + # connect to database + 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 queue + queue = connection.queue(QUEUE_NAME) + queue.deqoptions.wait = oracledb.DEQ_NO_WAIT + queue.deqoptions.navigation = oracledb.DEQ_FIRST_MSG + + # enqueue a few messages + print("Enqueuing messages...") + for data in PAYLOAD_DATA: + print(data) + await queue.enqone(connection.msgproperties(payload=data)) + await connection.commit() + print() + + # dequeue the messages for consumer A + print("Dequeuing the messages for consumer A...") + queue.deqoptions.consumername = "SUBSCRIBER_A" + while True: + props = await queue.deqone() + if not props: + break + print(props.payload.decode()) + await connection.commit() + print() + + # dequeue the message for consumer B + print("Dequeuing the messages for consumer B...") + queue.deqoptions.consumername = "SUBSCRIBER_B" + while True: + props = await queue.deqone() + if not props: + break + print(props.payload.decode()) + await connection.commit() + print("\nDone.") + + +asyncio.run(main()) diff --git a/python/python-oracledb/object_aq_async.py b/python/python-oracledb/object_aq_async.py new file mode 100644 index 00000000..1dda7de0 --- /dev/null +++ b/python/python-oracledb/object_aq_async.py @@ -0,0 +1,101 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. +# +# Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, +# Canada. All rights reserved. +# +# 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_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 asyncio +import decimal + +import oracledb +import sample_env + +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"), + ), +] + + +async def main(): + + # connect to database + 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 queue + books_type = await connection.gettype(BOOK_TYPE_NAME) + queue = connection.queue(QUEUE_NAME, payload_type=books_type) + 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 await queue.deqone(): + pass + + # enqueue a few messages + print("Enqueuing messages...") + for title, authors, price in BOOK_DATA: + book = books_type.newobject() + book.TITLE = title + book.AUTHORS = authors + book.PRICE = price + print(title) + await queue.enqone(connection.msgproperties(payload=book)) + await connection.commit() + + # dequeue the messages + print("\nDequeuing messages...") + while True: + props = await queue.deqone() + if not props: + break + print(props.payload.TITLE) + await connection.commit() + print("\nDone.") + + +asyncio.run(main()) diff --git a/python/python-oracledb/raw_aq_async.py b/python/python-oracledb/raw_aq_async.py new file mode 100644 index 00000000..8d25b614 --- /dev/null +++ b/python/python-oracledb/raw_aq_async.py @@ -0,0 +1,86 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved. +# +# Portions Copyright 2001-2007, Computronix (Canada) Ltd., Edmonton, Alberta, +# Canada. All rights reserved. +# +# 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# 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 asyncio + +import oracledb +import sample_env + +QUEUE_NAME = "DEMO_RAW_QUEUE" +PAYLOAD_DATA = [ + "The first message", + "The second message", + "The third message", + "The fourth and final message", +] + + +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 queue + 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 await queue.deqone(): + pass + + # enqueue a few messages + print("Enqueuing messages...") + for data in PAYLOAD_DATA: + print(data) + await queue.enqone(connection.msgproperties(payload=data)) + await connection.commit() + + # dequeue the messages + print("\nDequeuing messages...") + while True: + props = await queue.deqone() + if not props: + break + print(props.payload.decode()) + await connection.commit() + print("\nDone.") + + +asyncio.run(main()) diff --git a/python/python-oracledb/scrollable_cursors.py b/python/python-oracledb/scrollable_cursors.py index 90699842..a1341dd3 100644 --- a/python/python-oracledb/scrollable_cursors.py +++ b/python/python-oracledb/scrollable_cursors.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. # @@ -38,8 +38,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()) connection = oracledb.connect( user=sample_env.get_main_user(), diff --git a/python/python-oracledb/sessionless_transactions.py b/python/python-oracledb/sessionless_transactions.py new file mode 100644 index 00000000..c0b31b07 --- /dev/null +++ b/python/python-oracledb/sessionless_transactions.py @@ -0,0 +1,157 @@ +# ----------------------------------------------------------------------------- +# 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. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# sessionless_transactions.py +# +# Show Oracle Database 23ai Sessionless Transactions +# ----------------------------------------------------------------------------- + +import sys + +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()) + +# this script only works with Oracle Database 23.6 or later +if sample_env.get_server_version() < (23, 6): + sys.exit("This example requires Oracle Database 23.6 or later.") + +# this script works with thin mode, or with thick mode using Oracle Client +# 23.6 or later +if not oracledb.is_thin_mode() and oracledb.clientversion()[:2] < (23, 6): + sys.exit( + "This example requires python-oracledb thin mode, or Oracle Client" + " 23.6 or later" + ) + +TXN_ID = b"my_transaction_id" + +pool = oracledb.create_pool( + user=sample_env.get_main_user(), + password=sample_env.get_main_password(), + dsn=sample_env.get_connect_string(), + params=sample_env.get_pool_params(), +) + +# ----------------------------------------------------------------------------- +# Basic Sessionless Transaction example + +print("Example 1:") + +# Start and suspend a transaction +with pool.acquire() as connection1: + + # Immediately begin the transaction + connection1.begin_sessionless_transaction(transaction_id=TXN_ID) + + with connection1.cursor() as cursor1: + cursor1.execute( + "insert into mytab(id, data) values (:i, :d)", [1, "Sessionless 1"] + ) + connection1.suspend_sessionless_transaction() + + # Since the transaction is suspended, there will be no rows + print("1st query") + with connection1.cursor() as cursor1b: + for r in cursor1b.execute("select * from mytab"): + print(r) + +# Resume and complete the transaction in a different connection +with pool.acquire() as connection2: + + # Immediately resume the transaction + connection2.resume_sessionless_transaction(transaction_id=TXN_ID) + + with connection2.cursor() as cursor2: + cursor2.execute( + "insert into mytab(id, data) values (:i, :d)", [2, "Sessionless 2"] + ) + + # The query will show both rows inserted + print("2nd query") + for r in cursor2.execute("select * from mytab order by id"): + print(r) + + # Rollback so the example can be run multiple times. + # This concludes the Sessionless Transaction + connection2.rollback() + +# ----------------------------------------------------------------------------- +# Sessionless Transaction example with custom timeouts and round-trip +# optimizations + +print("Example 2:") + +# Start and suspend a transaction +with pool.acquire() as connection3: + + connection3.begin_sessionless_transaction( + transaction_id=TXN_ID, + # The transaction can only ever be suspended for 15 seconds before it + # is automatically rolled back + timeout=15, + # Only start the transaction when the next DB operation is performed + defer_round_trip=True, + ) + with connection3.cursor() as cursor3: + cursor3.execute( + "insert into mytab(id, data) values (:i, :d)", + [3, "Sessionless 3"], + suspend_on_success=True, # automatically suspend on success + ) + + # Since the transaction is suspended, there will be no rows + print("1st query") + with connection3.cursor() as cursor3b: + for r in cursor3b.execute("select * from mytab"): + print(r) + +# Resume and complete the transaction in a different connection +with pool.acquire() as connection4: + connection4.resume_sessionless_transaction( + transaction_id=TXN_ID, + # Only wait 20 seconds if someone else is using the transaction + timeout=20, + # Only initiate resuming the transaction when the next DB operation is + # performed + defer_round_trip=True, + ) + + with connection4.cursor() as cursor4: + cursor4.execute( + "insert into mytab(id, data) values (:i, :d)", [4, "Sessionless 4"] + ) + + # The query will show both rows inserted + print("2nd query") + for r in cursor4.execute("select * from mytab order by id"): + print(r) + + # Rollback so the example can be run multiple times. + # This concludes the Sessionless Transaction + connection4.rollback() diff --git a/python/python-oracledb/sql/create_schema_23.sql b/python/python-oracledb/sql/create_schema_23.sql index 30daef5f..53226f02 100644 --- a/python/python-oracledb/sql/create_schema_23.sql +++ b/python/python-oracledb/sql/create_schema_23.sql @@ -32,6 +32,7 @@ *---------------------------------------------------------------------------*/ create table &main_user..SampleVectorTab ( + id number generated by default on null as identity primary key, v32 vector(3, float32), v64 vector(3, float64), v8 vector(3, int8), diff --git a/python/python-oracledb/sql/drop_schema.sql b/python/python-oracledb/sql/drop_schema.sql index 82c6c949..62e11959 100644 --- a/python/python-oracledb/sql/drop_schema.sql +++ b/python/python-oracledb/sql/drop_schema.sql @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------- - * Copyright 2017, 2022, Oracle and/or its affiliates. + * Copyright 2017, 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 @@ -43,7 +43,9 @@ begin for r in ( select edition_name from dba_editions - where edition_name in (upper('&edition_name')) + start with edition_name = upper('&edition_name') + connect by prior edition_name = parent_edition_name + order by level desc ) loop execute immediate 'drop edition ' || r.edition_name || ' cascade'; end loop; diff --git a/python/python-oracledb/transaction_guard.py b/python/python-oracledb/transaction_guard.py index a9f8fdf5..5a9c286a 100644 --- a/python/python-oracledb/transaction_guard.py +++ b/python/python-oracledb/transaction_guard.py @@ -93,6 +93,9 @@ input("Press ENTER when complete.") +ltxid = connection.ltxid +if not ltxid: + sys.exit("Logical transaction not available. Terminating.") try: connection.commit() # this should fail sys.exit("Session was not killed. Sample cannot continue.") @@ -101,9 +104,6 @@ print("Session is recoverable:", error_obj.isrecoverable) if not error_obj.isrecoverable: sys.exit("Session is not recoverable. Terminating.") -ltxid = connection.ltxid -if not ltxid: - sys.exit("Logical transaction not available. Terminating.") pool.drop(connection) # check if previous transaction completed From d0b3e4fb7bcdaa41abe0c67f6218dfde561ecc35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:02:16 -0700 Subject: [PATCH 5/8] Bump on-headers and morgan (#452) Bumps [on-headers](https://github.com/jshttp/on-headers) to 1.1.0 and updates ancestor dependency [morgan](https://github.com/expressjs/morgan). These dependencies need to be updated together. Updates `on-headers` from 1.0.2 to 1.1.0 - [Release notes](https://github.com/jshttp/on-headers/releases) - [Changelog](https://github.com/jshttp/on-headers/blob/master/HISTORY.md) - [Commits](https://github.com/jshttp/on-headers/compare/v1.0.2...v1.1.0) Updates `morgan` from 1.10.0 to 1.10.1 - [Release notes](https://github.com/expressjs/morgan/releases) - [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/morgan/compare/1.10.0...1.10.1) --- updated-dependencies: - dependency-name: on-headers dependency-version: 1.1.0 dependency-type: indirect - dependency-name: morgan dependency-version: 1.10.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../hr_app/package-lock.json | 647 ++++++++++++------ .../hr_app/package.json | 2 +- 2 files changed, 452 insertions(+), 197 deletions(-) diff --git a/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json b/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json index ca9567d8..576dc631 100644 --- a/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json +++ b/javascript/rest-api/part-2-database-basics/hr_app/package-lock.json @@ -1,36 +1,51 @@ { "name": "hr_app", "version": "0.1.1", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "accepts": { + "packages": { + "": { + "version": "0.1.1", + "license": "Apache-2.0", + "dependencies": { + "express": "^4.21.2", + "morgan": "^1.10.1", + "oracledb": "^5.1.0" + } + }, + "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { + "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" } }, - "array-flatten": { + "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "basic-auth": { + "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { + "dependencies": { "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" } }, - "body-parser": { + "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "requires": { + "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", @@ -44,141 +59,204 @@ "type-is": "~1.6.18", "unpipe": "1.0.0" }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - } + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "bytes": { + "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } }, - "call-bind-apply-helpers": { + "node_modules/call-bind-apply-helpers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "requires": { + "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "call-bound": { + "node_modules/call-bound": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "requires": { + "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "content-disposition": { + "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { + "dependencies": { "safe-buffer": "5.2.1" }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } + "engines": { + "node": ">= 0.6" } }, - "content-type": { + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } }, - "cookie": { + "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } }, - "cookie-signature": { + "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "debug": { + "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { + "dependencies": { "ms": "2.0.0" } }, - "depd": { + "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } }, - "destroy": { + "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, - "dunder-proto": { + "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "requires": { + "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, - "ee-first": { + "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, - "encodeurl": { + "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } }, - "es-define-property": { + "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } }, - "es-errors": { + "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } }, - "es-object-atoms": { + "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "requires": { + "dependencies": { "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" } }, - "escape-html": { + "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, - "etag": { + "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } }, - "express": { + "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "requires": { + "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", @@ -211,27 +289,49 @@ "utils-merge": "1.0.1", "vary": "~1.1.2" }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } - } + ] }, - "finalhandler": { + "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "requires": { + "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -240,37 +340,50 @@ "statuses": "2.0.1", "unpipe": "~1.0.0" }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - } + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "forwarded": { + "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } }, - "fresh": { + "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } }, - "function-bind": { + "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "get-intrinsic": { + "node_modules/get-intrinsic": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", - "requires": { + "dependencies": { "call-bind-apply-helpers": "^1.0.1", "dunder-proto": "^1.0.0", "es-define-property": "^1.0.1", @@ -281,204 +394,294 @@ "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "gopd": { + "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "has-symbols": { + "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "hasown": { + "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "requires": { + "dependencies": { "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "http-errors": { + "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { + "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" } }, - "iconv-lite": { + "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { + "dependencies": { "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "ipaddr.js": { + "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } }, - "math-intrinsics": { + "node_modules/math-intrinsics": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", - "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==" + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "engines": { + "node": ">= 0.4" + } }, - "media-typer": { + "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } }, - "merge-descriptors": { + "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "methods": { + "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } }, - "mime": { + "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } }, - "mime-db": { + "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } }, - "mime-types": { + "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { + "dependencies": { "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" } }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "license": "MIT", + "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", "on-finished": "~2.3.0", - "on-headers": "~1.0.2" + "on-headers": "~1.1.0" }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } + "engines": { + "node": ">= 0.8.0" } }, - "ms": { + "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "negotiator": { + "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } }, - "object-inspect": { + "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==" + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "on-finished": { + "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { + "dependencies": { "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "oracledb": { + "node_modules/oracledb": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-5.1.0.tgz", - "integrity": "sha512-/IpzG729lFj8NBsA0xaOijdf6ZO3VuJbebma/hSf+VUZoIyzI/mCzBX047ramb/W8yJ1SV/HdWgs+XEczbqsXg==" + "integrity": "sha512-/IpzG729lFj8NBsA0xaOijdf6ZO3VuJbebma/hSf+VUZoIyzI/mCzBX047ramb/W8yJ1SV/HdWgs+XEczbqsXg==", + "deprecated": "Outdated package", + "hasInstallScript": true, + "engines": { + "node": ">=10.16" + } }, - "parseurl": { + "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } }, - "path-to-regexp": { + "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, - "proxy-addr": { + "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { + "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" } }, - "qs": { + "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "requires": { + "dependencies": { "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "range-parser": { + "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } }, - "raw-body": { + "node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { + "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "send": { + "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "requires": { + "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -493,120 +696,172 @@ "range-parser": "~1.2.1", "statuses": "2.0.1" }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - } + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "serve-static": { + "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "requires": { + "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "setprototypeof": { + "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "side-channel": { + "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "requires": { + "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "side-channel-list": { + "node_modules/side-channel-list": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "requires": { + "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "side-channel-map": { + "node_modules/side-channel-map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "requires": { + "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "side-channel-weakmap": { + "node_modules/side-channel-weakmap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "requires": { + "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "statuses": { + "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } }, - "toidentifier": { + "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } }, - "type-is": { + "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { + "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" } }, - "unpipe": { + "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } }, - "utils-merge": { + "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } }, - "vary": { + "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } } } } diff --git a/javascript/rest-api/part-2-database-basics/hr_app/package.json b/javascript/rest-api/part-2-database-basics/hr_app/package.json index 136a672d..80632a4a 100644 --- a/javascript/rest-api/part-2-database-basics/hr_app/package.json +++ b/javascript/rest-api/part-2-database-basics/hr_app/package.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "express": "^4.21.2", - "morgan": "^1.10.0", + "morgan": "^1.10.1", "oracledb": "^5.1.0" } } From ae815a34f5c2f5513200643b1dc63d22e6652b20 Mon Sep 17 00:00:00 2001 From: richardcevans Date: Mon, 29 Sep 2025 12:24:22 -0500 Subject: [PATCH 6/8] added data redaction demo scripts (#454) * added data redaction demo scripts * minor updates updated to check for HR user and updated README with clarifying steps. --- security/data-redaction/README.md | 55 +++++++++ security/data-redaction/demo.sql | 187 ++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 security/data-redaction/README.md create mode 100644 security/data-redaction/demo.sql diff --git a/security/data-redaction/README.md b/security/data-redaction/README.md new file mode 100644 index 00000000..ea5bf169 --- /dev/null +++ b/security/data-redaction/README.md @@ -0,0 +1,55 @@ +# Demo: Exploring Data Redaction Enhancements in Oracle Database 23ai + +This demo script showcases new **Oracle Data Redaction** capabilities in Oracle Database 23ai (23.6+). +It walks through creating and altering redaction policies, applying them to tables, virtual columns, indexes, and views, and observing how results differ for **HR** versus non-HR users. + +## Requirements + +- **Oracle Database 23ai 23.6+** +- **HR sample schema** installed from [Oracle Sample Schemas](https://github.com/oracle-samples/db-sample-schemas/releases) +- Privileges to run `DBMS_REDACT` and alter objects in the HR schema (recommended: run as HR) + + +## Script Sections + +### 0. Verify Setup +- Confirms **HR schema** exists +- Confirms **Database version ≥ 23** +- Script exits if either requirement is not met + +### 1. Basic Redaction Policy +- Creates a policy on `HR.EMPLOYEES` +- Redacts data for all users except HR + +### 2. Mathematical and Set Operators +- Adds redaction to the `SALARY` column +- Demonstrates impact on `GROUP BY` queries (e.g., average salary) + +### 3. Redaction with GROUP BY and ORDER BY +- Queries team statistics by manager +- Shows difference in totals/order for HR vs non-HR users + +### 4. Redacting Virtual Columns & Function-Based Indexes +- Creates a function-based index on `PHONE_NUMBER` +- Adds a virtual column (`ROUNDED_SALARY`) +- Applies redaction to `PHONE_NUMBER` +- Updates default redaction values (e.g., numbers -> 0, chars -> X) +- Queries redacted results + +### 5. Redaction in Views with Expressions +- Creates a view `HR.EMPLOYEE_VIEW` with calculated columns +- Redacts `FIRST_NAME` (full) +- Redacts `EMAIL` using **regular expression replacement** +- Queries the view to illustrate differences for HR vs non-HR users + +--- + +## How to Run +1. Connect as a user with privileges (e.g., `HR`) in **SQL*Plus** or **SQL Developer** +2. Execute the script + + +## To learn more about Oracle Data Redaction: +- Visit the Advanced Security product page (https://www.oracle.com/security/database-security/advanced-security/#redact-data) on the Oracle website. +- For hands-on experience, try our free, interactive Oracle Data Redaction LiveLab (https://apexapps.oracle.com/pls/apex/r/dbpm/livelabs/view-workshop?wid=4061&clear=RR,180&session=1856055320747). + diff --git a/security/data-redaction/demo.sql b/security/data-redaction/demo.sql new file mode 100644 index 00000000..f47ef840 --- /dev/null +++ b/security/data-redaction/demo.sql @@ -0,0 +1,187 @@ +-------------------------------------------------------------------------------- +-- DEMO SCRIPT: Exploring Data Redaction Enhancements in Oracle Database 23ai +-- Requirements: +-- (1) Oracle Database 23ai 23.6+ +-- (2) HR sample schema installed from https://github.com/oracle-samples/db-sample-schemas/releases +-- (3) Privileges to run DBMS_REDACT and alter HR objects (or run as HR) +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- 0. Verify setup +-------------------------------------------------------------------------------- +SET SERVEROUTPUT ON; + +DECLARE + v_count NUMBER; + v_version NUMBER; +BEGIN + -- Check HR schema + SELECT COUNT(*) INTO v_count FROM all_users WHERE username = 'HR'; + IF v_count = 0 THEN + RAISE_APPLICATION_ERROR(-20001, 'ERROR: HR schema not found. Exiting.'); + END IF; + + -- Check DB version >= 23 + SELECT TO_NUMBER(REGEXP_SUBSTR(version, '^[0-9]+')) INTO v_version FROM v$instance; + IF v_version < 23 THEN + RAISE_APPLICATION_ERROR(-20002, 'ERROR: Requires DB version 23 or higher. Exiting.'); + END IF; +END; +/ +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +-- 1. Create a basic redaction policy +-- Redact data for all users except HR +-------------------------------------------------------------------------------- +BEGIN + DBMS_REDACT.ADD_POLICY( + object_schema => 'HR', + object_name => 'EMPLOYEES', + policy_name => 'REDACT_DATA', + expression => 'SYS_CONTEXT(''USERENV'',''SESSION_USER'') != ''HR''' + ); +END; +/ +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +-- 2. Mathematical and set operators +-- Alter redaction policy to include SALARY column for full redaction +-------------------------------------------------------------------------------- +BEGIN + DBMS_REDACT.ALTER_POLICY( + object_schema => 'HR', + object_name => 'EMPLOYEES', + policy_name => 'REDACT_DATA', + column_name => 'SALARY', + action => DBMS_REDACT.ADD_COLUMN, + function_type => DBMS_REDACT.FULL + ); +END; +/ +-------------------------------------------------------------------------------- + +-- Query employee statistics +SELECT department_id AS dept_id, + COUNT(employee_id) AS emp_count, + AVG(salary) AS avg_salary +FROM hr.employees +GROUP BY department_id +FETCH FIRST 5 ROWS ONLY; +-------------------------------------------------------------------------------- +-- HR user sees real values; non-HR sees 0 for avg_salary +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +-- 3. Redaction with GROUP BY and ORDER BY +-------------------------------------------------------------------------------- +SELECT manager_id, + COUNT(DISTINCT employee_id) AS direct_reports, + SUM(salary) AS total_team_salary +FROM hr.employees +GROUP BY manager_id +ORDER BY total_team_salary DESC +FETCH FIRST 5 ROWS ONLY; +-------------------------------------------------------------------------------- +-- HR sees totals; non-HR sees 0 (rows may order differently) +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +-- 4. Redacting virtual columns in function-based indexes +-------------------------------------------------------------------------------- + +-- Step 1: Create a function-based index (removes periods and hyphens) +CREATE INDEX hr.phone_number_idx + ON hr.employees( + REPLACE(REPLACE(phone_number, '.', ''), '-', '') + ); + +-- Step 2: Add a virtual column for rounded salary +ALTER TABLE hr.employees ADD ( + rounded_salary AS (ROUND(salary, -3)) +); + +-- Step 3: Apply redaction to PHONE_NUMBER +BEGIN + DBMS_REDACT.ALTER_POLICY( + object_schema => 'HR', + object_name => 'EMPLOYEES', + column_name => 'PHONE_NUMBER', + policy_name => 'REDACT_DATA', + action => DBMS_REDACT.ADD_COLUMN, + function_type => DBMS_REDACT.FULL + ); +END; +/ + +-- Step 4: Update default redaction values +BEGIN + DBMS_REDACT.UPDATE_FULL_REDACTION_VALUES( + number_value => 0, + char_value => 'X' + ); +END; +/ + +-- Step 5: Query redacted virtual columns +SELECT employee_id, phone_number, rounded_salary +FROM hr.employees +WHERE employee_id IN (101, 103, 176, 201); +-------------------------------------------------------------------------------- + + +-------------------------------------------------------------------------------- +-- 5. Redaction in Views with Expressions +-------------------------------------------------------------------------------- + +-- Step 1: Define a view +CREATE OR REPLACE VIEW hr.employee_view AS + SELECT employee_id AS EMP_ID, + ROUND(MONTHS_BETWEEN(SYSDATE, hire_date)/12) AS YEARS_OF_SERVICE, + CONCAT(UPPER(first_name), ' ', UPPER(last_name)) AS FULL_NAME, + EMAIL + FROM hr.employees; + +-- Step 2: Apply redaction to FIRST_NAME +BEGIN + DBMS_REDACT.ALTER_POLICY( + object_schema => 'HR', + object_name => 'EMPLOYEES', + policy_name => 'REDACT_DATA', + column_name => 'FIRST_NAME', + action => DBMS_REDACT.ADD_COLUMN, + function_type => DBMS_REDACT.FULL + ); +END; +/ + +-- Step 3: Apply redaction to EMAIL using regexp +BEGIN + DBMS_REDACT.ALTER_POLICY( + object_schema => 'HR', + object_name => 'EMPLOYEES', + policy_name => 'REDACT_DATA', + column_name => 'EMAIL', + action => DBMS_REDACT.ADD_COLUMN, + function_type => DBMS_REDACT.REGEXP, + regexp_pattern => '^.*$', + regexp_replace_string => 'xxxx@company.com', + regexp_position => 1, + regexp_occurrence => 1, + regexp_match_parameter => 'i' + ); +END; +/ + +-- Step 4: Query the view +SELECT emp_id, years_of_service, full_name, email +FROM hr.employee_view +WHERE emp_id IN (101, 103, 176, 201); +-------------------------------------------------------------------------------- +-- HR sees names/emails; non-HR sees X and xxxx@company.com +-------------------------------------------------------------------------------- From 61a06c09a5d3e68cd99c91f60699a92877eb5f8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 09:58:56 -0800 Subject: [PATCH 7/8] Bump ch.qos.logback:logback-core in /sagas/CloudBank-DEMO (#456) Bumps [ch.qos.logback:logback-core](https://github.com/qos-ch/logback) from 1.5.13 to 1.5.19. - [Release notes](https://github.com/qos-ch/logback/releases) - [Commits](https://github.com/qos-ch/logback/compare/v_1.5.13...v_1.5.19) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-core dependency-version: 1.5.19 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- sagas/CloudBank-DEMO/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sagas/CloudBank-DEMO/pom.xml b/sagas/CloudBank-DEMO/pom.xml index bcd775f3..1f92dabe 100644 --- a/sagas/CloudBank-DEMO/pom.xml +++ b/sagas/CloudBank-DEMO/pom.xml @@ -65,7 +65,7 @@ ch.qos.logback logback-core - 1.5.13 + 1.5.19 ch.qos.logback From ff04c27e7ed21acdac4a2938dd72ad149df2cca6 Mon Sep 17 00:00:00 2001 From: Sharad Chandran R Date: Fri, 14 Nov 2025 02:00:05 +0530 Subject: [PATCH 8/8] Add precompiler (Pro*C and Pro*COBOL demos) (#457) * Create simpleConnDemo.c Basic Connection and Query execution to Oracle Database * Update and rename drcp.c to drcpDemo.c Update the username and password to a generic string * Update makefile Make the makefile generic * Update simpleConnDemo.c Add the required setup and related blog details * Update simpleConnDemo.c Remove blog details. To be updated in README * Create sessionPoolingDemo Demo for OCI Session Pooling and Multithreading * Update README.md Add more details in the README.md file * Update README.md Minor change * Rename sessionPoolingDemo to sessionPoolingDemo.c Add .c extension in the file name * Update makefile Make the makefile more correct * Update makefile Add some comments * Add precompiler demos * Add precompilers section to main README --- README.md | 1 + precompilers/Readme.md | 41 +++++++++++ precompilers/makefile_proc.mk | 123 +++++++++++++++++++++++++++++++ precompilers/makefile_procob.mk | 96 ++++++++++++++++++++++++ precompilers/procdemo.pc | 125 ++++++++++++++++++++++++++++++++ precompilers/procobdemo.pco | 99 +++++++++++++++++++++++++ 6 files changed, 485 insertions(+) create mode 100644 precompilers/Readme.md create mode 100644 precompilers/makefile_proc.mk create mode 100644 precompilers/makefile_procob.mk create mode 100644 precompilers/procdemo.pc create mode 100644 precompilers/procobdemo.pco diff --git a/README.md b/README.md index 2e24d8a3..d3db05b8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This repository stores a variety of examples demonstrating how to use the Oracle | [json-relational-duality](./json-relational-duality) | JSON Relational Duality examples | [machine-learning](./machine-learning) | Oracle Machine Learning examples | | [optimizer](./optimizer) | Oracle Optimizer and Optimizer Stats examples | +| [precompilers](./precompilers) | Oracle Pro\*C and Pro\*COBOL precompiler examples | | [plsql](./plsql) | PL/SQL examples | | [python](./python) | Python examples | | [ruby](./ruby) | Ruby examples | diff --git a/precompilers/Readme.md b/precompilers/Readme.md new file mode 100644 index 00000000..d2ee4bc2 --- /dev/null +++ b/precompilers/Readme.md @@ -0,0 +1,41 @@ +# Precompiler Examples + +This directory contains sample programs illustrating the use of Oracle Precompilers (Pro*C and Pro*COBOL). +It also includes makefiles for building and compiling these samples into executables. + +## Prerequisites + +### Oracle Packages / Components + +If you are using the Oracle Instant Client, install the following packages for your platform: + +- **Basic** +- **SDK** +- **Precompiler** + +### Compilers + +- A **C/C++ compiler** is required for the Pro*C samples. +- A **COBOL compiler** is required for the Pro*COBOL samples. + +### Minimum Required Version + +The minimum required **Oracle Database** and **Oracle Client** versions are **19c** in both cases. + +## Included Files + +| File Name | Description | +|-----------------------|-----------------------------------------------------------------------------------------------| +| [`procdemo.pc`](./procdemo.pc) | Sample Pro*C program demonstrating basic database operations using Oracle Precompiler. | +| [`procobdemo.pco`](./procobdemo.pco) | Sample Pro*COBOL program illustrating database interaction using Oracle Precompiler. | +| [`makefile_proc.mk`](./makefile_proc.mk) | Makefile for compiling the Pro*C example, managing dependencies, and generating the executable. | +| [`makefile_procob.mk`](./makefile_procob.mk) | Makefile for compiling the Pro*COBOL example, handling build processes, and producing the executable. | + +## How to Compile and Run + +To build the sample programs: + +- Use `make -f makefile_proc.mk` for **Pro*C** samples. +- Use `make -f makefile_procob.mk` for **Pro*COBOL** samples. + +Refer to the respective makefiles for more detailed instructions on compilation and execution steps. diff --git a/precompilers/makefile_proc.mk b/precompilers/makefile_proc.mk new file mode 100644 index 00000000..9223fe99 --- /dev/null +++ b/precompilers/makefile_proc.mk @@ -0,0 +1,123 @@ +############################################################################### +# Make file for PROC demos +############################################################################### +# Usage : +# For compiling proc demos +# make -f makefile_proc.mk +# +# For precompiling, compiling & linking the procdemo.pc file +# make -f makefile_proc.mk build EXE=procdemo OBJS=procdemo.o +# +# In general, for any proc program +# make -f makefile_proc.mk build EXE= OBJS="" +# +# To make use of any PROC options during precompilation, +# make -f makefile_proc.mk build PROCFLAGS="" +# EXE= OBJS="" +# +# NOTES: +# 1. Please change "cc/CC" and the "InstantClient directories" to point to +# appropiate locations on your machine before using this makefile. +# 2. In case of RPM installation, please change the following variables +# as mentioned below: +# PROC=/usr/lib/oracle/VV.v/client/bin/proc +# CCINCLUDES=$(I_SYM)/usr/include/oracle/VV.v/client +# PRECOMPPUBH=/usr/include/oracle/VV.v/client +# ICLIBHOME=/usr/lib/oracle/VV.v/client/lib/ +# Legend: +# VV - Major Oracle version number +# v - Minor Oracle version number +# (Ex: For the release 11.2, VV = 11 and v = 2) +# +############################################################################### + + +CC=/usr/bin/gcc +cc=/usr/bin/gcc + +# InstantClient Directories. +ICSDKHOME=../ +ICLIBHOME=../../ + +MKLINK=ln +REMOVE=rm -rf +CLNCACHE=cleancache +CACHEDIR=SunWS_cachea +MAKE=make +MAKEFILE=makefile_proc.mk +PROCDEMO=procdemo + +PROC=$(ICSDKHOME)/proc +SO_EXT=.so +I_SYM=-I + +CCINCLUDES= $(I_SYM)$(ICSDKHOME)/include + +# Pre-compiler Flags. +PRECOMPPUBH=$(ICSDKHOME)include + +# Compiler Flags. +OPTIMIZE=-O2 +LDPATHFLAG=-L +SPFLAGS=-DLINUX -D_GNU_SOURCE -D_LARGEFILE64_SOURCE=1 -D_LARGEFILE_SOURCE=1 -DSLTS_ENABLE -DSLMXMX_ENABLE -D_REENTRANT -DNS_THREADS +CCFLAGS= -fPIC -DPRECOMP +LDFLAGS=-g +LPFLAGS= +GFLAG= +CDEBUG= +USRFLAGS= +ICLIBPATH=$(LDPATHFLAG)$(ICLIBHOME) +PFLAGS=$(CCINCLUDES) $(SPFLAGS) $(LPFLAGS) +CFLAGS=$(GFLAG) $(OPTIMIZE) $(CDEBUG) $(CCFLAGS) $(PFLAGS) $(USRFLAGS) + +# Libraries. +PROLDLIBS=$(LDCLIENTLIBS) $(THREADLIBS) +LDCLIENTLIBS=$(ICLIBPATH) $(LLIBCLNTSH) $(LDLIBS) +LLIBCLNTSH=$(LDLIBFLAG)$(LIBCLNTSHNAME) +LDLIBFLAG=-l +LIBCLNTCORENAME=clntshcore +LIBCLNTSHNAME=clntsh +LDLIBS=$(EXSYSLIBS) $(MATHLIB) $(USRLIBS) +EXSYSLIBS=-ldl +MATHLIB=-lm +THREADLIBS=-lpthread + +C2O=$(CC) $(CFLAGS) -c $*.c +PCC2C=$(PROC) $(PROCFLAGS) iname=$(PCCSRC) +DEMO_PROC_BUILD=$(CC) -o $(EXE) $(OBJS) $(LDFLAGS) $(PROLDLIBS) + +#----------------------------------------------------------------------------- +# Targets for building the proc sample programs. +all: clean $(PROCDEMO) + +$(PROCDEMO): + $(MAKE) -f $(MAKEFILE) build OBJS=$@.o EXE=$@ + +build: $(CLNCACHE) $(OBJS) + $(DEMO_PROC_BUILD) + +#----------------------------------------------------------------------------- +# Here are some rules for converting .pc -> .c -> .o +.SUFFIXES: .pc .c .o + +pc1: + $(PCC2C) + +.pc.c: + $(MAKE) -f $(MAKEFILE) PROCFLAGS="$(PROCFLAGS)" PCCSRC=$* I_SYM=include= pc1 + +.pc.o: + $(MAKE) -f $(MAKEFILE) PROCFLAGS="$(PROCFLAGS)" PCCSRC=$* I_SYM=include= pc1 + $(C2O) + +.c.o: + $(C2O) + +#----------------------------------------------------------------------------- +# Clean up all executables, *.o and generated *.c files +clean: $(CLNCACHE) + $(REMOVE) $(PROCDEMO) $(PROCDEMO).o $(PROCDEMO).c $(PROCDEMO).lis + +cleancache: + $(REMOVE) $(CACHEDIR) + diff --git a/precompilers/makefile_procob.mk b/precompilers/makefile_procob.mk new file mode 100644 index 00000000..e4eeb493 --- /dev/null +++ b/precompilers/makefile_procob.mk @@ -0,0 +1,96 @@ +############################################################################### +# Make file for PROCOB demos +############################################################################### +# Usage : +# For compiling procob demos +# make -f makefile_procob.mk +# +# For precompiling, compiling & linking the procobdemo.pco file +# make -f makefile_procob.mk build EXE=procobdemo COBS=procobdemo.cob +# +# In general, for any procob program +# make -f makefile_procob.mk build EXE= COBS="" +# To make use of any PROCOB options during precompilation, +# make -f makefile_procob.mk build PROCOBFLAGS="" +# EXE= COBS="" +# +# NOTES: +# 1. Please change "COB" and the "InstantClient directories" to point to +# appropiate locations on your machine before using this makefile. +# 2. In case of RPM installation, please change the following variables +# as mentioned below: +# PROCOB=/usr/lib/oracle/VV.v/client/bin/procob +# ICLIBHOME=/usr/lib/oracle/VV.v/client/lib/ +# Legend: +# VV - Major Oracle version number +# v - Minor Oracle version number +# (Ex: For the release 18.1, VV = 12 and v = 1) +# +############################################################################### + +COB=cob + +# InstantClient Directories. +ICSDKHOME=../ +ICLIBHOME=../../ + +MKLINK=ln +REMOVE=rm -rf +CLNCACHE=cleancache +CACHEDIR=SunWS_cachea +MAKE=make +MAKEFILE=makefile_procob.mk +PROCOBDEMO=procobdemo + +PROCOB=$(ICSDKHOME)/procob +ICLIBPATH=$(LDPATHFLAG)$(ICLIBHOME) +SO_EXT=.so +COBFLAGS=-C IBMCOMP -C NESTCALL -t -x +LDPATHFLAG=-L +COBSQLINTF=$(ICLIBHOME)cobsqlintf.o +LDLIBS=$(EXSYSLIBS) $(MATHLIB) $(USRLIBS) +EXSYSLIBS=-ldl +MATHLIB=-lm +COBOL_PROLDLIBS=$(SHARED_CLIENTLIBS) $(LDLIBS) +SHARED_CLIENTLIBS=$(LLIBCLNTSH) $(LDFLAGS) +LLIBCLNTSH=$(LDLIBFLAG)$(LIBCLNTSHNAME) +LDLIBFLAG=-l +LIBCLNTCORENAME=clntshcore +LIBCLNTSHNAME=clntsh +LDFLAGS=-g + +DEMO_PROCOB_BUILD=$(COB) $(COBFLAGS) -o $(EXE) $(COBS) $(ICLIBPATH) $(COBSQLINTF) $(COBOL_PROLDLIBS) + +#----------------------------------------------------------------------------- +# Targets for building the procob sample programs. +# +# The target 'build' puts together an executable $(EXE) from the cobol +# sources in $(COBS) and the libraries in $(COBOL_PROLDLIBS). +# The rules to make .cob files from .pco files are later in this file. +# +all: clean $(PROCOBDEMO) + +$(PROCOBDEMO): + $(MAKE) -f $(MAKEFILE) build COBS=$@.cob EXE=$@ + +build: $(CLNCACHE) $(COBS) + $(DEMO_PROCOB_BUILD) + +#----------------------------------------------------------------------------- +# Here are some rules for converting .pco -> .cob -> .o and for .cob -> .gnt. +# +.SUFFIXES: .cob .cbl .o .pco $(GNT) + +.pco.cob: + $(PROCOB) $(PROCOBFLAGS) iname=$*.pco + +.cob$(GNT): + $(COB2GNT) + +#----------------------------------------------------------------------------- +# Clean up all executables, *.o and generated *.cob files +clean: $(CLNCACHE) + $(REMOVE) $(PROCOBDEMO) $(PROCOBDEMO).o $(PROCOBDEMO).cob $(PROCDEMO).lis $(PROCOBDEMO).int $(PROCOBDEMO).idy + +cleancache: + $(REMOVE) $(CACHEDIR) diff --git a/precompilers/procdemo.pc b/precompilers/procdemo.pc new file mode 100644 index 00000000..a2f35ff4 --- /dev/null +++ b/precompilers/procdemo.pc @@ -0,0 +1,125 @@ +/* Copyright (c) 2025, Oracle and/or its affiliates.*/ +/* All rights reserved.*/ + +/* NAME + * procdemo.pc - Pro*C demo program + * + * DESCRIPTION + * This program connects to ORACLE, declares and opens a cursor, + * fetches the names, salaries, and commissions of all + * salespeople, displays the results, then closes the cursor. + * + */ + +#include +#include +#include +#include +#include +#include + +#define UNAME_PWD_LEN 256 + +/* + * Use the precompiler typedef'ing capability to create + * null-terminated strings for the authentication host + * variables. (This isn't really necessary--plain char *'s + * would work as well. This is just for illustration.) + */ +typedef char asciiz[UNAME_PWD_LEN]; + +EXEC SQL TYPE asciiz IS CHARZ(UNAME_PWD_LEN) REFERENCE; +asciiz username; +asciiz password; + +struct emp_info +{ + asciiz emp_name; + float salary; + float commission; +}; + +void sql_error(msg) + char *msg; +{ + char err_msg[512]; + size_t buf_len, msg_len; + + EXEC SQL WHENEVER SQLERROR CONTINUE; + + printf("\n%s\n", msg); + +/* Call sqlglm() to get the complete text of the + * error message. + */ + buf_len = sizeof (err_msg); + sqlglm(err_msg, &buf_len, &msg_len); + printf("%.*s\n", msg_len, err_msg); + + EXEC SQL ROLLBACK RELEASE; + exit(EXIT_FAILURE); +} + +void main() +{ + struct emp_info *emp_rec_ptr; + +/* Allocate memory for emp_info struct. */ + if ((emp_rec_ptr = + (struct emp_info *) malloc(sizeof(struct emp_info))) == 0) + { + fprintf(stderr, "Memory allocation error.\n"); + exit(EXIT_FAILURE); + } + +/* application user to set the database credentials here */ +/* CAUTION: Username and password buffers are 256 characters. */ +/* Do not paste credentials longer than 256 characters. */ + + strcpy(username, ""); + strcpy(password, ""); + + EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--"); + + EXEC SQL CONNECT :username IDENTIFIED BY :password; + printf("\nConnected to ORACLE as user: %s\n", username); + +/* Declare the cursor. All static SQL explicit cursors + * contain SELECT commands. 'salespeople' is a SQL identifier, + * not a (C) host variable. + */ + EXEC SQL DECLARE salespeople CURSOR FOR + SELECT ENAME, SAL, COMM + FROM EMP + WHERE JOB LIKE 'SALES%'; + +/* Open the cursor. */ + EXEC SQL OPEN salespeople; + +/* Get ready to print results. */ + printf("\n\nThe company's salespeople are--\n\n"); + printf("Salesperson Salary Commission\n"); + printf("----------- ------ ----------\n"); + +/* Loop, fetching all salesperson's statistics. + * Cause the program to break the loop when no more + * data can be retrieved on the cursor. + */ + EXEC SQL WHENEVER NOT FOUND DO break; + + for (;;) + { + EXEC SQL FETCH salespeople INTO :emp_rec_ptr; + printf("%s %9.2f %12.2f\n", emp_rec_ptr->emp_name, + emp_rec_ptr->salary, emp_rec_ptr->commission); + } + +/* Close the cursor. */ + EXEC SQL CLOSE salespeople; + + printf("\nGOOD-BYE!!\n\n"); + + EXEC SQL COMMIT WORK RELEASE; + exit(EXIT_SUCCESS); +} + diff --git a/precompilers/procobdemo.pco b/precompilers/procobdemo.pco new file mode 100644 index 00000000..5e947b6f --- /dev/null +++ b/precompilers/procobdemo.pco @@ -0,0 +1,99 @@ + ***************************************************************** + * Copyright (c) 2025, Oracle and/or its affiliates.. * + * All rights reserved. * + * * + * procobdemo.pco - Pro*COBOL demo file. * + * * + * DESCRIPTION: * + * This program logs on to ORACLE, declares and opens a cursor, * + * fetches the names, salaries, and commissions of all * + * salespeople, displays the results, then closes the cursor. * + ***************************************************************** + + IDENTIFICATION DIVISION. + PROGRAM-ID. CURSOR-OPS. + ENVIRONMENT DIVISION. + DATA DIVISION. + WORKING-STORAGE SECTION. + + EXEC SQL BEGIN DECLARE SECTION END-EXEC. + 01 USERNAME PIC X(256) VARYING. + 01 PASSWD PIC X(256) VARYING. + 01 EMP-REC-VARS. + 05 EMP-NAME PIC X(10) VARYING. + 05 SALARY PIC S9(6)V99 + DISPLAY SIGN LEADING SEPARATE. + 05 COMMISSION PIC S9(6)V99 + DISPLAY SIGN LEADING SEPARATE. + EXEC SQL VAR SALARY IS DISPLAY(8,2) END-EXEC. + EXEC SQL VAR COMMISSION IS DISPLAY(8,2) END-EXEC. + EXEC SQL END DECLARE SECTION END-EXEC. + + EXEC SQL INCLUDE SQLCA END-EXEC. + + 01 DISPLAY-VARIABLES. + 05 D-EMP-NAME PIC X(10). + 05 D-SALARY PIC Z(4)9.99. + 05 D-COMMISSION PIC Z(4)9.99. + + PROCEDURE DIVISION. + + BEGIN-PGM. + EXEC SQL WHENEVER SQLERROR + DO PERFORM SQL-ERROR END-EXEC. + PERFORM LOGON. + EXEC SQL DECLARE SALESPEOPLE CURSOR FOR + SELECT ENAME, SAL, COMM + FROM EMP + WHERE JOB LIKE 'SALES%' + END-EXEC. + EXEC SQL OPEN SALESPEOPLE END-EXEC. + DISPLAY " ". + DISPLAY "SALESPERSON SALARY COMMISSION". + DISPLAY "----------- ---------- ----------". + + FETCH-LOOP. + EXEC SQL WHENEVER NOT FOUND + DO PERFORM SIGN-OFF END-EXEC. + EXEC SQL FETCH SALESPEOPLE + INTO :EMP-NAME, :SALARY, :COMMISSION + END-EXEC. + MOVE EMP-NAME-ARR TO D-EMP-NAME. + MOVE SALARY TO D-SALARY. + MOVE COMMISSION TO D-COMMISSION. + DISPLAY D-EMP-NAME, " ", D-SALARY, " ", D-COMMISSION. + MOVE SPACES TO EMP-NAME-ARR. + GO TO FETCH-LOOP. + + LOGON. + ***************************************************************** + * Application user: Set the database credentials here. * + * CAUTION: Username and password buffers are 256 bytes. * + * Do not paste credentials longer than 256 characters. * + ***************************************************************** + MOVE "" TO USERNAME-ARR. + MOVE LENGTH OF "" TO USERNAME-LEN. + MOVE "" TO PASSWD-ARR. + MOVE LENGTH OF "" TO PASSWD-LEN. + EXEC SQL + CONNECT :USERNAME IDENTIFIED BY :PASSWD + END-EXEC. + DISPLAY " ". + DISPLAY "CONNECTED TO ORACLE AS USER: ", USERNAME-ARR. + + SIGN-OFF. + EXEC SQL CLOSE SALESPEOPLE END-EXEC. + DISPLAY " ". + DISPLAY "HAVE A GOOD DAY.". + DISPLAY " ". + EXEC SQL COMMIT WORK RELEASE END-EXEC. + STOP RUN. + + SQL-ERROR. + EXEC SQL WHENEVER SQLERROR CONTINUE END-EXEC. + DISPLAY " ". + DISPLAY "ORACLE ERROR DETECTED:". + DISPLAY " ". + DISPLAY SQLERRMC. + EXEC SQL ROLLBACK WORK RELEASE END-EXEC. + STOP RUN.