Skip to content

Commit b0f0c67

Browse files
Fixed bug that prevented binding data of types DB_TYPE_ROWID and
DB_TYPE_UROWID.
1 parent 6f37dec commit b0f0c67

File tree

11 files changed

+191
-19
lines changed

11 files changed

+191
-19
lines changed

doc/src/api_manual/module.rst

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,9 +2066,9 @@ DB API Types
20662066
.. data:: ROWID
20672067

20682068
This type object is used to describe the pseudo column "rowid". The
2069-
database type :data:`DB_TYPE_ROWID` will compare equal to this value. If a
2070-
variable is created with this type, the database type
2071-
:data:`DB_TYPE_VARCHAR` will be used.
2069+
database types :data:`DB_TYPE_ROWID` and :data:`DB_TYPE_UROWID` will
2070+
compare equal to this value. If a variable is created with this type, the
2071+
database type :data:`DB_TYPE_VARCHAR` will be used.
20722072

20732073

20742074
.. data:: STRING
@@ -2281,6 +2281,17 @@ when binding data.
22812281
:data:`DATETIME`.
22822282

22832283

2284+
.. data:: DB_TYPE_UROWID
2285+
2286+
Describes columns, attributes or array elements in a database that are of
2287+
type UROWID. It will compare equal to the DB API type :data:`ROWID`.
2288+
2289+
.. note::
2290+
2291+
This type is not supported in python-oracledb Thick mode.
2292+
See :ref:`querymetadatadiff`.
2293+
2294+
22842295
.. data:: DB_TYPE_VARCHAR
22852296

22862297
Describes columns, attributes or array elements in a database that are of

doc/src/release_notes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ oracledb 1.1.1 (TBD)
1313
Thin Mode Changes
1414
+++++++++++++++++
1515

16+
#) Fixed bug that prevented binding data of types
17+
:data:`~oracledb.DB_TYPE_ROWID` and :data:`~oracledb.DB_TYPE_UROWID`.
18+
1619
Thick Mode Changes
1720
++++++++++++++++++
1821

doc/src/user_guide/appendix_a.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,11 +463,11 @@ values.
463463
* - ROWID
464464
- DB_TYPE_ROWID
465465
- Yes
466-
- cannot be set
466+
- bytes, str
467467
* - UROWID
468-
- DB_TYPE_ROWID
468+
- DB_TYPE_ROWID, DB_TYPE_UROWID (only supported in python-oracledb Thin mode)
469469
- Yes. May show DB_TYPE_UROWID in metadata. See :ref:`Query Metadata Differences <querymetadatadiff>`.
470-
- cannot be set
470+
- bytes, str
471471
* - CHAR
472472
- DB_TYPE_CHAR
473473
- Yes
@@ -578,14 +578,14 @@ these arrays.
578578
* - DB_TYPE_RAW
579579
- bytes, str
580580
* - DB_TYPE_ROWID
581-
- cannot be set
581+
- bytes, str
582582
* - DB_TYPE_TIMESTAMP
583583
- datetime.date, datetime.datetime
584584
* - DB_TYPE_TIMESTAMP_LTZ
585585
- datetime.date, datetime.datetime
586586
* - DB_TYPE_TIMESTAMP_TZ
587587
- datetime.date, datetime.datetime
588588
* - DB_TYPE_UROWID
589-
- cannot be set
589+
- bytes, str
590590
* - DB_TYPE_VARCHAR
591591
- bytes, str

doc/src/user_guide/bind.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,48 @@ fetching a row and then updating that row by binding its rowid:
183183
where rowid = :rid""", manager_id=205, rid=rowid)
184184
185185
186+
Binding UROWID Values
187+
=====================
188+
189+
Universal rowids (UROWID) are used to uniquely identify rows in index
190+
organized tables. In python-oracledb, UROWID values are represented as strings.
191+
The example below shows fetching a row from index organized table
192+
``universal_rowids`` and then updating that row by binding its urowid:
193+
194+
.. code-block:: sql
195+
196+
CREATE TABLE universal_rowids (
197+
int_col number(9) not null,
198+
str_col varchar2(250) not null,
199+
date_col date not null,
200+
CONSTRAINT universal_rowids_pk PRIMARY KEY(int_col, str_col, date_col)
201+
) ORGANIZATION INDEX
202+
203+
204+
.. code-block:: python
205+
206+
ridvar = cursor.var(oracledb.DB_TYPE_UROWID)
207+
208+
# fetch the row
209+
cursor.execute("""
210+
begin
211+
select rowid into :rid from universal_rowids
212+
where int_col = 3;
213+
end;""", rid=ridvar)
214+
215+
# update the row by binding UROWID
216+
cursor.execute("""
217+
update universal_rowids set
218+
str_col = :str_val
219+
where rowid = :rowid_val""",
220+
str_val="String #33", rowid_val=ridvar)
221+
222+
Note that the type :attr:`oracledb.DB_TYPE_UROWID` is only supported in
223+
python-oracledb Thin mode. For python-oracledb Thick mode, the database type
224+
UROWID can be bound with type :attr:`oracledb.DB_TYPE_ROWID`.
225+
See :ref:`querymetadatadiff`.
226+
227+
186228
DML RETURNING Bind Variables
187229
============================
188230

doc/src/user_guide/sql_execution.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ Python object that is returned by default. Python types can be changed with
239239
- :attr:`oracledb.DB_TYPE_TIMESTAMP_TZ`
240240
- datetime.datetime [2]_
241241
* - UROWID
242-
- :attr:`oracledb.DB_TYPE_ROWID`
242+
- :attr:`oracledb.DB_TYPE_ROWID`, :attr:`oracledb.DB_TYPE_UROWID`
243243
- str
244244
* - VARCHAR2
245245
- :attr:`oracledb.DB_TYPE_VARCHAR`

src/oracledb/impl/thin/buffer.pyx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,15 +1016,22 @@ cdef class ReadBuffer:
10161016
const char_type *input_ptr
10171017
bytearray output_value
10181018
uint32_t num_bytes
1019+
uint8_t length
10191020
Rowid rowid
10201021

10211022
# check for null
1022-
self.read_ub4(&num_bytes)
1023-
if num_bytes == 0:
1023+
self.read_ub1(&length)
1024+
if _is_null_length(length):
10241025
return None
10251026

1026-
# handle physical rowid
1027+
if length == 1:
1028+
self.skip_ub1()
1029+
else:
1030+
self.read_ub4(&num_bytes)
1031+
10271032
self.read_raw_bytes_chunked(&input_ptr, &input_len)
1033+
1034+
# handle physical rowid
10281035
if input_ptr[0] == 1:
10291036
rowid.rba = unpack_uint32(&input_ptr[1], BYTE_ORDER_MSB)
10301037
rowid.partition_id = unpack_uint16(&input_ptr[5], BYTE_ORDER_MSB)

src/oracledb/impl/thin/constants.pxi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ DEF TNS_MAX_CURSORS_TO_CLOSE = 500
621621
DEF TNS_TXN_IN_PROGRESS = 0x00000002
622622
DEF TNS_MAX_CONNECT_DATA = 230
623623
DEF TNS_CHUNK_SIZE = 32767
624+
DEF TNS_MAX_UROWID_LENGTH = 3950
624625

625626
# base 64 encoding alphabet
626627
DEF TNS_BASE64_ALPHABET = \

src/oracledb/impl/thin/messages.pyx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,8 @@ cdef class MessageWithData(Message):
537537
if var_impl.bypass_decode:
538538
ora_type_num = TNS_DATA_TYPE_RAW
539539
if buffer_size == 0 and ora_type_num != TNS_DATA_TYPE_LONG \
540-
and ora_type_num != TNS_DATA_TYPE_LONG_RAW:
540+
and ora_type_num != TNS_DATA_TYPE_LONG_RAW \
541+
and ora_type_num != TNS_DATA_TYPE_UROWID:
541542
column_value = None # column is null by describe
542543
elif ora_type_num == TNS_DATA_TYPE_VARCHAR \
543544
or ora_type_num == TNS_DATA_TYPE_CHAR \
@@ -557,7 +558,7 @@ cdef class MessageWithData(Message):
557558
column_value = buf.read_date()
558559
elif ora_type_num == TNS_DATA_TYPE_ROWID:
559560
if not self.in_fetch:
560-
column_value = buf.read_urowid()
561+
column_value = buf.read_str(TNS_CS_IMPLICIT)
561562
else:
562563
buf.read_ub1(&num_bytes)
563564
if _is_null_length(num_bytes):
@@ -566,7 +567,10 @@ cdef class MessageWithData(Message):
566567
buf.read_rowid(&rowid)
567568
column_value = _encode_rowid(&rowid)
568569
elif ora_type_num == TNS_DATA_TYPE_UROWID:
569-
column_value = buf.read_urowid()
570+
if not self.in_fetch:
571+
column_value = buf.read_str(TNS_CS_IMPLICIT)
572+
else:
573+
column_value = buf.read_urowid()
570574
elif ora_type_num == TNS_DATA_TYPE_BINARY_DOUBLE:
571575
column_value = buf.read_binary_double()
572576
elif ora_type_num == TNS_DATA_TYPE_BINARY_FLOAT:
@@ -895,11 +899,14 @@ cdef class MessageWithData(Message):
895899
list bind_var_impls) except -1:
896900
cdef:
897901
uint8_t ora_type_num, flag
902+
uint32_t buffer_size
898903
ThinVarImpl var_impl
899904
for var_impl in bind_var_impls:
900905
ora_type_num = var_impl.dbtype._ora_type_num
901-
if ora_type_num == TNS_DATA_TYPE_ROWID:
902-
ora_type_num = TNS_DATA_TYPE_UROWID
906+
buffer_size = var_impl.buffer_size
907+
if ora_type_num in (TNS_DATA_TYPE_ROWID, TNS_DATA_TYPE_UROWID):
908+
ora_type_num = TNS_DATA_TYPE_VARCHAR
909+
buffer_size = TNS_MAX_UROWID_LENGTH
903910
flag = TNS_BIND_USE_INDICATORS
904911
if var_impl.is_array:
905912
flag |= TNS_BIND_ARRAY
@@ -909,10 +916,10 @@ cdef class MessageWithData(Message):
909916
# expects that and complains if any other value is sent!
910917
buf.write_uint8(0)
911918
buf.write_uint8(0)
912-
if var_impl.buffer_size >= TNS_MIN_LONG_LENGTH:
919+
if buffer_size >= TNS_MIN_LONG_LENGTH:
913920
buf.write_ub4(TNS_MAX_LONG_LENGTH)
914921
else:
915-
buf.write_ub4(var_impl.buffer_size)
922+
buf.write_ub4(buffer_size)
916923
if var_impl.is_array:
917924
buf.write_ub4(var_impl.num_elements)
918925
else:
@@ -996,6 +1003,9 @@ cdef class MessageWithData(Message):
9961003
num_bytes = <uint32_t> len(lob_impl._locator)
9971004
buf.write_ub4(num_bytes)
9981005
buf.write_bytes_chunked(lob_impl._locator)
1006+
elif ora_type_num in (TNS_DATA_TYPE_ROWID, TNS_DATA_TYPE_UROWID):
1007+
temp_bytes = (<str> value).encode()
1008+
buf.write_bytes_chunked(temp_bytes)
9991009
else:
10001010
errors._raise_err(errors.ERR_DB_TYPE_NOT_SUPPORTED,
10011011
name=var_impl.dbtype.name)

tests/test_1500_types.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,5 +194,10 @@ def test_1528_ROWID(self):
194194
"1528 - test oracledb.ROWID pickling"
195195
self.__test_pickle(oracledb.ROWID)
196196

197+
def test_1529_DB_TYPE_UROWID(self):
198+
"1529 - test oracledb.DB_TYPE_UROWID comparisons and pickling"
199+
self.__test_compare(oracledb.DB_TYPE_UROWID, oracledb.ROWID)
200+
self.__test_pickle(oracledb.DB_TYPE_UROWID)
201+
197202
if __name__ == "__main__":
198203
test_env.run_test_cases()

tests/test_2900_rowid.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"""
2828

2929
import datetime
30+
import unittest
3031

3132
import oracledb
3233
import test_env
@@ -84,5 +85,90 @@ def test_2903_select_rowids_as_urowids(self):
8485
val=rowid)
8586
self.assertEqual(self.cursor.fetchall(), [(int_val,)])
8687

88+
def test_2904_select_rowids_as_rowids(self):
89+
"2904 - test selecting regular rowids stored in a rowid column"
90+
self.cursor.execute("truncate table TestRowids")
91+
self.cursor.execute("""
92+
insert into TestRowids (IntCol, RowidCol)
93+
select IntCol, rowid from TestNumbers""")
94+
self.connection.commit()
95+
self.cursor.execute("select IntCol, RowidCol from TestRowids")
96+
for int_val, rowid in self.cursor.fetchall():
97+
self.cursor.execute("""
98+
select IntCol
99+
from TestNumbers
100+
where rowid = :val""",
101+
val=rowid)
102+
self.assertEqual(self.cursor.fetchall(), [(int_val,)])
103+
104+
def test_2905_test_bind_and_insert_rowid(self):
105+
"2905 - binding and inserting a rowid"
106+
self.cursor.execute("truncate table TestRowids")
107+
insert_data = [
108+
(1, "String #1"), (2, "String #2"),
109+
(3, "String #3"),(4, "String #4")
110+
]
111+
self.cursor.execute("truncate table TestTempTable")
112+
sql = "insert into TestTempTable (IntCol, StringCol1) values (:1, :2)"
113+
self.cursor.executemany(sql, insert_data)
114+
self.connection.commit()
115+
ridvar = self.cursor.var(oracledb.ROWID)
116+
self.cursor.execute("""
117+
begin
118+
select rowid into :rid from TestTempTable
119+
where IntCol = 3;
120+
end;""",
121+
rid=ridvar)
122+
self.cursor.setinputsizes(r1=oracledb.ROWID)
123+
self.cursor.execute("""
124+
insert into TestRowids (IntCol, RowidCol)
125+
values(1, :r1)""",
126+
r1=ridvar)
127+
self.connection.commit()
128+
self.cursor.execute("select IntCol, RowidCol from TestRowids")
129+
int_val, rowid = self.cursor.fetchone()
130+
self.cursor.execute("""
131+
select IntCol, StringCol1 from TestTempTable
132+
where rowid = :val""",
133+
val=rowid)
134+
self.assertEqual(self.cursor.fetchone(), (3, "String #3"))
135+
136+
@unittest.skipIf(not test_env.get_is_thin(),
137+
"thick mode doesn't support DB_TYPE_UROWID")
138+
def test_2906_test_bind_and_insert_rowid_as_urowid(self):
139+
"2906 - binding and inserting a rowid as urowid"
140+
self.cursor.execute("truncate table TestRowids")
141+
insert_data = [
142+
(1, "String #1", datetime.datetime(2017, 4, 4)),
143+
(2, "String #2", datetime.datetime(2017, 4, 5)),
144+
(3, "String #3", datetime.datetime(2017, 4, 6)),
145+
(4, "A" * 250, datetime.datetime(2017, 4, 7))
146+
]
147+
self.cursor.execute("truncate table TestUniversalRowids")
148+
sql = "insert into TestUniversalRowids values (:1, :2, :3)"
149+
self.cursor.executemany(sql, insert_data)
150+
self.connection.commit()
151+
ridvar = self.cursor.var(oracledb.DB_TYPE_UROWID)
152+
self.cursor.execute("""
153+
begin
154+
select rowid into :rid from TestUniversalRowids
155+
where IntCol = 3;
156+
end;""",
157+
rid=ridvar)
158+
self.cursor.setinputsizes(r1=oracledb.DB_TYPE_UROWID)
159+
self.cursor.execute("""
160+
insert into TestRowids (IntCol, UrowidCol)
161+
values(1, :r1)""",
162+
r1=ridvar)
163+
self.connection.commit()
164+
self.cursor.execute("select IntCol, UrowidCol from TestRowids")
165+
int_val, rowid = self.cursor.fetchone()
166+
self.cursor.execute("""
167+
select IntCol, StringCol, DateCol from TestUniversalRowids
168+
where rowid = :val""",
169+
val=rowid)
170+
self.assertEqual(self.cursor.fetchone(),
171+
(3, "String #3", datetime.datetime(2017, 4, 6)))
172+
87173
if __name__ == "__main__":
88174
test_env.run_test_cases()

0 commit comments

Comments
 (0)