Skip to content

Commit 4d487c5

Browse files
Add AQ support in thin mode for single enqueue and dequeue of RAW and
Oracle object payload types (#437).
1 parent 084ecf0 commit 4d487c5

File tree

19 files changed

+1393
-32
lines changed

19 files changed

+1393
-32
lines changed

doc/src/release_notes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ oracledb 3.0.0 (TBD)
1717
Thin Mode Changes
1818
+++++++++++++++++
1919

20+
#) Added :ref:`Oracle Advanced Queuing <aqusermanual>` support for single
21+
enqueue and dequeue of RAW and Oracle object payload types.
2022
#) Added namespace package :ref:`oracledb.plugins <plugins>` for plugins that
2123
can be used to extend the capability of python-oracledb.
2224
#) Added support for property :attr:`ConnectionPool.max_lifetime_session`

doc/src/user_guide/appendix_a.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ see :ref:`driverdiff` and :ref:`compatibility`.
248248
- Yes
249249
- Yes
250250
* - Oracle Transactional Event Queues and Advanced Queuing (AQ) (see :ref:`aqusermanual`)
251-
- No
251+
- Yes - RAW and named Oracle object payloads
252252
- Yes
253253
- Yes
254254
* - Call timeouts (see :attr:`Connection.call_timeout`)

doc/src/user_guide/aq.rst

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ receiving of various payloads, such as RAW values, JSON, JMS, and objects.
1515
Transactional Event Queues use a highly optimized implementation of Advanced
1616
Queuing. They were previously called AQ Sharded Queues.
1717

18-
.. note::
19-
20-
TxEventQ and AQ Classic queues are only supported in python-oracledb Thick
21-
mode. See :ref:`enablingthick`.
2218

2319
Python-oracledb API calls are the same for Transactional Event Queues and
2420
Classic Queues, however there are differences in support for some payload
@@ -31,11 +27,18 @@ types.
3127
- The JSON payload requires Oracle Client libraries 21c (or later) and Oracle
3228
Database 21c (or later).
3329

34-
There are examples of AQ Classic Queues in the `GitHub examples
30+
JSON and JMS payloads, array message queuing and dequeuing operations, and
31+
:ref:`Recipient Lists <reciplists>` are only supported in python-oracledb
32+
:ref:`Thick mode <enablingthick>`.
33+
34+
There are examples of AQ Classic Queues in the `GitHub samples
3535
<https://github.com/oracle/python-oracledb/tree/main/samples>`__ directory.
3636

3737
**Transactional Event Queue Support**
3838

39+
Transactional Event Queues are only supported in python-oracledb :ref:`Thick
40+
mode <enablingthick>`.
41+
3942
- RAW and named Oracle object payloads are supported for single and array
4043
message enqueuing and dequeuing when using Oracle Client 19c (or later) and
4144
connected to Oracle Database 19c (or later).
@@ -55,7 +58,15 @@ Creating a Queue
5558

5659
Before being used in applications, queues need to be created in the database.
5760

58-
**Using RAW Payloads**
61+
To experiment with queueing, you can grant yourself privileges, for example in
62+
SQL*Plus as a DBA user:
63+
64+
.. code-block:: sql
65+
66+
grant aq_administrator_role, aq_user_role to &&username;
67+
grant execute on dbms_aq to &&username;
68+
69+
**Creating RAW Payload Queues**
5970

6071
To use SQL*Plus to create a Classic Queue for the RAW payload which is suitable
6172
for sending string or bytes messages:
@@ -79,7 +90,7 @@ To create a Transactional Event Queue for RAW payloads:
7990
end;
8091
/
8192
82-
**Using JSON Payloads**
93+
**Creating JSON Payload Queues**
8394

8495
Queues can also be created for JSON payloads. For example, to create a Classic
8596
Queue in SQL*Plus:
@@ -99,7 +110,7 @@ Enqueuing Messages
99110
To send messages in Python, you connect and get a :ref:`queue <queue>`. The
100111
queue can then be used for enqueuing, dequeuing, or for both.
101112

102-
**Using RAW Payloads**
113+
**Enqueuing RAW Payloads**
103114

104115
You can connect to the database and get the queue that was created with RAW
105116
payload type by using:
@@ -123,13 +134,14 @@ messages:
123134
connection.commit()
124135
125136
Since the queue is a RAW queue, strings are internally encoded to bytes using
126-
``message.encode()`` before being enqueued.
137+
`encode() <https://docs.python.org/3/library/stdtypes.html#str.encode>`__
138+
before being enqueued.
127139

128-
The use of :meth:`~Connection.commit()` means that messages are sent only when
129-
any database transaction related to them is committed. This behavior can be
130-
altered, see :ref:`aqoptions`.
140+
The use of :meth:`~Connection.commit()` allows messages to be sent only when
141+
any database transaction related to them is committed. This default behavior
142+
can be altered, see :ref:`aqoptions`.
131143

132-
**Using JSON Payloads**
144+
**Enqueuing JSON Payloads**
133145

134146
You can connect to the database and get the queue that was created with JSON
135147
payload type by using:
@@ -162,9 +174,11 @@ Dequeuing Messages
162174
==================
163175

164176
Dequeuing is performed similarly. To dequeue a message call the method
165-
:meth:`~Queue.deqone()` as shown in the examples below.
177+
:meth:`~Queue.deqone()` as shown in the examples below. This returns a
178+
:ref:`MessageProperties <msgproperties>` object containing the message payload
179+
and related attributes.
166180

167-
**Using RAW Payloads**
181+
**Dequeuing RAW Payloads**
168182

169183
.. code-block:: python
170184
@@ -174,9 +188,21 @@ Dequeuing is performed similarly. To dequeue a message call the method
174188
print(message.payload.decode())
175189
176190
Note that if the message is expected to be a string, the bytes must be decoded
177-
by the application using ``message.payload.decode()``, as shown.
191+
by the application using `decode()
192+
<https://docs.python.org/3/library/stdtypes.html#bytes.decode>`__, as shown.
193+
194+
If there are no messages in the queue, :meth:`~Queue.deqone()` will wait for
195+
one to be enqueued. This default behavior can be altered, see
196+
:ref:`aqoptions`.
197+
198+
Various :ref:`message properties <msgproperties>` can be accessed. For example
199+
to show the :attr:`~MessageProperties.msgid` of a dequeued message:
200+
201+
.. code-block:: python
202+
203+
print(message.msgid.hex())
178204
179-
**Using JSON Payloads**
205+
**Dequeuing JSON Payloads**
180206

181207
.. code-block:: python
182208

samples/multi_consumer_aq.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2023, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
33
#
44
# Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved.
55
#
@@ -37,8 +37,9 @@
3737
import oracledb
3838
import sample_env
3939

40-
# this script is currently only supported in python-oracledb thick mode
41-
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
40+
# determine whether to use python-oracledb thin mode or thick mode
41+
if not sample_env.get_is_thin():
42+
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
4243

4344
QUEUE_NAME = "DEMO_RAW_QUEUE_MULTI"
4445
PAYLOAD_DATA = [

samples/object_aq.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (c) 2016, 2023, Oracle and/or its affiliates.
2+
# Copyright (c) 2016, 2025, Oracle and/or its affiliates.
33
#
44
# Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved.
55
#
@@ -39,8 +39,9 @@
3939
import oracledb
4040
import sample_env
4141

42-
# this script is currently only supported in python-oracledb thick mode
43-
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
42+
# determine whether to use python-oracledb thin mode or thick mode
43+
if not sample_env.get_is_thin():
44+
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
4445

4546
BOOK_TYPE_NAME = "UDT_BOOK"
4647
QUEUE_NAME = "DEMO_BOOK_QUEUE"

samples/raw_aq.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -----------------------------------------------------------------------------
2-
# Copyright (c) 2019, 2023, Oracle and/or its affiliates.
2+
# Copyright (c) 2019, 2025, Oracle and/or its affiliates.
33
#
44
# Portions Copyright 2007-2015, Anthony Tuininga. All rights reserved.
55
#
@@ -37,8 +37,9 @@
3737
import oracledb
3838
import sample_env
3939

40-
# this script is currently only supported in python-oracledb thick mode
41-
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
40+
# determine whether to use python-oracledb thin mode or thick mode
41+
if not sample_env.get_is_thin():
42+
oracledb.init_oracle_client(lib_dir=sample_env.get_oracle_client())
4243

4344
QUEUE_NAME = "DEMO_RAW_QUEUE"
4445
PAYLOAD_DATA = [

src/oracledb/base_impl.pxd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ cdef class Buffer:
317317
bint write_length=*) except -1
318318
cdef int write_oracle_number(self, bytes num_bytes) except -1
319319
cdef int write_raw(self, const char_type *data, ssize_t length) except -1
320+
cdef int write_sb4(self, int32_t value) except -1
320321
cdef int write_str(self, str value) except -1
321322
cdef int write_uint8(self, uint8_t value) except -1
322323
cdef int write_uint16be(self, uint16_t value) except -1
@@ -955,10 +956,13 @@ cdef object convert_oracle_data_to_python(OracleMetadata from_metadata,
955956
OracleData* data,
956957
const char* encoding_errors,
957958
bint from_dbobject)
959+
cdef object convert_date_to_python(OracleDataBuffer *buffer)
958960
cdef uint16_t decode_uint16be(const char_type *buf)
959961
cdef uint32_t decode_uint32be(const char_type *buf)
960962
cdef uint16_t decode_uint16le(const char_type *buf)
961963
cdef uint64_t decode_uint64be(const char_type *buf)
964+
cdef int decode_date(const uint8_t* ptr, ssize_t num_bytes,
965+
OracleDataBuffer *buffer)
962966
cdef void encode_uint16be(char_type *buf, uint16_t value)
963967
cdef void encode_uint16le(char_type *buf, uint16_t value)
964968
cdef void encode_uint32be(char_type *buf, uint32_t value)

src/oracledb/errors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ def _raise_not_supported(feature: str) -> None:
358358
ERR_UNKNOWN_SERVER_PIGGYBACK = 5009
359359
ERR_UNKNOWN_TRANSACTION_STATE = 5010
360360
ERR_UNEXPECTED_PIPELINE_FAILURE = 5011
361+
ERR_NOT_IMPLEMENTED = 5012
361362

362363
# error numbers that result in OperationalError
363364
ERR_LISTENER_REFUSED_CONNECTION = 6000
@@ -713,6 +714,7 @@ def _raise_not_supported(feature: str) -> None:
713714
ERR_NO_STATEMENT_PREPARED: "statement must be prepared first",
714715
ERR_NOT_A_QUERY: "the executed statement does not return rows",
715716
ERR_NOT_CONNECTED: "not connected to database",
717+
ERR_NOT_IMPLEMENTED: "not implemented",
716718
ERR_NUMBER_STRING_OF_ZERO_LENGTH: "invalid number: zero length string",
717719
ERR_NUMBER_STRING_TOO_LONG: "invalid number: string too long",
718720
ERR_NUMBER_WITH_EMPTY_EXPONENT: "invalid number: empty exponent",

src/oracledb/impl/base/buffer.pyx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2024, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -791,6 +791,26 @@ cdef class Buffer:
791791
length -= bytes_to_write
792792
data += bytes_to_write
793793

794+
cdef int write_sb4(self, int32_t value) except -1:
795+
"""
796+
Writes a 32-bit signed integer to the buffer in universal format.
797+
"""
798+
cdef uint8_t sign = 0
799+
if value < 0:
800+
value = -value
801+
sign = 0x80
802+
if value == 0:
803+
self.write_uint8(0)
804+
elif value <= UINT8_MAX:
805+
self.write_uint8(1 | sign)
806+
self.write_uint8(<uint8_t> value)
807+
elif value <= UINT16_MAX:
808+
self.write_uint8(2 | sign)
809+
self.write_uint16be(<uint16_t> value)
810+
else:
811+
self.write_uint8(4 | sign)
812+
self.write_uint32be(value)
813+
794814
cdef int write_str(self, str value) except -1:
795815
"""
796816
Writes a string to the buffer as UTF-8 encoded bytes.

src/oracledb/impl/base/connection.pyx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#------------------------------------------------------------------------------
2-
# Copyright (c) 2020, 2024, Oracle and/or its affiliates.
2+
# Copyright (c) 2020, 2025, Oracle and/or its affiliates.
33
#
44
# This software is dual-licensed to you under the Universal Permissive License
55
# (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
@@ -225,6 +225,9 @@ cdef class BaseConnImpl:
225225
cursor_impl.prefetchrows = C_DEFAULTS.prefetchrows
226226
return cursor_impl
227227

228+
def create_msg_props_impl(self):
229+
errors._raise_not_supported("creating a message property object")
230+
228231
def create_queue_impl(self):
229232
errors._raise_not_supported("creating a queue")
230233

0 commit comments

Comments
 (0)