Skip to content

Commit d5e6887

Browse files
Fixed bug when a DbObject instance contains an attribute of type
SYS.XMLTYPE (#336).
1 parent 421a0c3 commit d5e6887

File tree

6 files changed

+107
-68
lines changed

6 files changed

+107
-68
lines changed

doc/src/release_notes.rst

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

20+
#) Fixed bug when a :ref:`DbObject <dbobject>` instance contains an attribute
21+
of type ``SYS.XMLTYPE``
22+
(`issue 336 <https://github.com/oracle/python-oracledb/issues/336>`__).
2023
#) Fixed bug in statement cache when the maximum number of cursors is unknown
2124
due to the database not being open.
2225
#) Fixed bug in handling redirect data with small SDU sizes.

src/oracledb/impl/thin/dbobject.pyx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,36 @@ cdef class DbObjectPickleBuffer(GrowableBuffer):
9494
else:
9595
length[0] = short_length
9696

97+
cdef object read_xmltype(self, BaseThinConnImpl conn_impl):
98+
"""
99+
Reads an XML type from the buffer. This is similar to reading a
100+
database object but with specialized processing.
101+
"""
102+
cdef:
103+
uint8_t image_flags, image_version
104+
BaseThinLobImpl lob_impl
105+
const char_type *ptr
106+
ssize_t bytes_left
107+
uint32_t xml_flag
108+
type cls
109+
self.read_header(&image_flags, &image_version)
110+
self.skip_raw_bytes(1) # XML version
111+
self.read_uint32(&xml_flag)
112+
if xml_flag & TNS_XML_TYPE_FLAG_SKIP_NEXT_4:
113+
self.skip_raw_bytes(4)
114+
bytes_left = self.bytes_left()
115+
ptr = self.read_raw_bytes(bytes_left)
116+
if xml_flag & TNS_XML_TYPE_STRING:
117+
return ptr[:bytes_left].decode()
118+
elif xml_flag & TNS_XML_TYPE_LOB:
119+
lob_impl = conn_impl._create_lob_impl(DB_TYPE_CLOB,
120+
ptr[:bytes_left])
121+
cls = PY_TYPE_ASYNC_LOB \
122+
if conn_impl._protocol._transport._is_async \
123+
else PY_TYPE_LOB
124+
return cls._from_impl(lob_impl)
125+
errors._raise_err(errors.ERR_UNEXPECTED_XML_TYPE, flag=xml_flag)
126+
97127
cdef int skip_length(self) except -1:
98128
"""
99129
Skips the length instead of reading it from the buffer.
@@ -329,6 +359,7 @@ cdef class ThinDbObjectImpl(BaseDbObjectImpl):
329359
cdef:
330360
uint8_t ora_type_num = dbtype._ora_type_num
331361
uint8_t csfrm = dbtype._csfrm
362+
DbObjectPickleBuffer xml_buf
332363
BaseThinConnImpl conn_impl
333364
ThinDbObjectImpl obj_impl
334365
BaseThinLobImpl lob_impl
@@ -370,6 +401,10 @@ cdef class ThinDbObjectImpl(BaseDbObjectImpl):
370401
buf.get_is_atomic_null(&is_null)
371402
if is_null:
372403
return None
404+
if objtype is None:
405+
xml_buf = DbObjectPickleBuffer.__new__(DbObjectPickleBuffer)
406+
xml_buf._populate_from_bytes(buf.read_bytes())
407+
return xml_buf.read_xmltype(self.type._conn_impl)
373408
obj_impl = ThinDbObjectImpl.__new__(ThinDbObjectImpl)
374409
obj_impl.type = objtype
375410
if objtype.is_collection or self.type.is_collection:

src/oracledb/impl/thin/dbobject_cache.pyx

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,40 @@ cdef class BaseThinDbObjectTypeCache:
365365
errors._raise_err(errors.ERR_TDS_TYPE_NOT_SUPPORTED, num=attr_type)
366366
return DbType._from_ora_type_and_csfrm(ora_type_num, csfrm)
367367

368+
cdef int _create_attr(self, ThinDbObjectTypeImpl typ_impl, str name,
369+
str type_name, str type_owner,
370+
str type_package_name=None, bytes oid=None,
371+
int8_t precision=0, int8_t scale=0,
372+
uint32_t max_size=0) except -1:
373+
"""
374+
Creates an attribute from the supplied information and adds it to the
375+
list of attributes for the type.
376+
"""
377+
cdef:
378+
ThinDbObjectTypeImpl attr_typ_impl
379+
ThinDbObjectAttrImpl attr_impl
380+
attr_impl = ThinDbObjectAttrImpl.__new__(ThinDbObjectAttrImpl)
381+
attr_impl.name = name
382+
if type_owner is not None:
383+
attr_typ_impl = self.get_type_for_info(oid, type_owner,
384+
type_package_name,
385+
type_name)
386+
if attr_typ_impl.is_xml_type:
387+
attr_impl.dbtype = DB_TYPE_XMLTYPE
388+
else:
389+
attr_impl.dbtype = DB_TYPE_OBJECT
390+
attr_impl.objtype = attr_typ_impl
391+
else:
392+
attr_impl.dbtype = DbType._from_ora_name(type_name)
393+
attr_impl.max_size = max_size
394+
if precision != 0 or scale != 0:
395+
attr_impl.precision = precision
396+
attr_impl.scale = scale
397+
attr_impl._preferred_num_type = \
398+
get_preferred_num_type(precision, scale)
399+
typ_impl.attrs.append(attr_impl)
400+
typ_impl.attrs_by_name[name] = attr_impl
401+
368402
cdef object _populate_type_info(self, str name, object attrs,
369403
ThinDbObjectTypeImpl typ_impl):
370404
"""
@@ -389,48 +423,25 @@ cdef class BaseThinDbObjectTypeCache:
389423
if typ_impl.is_row_type:
390424
for name, data_type, data_type_owner, max_size, precision, \
391425
scale in attrs:
392-
attr_impl = ThinDbObjectAttrImpl.__new__(ThinDbObjectAttrImpl)
393-
attr_impl.name = name
394-
if data_type_owner is not None:
395-
attr_impl.dbtype = DB_TYPE_OBJECT
396-
attr_impl.objtype = self.get_type_for_info(None,
397-
data_type_owner,
398-
None,
399-
data_type)
400-
else:
426+
if data_type_owner is None:
401427
start_pos = data_type.find("(")
402428
if start_pos > 0:
403429
end_pos = data_type.find(")")
404430
if end_pos > start_pos:
405431
data_type = data_type[:start_pos] + \
406432
data_type[end_pos + 1:]
407-
attr_impl.dbtype = DbType._from_ora_name(data_type)
408-
attr_impl.max_size = max_size
409-
attr_impl.precision = precision
410-
attr_impl.scale = scale
411-
typ_impl.attrs.append(attr_impl)
412-
typ_impl.attrs_by_name[name] = attr_impl
433+
self._create_attr(typ_impl, name, data_type, data_type_owner,
434+
None, None, precision, scale, max_size)
413435
else:
414436
for cursor_version, attr_name, attr_num, attr_type_name, \
415437
attr_type_owner, attr_type_package, attr_type_oid, \
416438
attr_instantiable, attr_super_type_owner, \
417439
attr_super_type_name in attrs:
418440
if attr_name is None:
419441
continue
420-
attr_impl = ThinDbObjectAttrImpl.__new__(ThinDbObjectAttrImpl)
421-
attr_impl.name = attr_name
422-
if attr_type_owner is not None:
423-
attr_impl.dbtype = DB_TYPE_OBJECT
424-
attr_impl.objtype = self.get_type_for_info(
425-
attr_type_oid,
426-
attr_type_owner,
427-
attr_type_package,
428-
attr_type_name
429-
)
430-
else:
431-
attr_impl.dbtype = DbType._from_ora_name(attr_type_name)
432-
typ_impl.attrs.append(attr_impl)
433-
typ_impl.attrs_by_name[attr_name] = attr_impl
442+
self._create_attr(typ_impl, attr_name, attr_type_name,
443+
attr_type_owner, attr_type_package,
444+
attr_type_oid)
434445
return self._parse_tds(typ_impl, self.tds_var.getvalue())
435446

436447
cdef ThinDbObjectTypeImpl get_type_for_info(self, bytes oid, str schema,

src/oracledb/impl/thin/packet.pyx

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -589,15 +589,8 @@ cdef class ReadBuffer(Buffer):
589589
since the structure is a bit different.
590590
"""
591591
cdef:
592-
uint8_t image_flags, image_version
593592
DbObjectPickleBuffer buf
594-
BaseThinLobImpl lob_impl
595-
const char_type *ptr
596593
uint32_t num_bytes
597-
ssize_t bytes_left
598-
uint32_t xml_flag
599-
bytes packed_data
600-
type cls
601594
self.read_ub4(&num_bytes)
602595
if num_bytes > 0: # type OID
603596
self.read_bytes()
@@ -611,26 +604,9 @@ cdef class ReadBuffer(Buffer):
611604
self.read_ub4(&num_bytes) # length of data
612605
self.skip_ub2() # flags
613606
if num_bytes > 0:
614-
packed_data = self.read_bytes()
615607
buf = DbObjectPickleBuffer.__new__(DbObjectPickleBuffer)
616-
buf._populate_from_bytes(packed_data)
617-
buf.read_header(&image_flags, &image_version)
618-
buf.skip_raw_bytes(1) # XML version
619-
buf.read_uint32(&xml_flag)
620-
if xml_flag & TNS_XML_TYPE_FLAG_SKIP_NEXT_4:
621-
buf.skip_raw_bytes(4)
622-
bytes_left = buf.bytes_left()
623-
ptr = buf.read_raw_bytes(bytes_left)
624-
if xml_flag & TNS_XML_TYPE_STRING:
625-
return ptr[:bytes_left].decode()
626-
elif xml_flag & TNS_XML_TYPE_LOB:
627-
lob_impl = conn_impl._create_lob_impl(DB_TYPE_CLOB,
628-
ptr[:bytes_left])
629-
cls = PY_TYPE_ASYNC_LOB \
630-
if conn_impl._protocol._transport._is_async \
631-
else PY_TYPE_LOB
632-
return cls._from_impl(lob_impl)
633-
errors._raise_err(errors.ERR_UNEXPECTED_XML_TYPE, flag=xml_flag)
608+
buf._populate_from_bytes(self.read_bytes())
609+
return buf.read_xmltype(conn_impl)
634610

635611
cdef int check_control_packet(self) except -1:
636612
"""

tests/sql/create_schema.sql

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,14 @@ create or replace type &main_user..udt_Book as object (
135135
);
136136
/
137137

138-
create or replace type &main_user..udt_UnknownAttributeType as object (
138+
create or replace type &main_user..udt_ObjectWithXmlType as object (
139139
NumberValue number(9),
140-
XMLValue sys.xmltype
140+
XMLValue sys.xmltype,
141+
StringValue varchar2(60)
141142
);
142143
/
143144

144-
create or replace type &main_user..udt_UnknownElementType
145+
create or replace type &main_user..udt_XmlTypeArray
145146
as table of sys.xmltype;
146147
/
147148

tests/test_2300_object_var.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -660,22 +660,16 @@ def test_2327(self):
660660
)
661661
self.assertEqual(result, 7146445847327)
662662

663-
@unittest.skipIf(
664-
test_env.get_is_thin(),
665-
"thin mode doesn't have any unknown types currently",
666-
)
663+
@unittest.skipIf(test_env.get_is_thin(), "thin mode supports xmltype")
667664
def test_2328(self):
668665
"2328 - test object with unknown type in one of its attributes"
669-
typ = self.conn.gettype("UDT_UNKNOWNATTRIBUTETYPE")
666+
typ = self.conn.gettype("UDT_OBJECTWITHXMLTYPE")
670667
self.assertEqual(typ.attributes[1].type, oracledb.DB_TYPE_UNKNOWN)
671668

672-
@unittest.skipIf(
673-
test_env.get_is_thin(),
674-
"thin mode doesn't have any unknown types currently",
675-
)
669+
@unittest.skipIf(test_env.get_is_thin(), "thin mode supports xmltype")
676670
def test_2329(self):
677671
"2329 - test object with unknown type as the element type"
678-
typ = self.conn.gettype("UDT_UNKNOWNELEMENTTYPE")
672+
typ = self.conn.gettype("UDT_XMLTYPEARRAY")
679673
self.assertEqual(typ.element_type, oracledb.DB_TYPE_UNKNOWN)
680674

681675
def test_2330(self):
@@ -790,6 +784,25 @@ def test_2338(self):
790784
result = [i for i in obj]
791785
self.assertEqual(result, [5, 10, 15])
792786

787+
@unittest.skipUnless(
788+
test_env.get_is_thin(), "thick mode does not support xmltype"
789+
)
790+
def test_2339(self):
791+
"2339 - test fetching an object containing an XmlType instance"
792+
num_val = 2339
793+
xml_val = "<item>test_2339</item>"
794+
str_val = "A string for test 2339"
795+
self.cursor.execute(
796+
f"""
797+
select udt_ObjectWithXmlType({num_val}, sys.xmltype('{xml_val}'),
798+
'{str_val}') from dual
799+
"""
800+
)
801+
(obj,) = self.cursor.fetchone()
802+
self.assertEqual(obj.NUMBERVALUE, num_val)
803+
self.assertEqual(obj.XMLVALUE, xml_val)
804+
self.assertEqual(obj.STRINGVALUE, str_val)
805+
793806

794807
if __name__ == "__main__":
795808
test_env.run_test_cases()

0 commit comments

Comments
 (0)