Skip to content

Commit e6a2886

Browse files
Fixed bug when multiple rows containing LOBs and DbObjects are returned
in a DML RETURNING statement.
1 parent 911cbc3 commit e6a2886

File tree

4 files changed

+84
-12
lines changed

4 files changed

+84
-12
lines changed

doc/src/release_notes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ Common Changes
6868

6969
#) Fixed bug when binding a variable that was previously bound as an output
7070
variable in a DML RETURNING statement.
71+
#) Fixed bug when multiple rows containing LOBs and DbObjects are returned in
72+
a DML RETURNING statement.
7173
#) An error message that links to :ref:`documentation <ldapconnections>` on
7274
setting up a protocol hook function is now returned by default for LDAP and
7375
LDAPS URL connection strings in python-oracledb thin mode, or when

src/oracledb/impl/thick/var.pyx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ cdef class ThickVarImpl(BaseVarImpl):
121121
object cursor
122122
cursor = self._values[pos]
123123
if cursor is None:
124-
cursor = self._values[pos] = self._conn.cursor()
124+
cursor = self._conn.cursor()
125125
cursor_impl = <ThickCursorImpl> cursor._impl
126126
if dpiStmt_addRef(dbvalue.asStmt) < 0:
127127
_raise_from_odpi()
@@ -138,8 +138,9 @@ cdef class ThickVarImpl(BaseVarImpl):
138138
"""
139139
cdef:
140140
ThickDbObjectImpl obj_impl
141-
object obj
142-
obj = self._values[pos]
141+
object obj = None
142+
if not self._has_returned_data:
143+
obj = self._values[pos]
143144
if obj is not None:
144145
obj_impl = <ThickDbObjectImpl> obj._impl
145146
if obj_impl._handle == dbvalue.asObject:
@@ -149,8 +150,7 @@ cdef class ThickVarImpl(BaseVarImpl):
149150
if dpiObject_addRef(dbvalue.asObject) < 0:
150151
_raise_from_odpi()
151152
obj_impl._handle = dbvalue.asObject
152-
obj = self._values[pos] = PY_TYPE_DB_OBJECT._from_impl(obj_impl)
153-
return obj
153+
return PY_TYPE_DB_OBJECT._from_impl(obj_impl)
154154

155155
cdef object _get_lob_value(self, dpiDataBuffer *dbvalue, uint32_t pos):
156156
"""
@@ -160,16 +160,16 @@ cdef class ThickVarImpl(BaseVarImpl):
160160
"""
161161
cdef:
162162
ThickLobImpl lob_impl
163-
object lob
164-
lob = self._values[pos]
163+
object lob = None
164+
if not self._has_returned_data:
165+
lob = self._values[pos]
165166
if lob is not None:
166167
lob_impl = <ThickLobImpl> lob._impl
167168
if lob_impl._handle == dbvalue.asLOB:
168169
return lob
169170
lob_impl = ThickLobImpl._create(self._conn_impl, self.metadata.dbtype,
170171
dbvalue.asLOB)
171-
lob = self._values[pos] = PY_TYPE_LOB._from_impl(lob_impl)
172-
return lob
172+
return PY_TYPE_LOB._from_impl(lob_impl)
173173

174174
cdef object _get_scalar_value(self, uint32_t pos):
175175
"""
@@ -179,13 +179,21 @@ cdef class ThickVarImpl(BaseVarImpl):
179179
cdef:
180180
uint32_t num_returned_rows
181181
dpiData *returned_data
182+
object value
182183
if self._has_returned_data:
183184
if dpiVar_getReturnedData(self._handle, pos, &num_returned_rows,
184185
&returned_data) < 0:
185186
_raise_from_odpi()
186187
return self._transform_array_to_python(num_returned_rows,
187188
returned_data)
188-
return self._transform_element_to_python(pos, self._data)
189+
value = self._transform_element_to_python(pos, self._data)
190+
if self.metadata.dbtype._native_num in (
191+
DPI_NATIVE_TYPE_LOB,
192+
DPI_NATIVE_TYPE_OBJECT,
193+
DPI_NATIVE_TYPE_STMT,
194+
):
195+
self._values[pos] = value
196+
return value
189197

190198
cdef int _on_reset_bind(self, uint32_t num_rows) except -1:
191199
"""

src/oracledb/impl/thin/messages/base.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ cdef class MessageWithData(Message):
893893
elif ora_type_num in (ORA_TYPE_NUM_CLOB,
894894
ORA_TYPE_NUM_BLOB,
895895
ORA_TYPE_NUM_BFILE):
896-
if not self.in_fetch:
896+
if self.cursor_impl._statement._is_plsql:
897897
column_value = var_impl._values[pos]
898898
column_value = buf.read_lob_with_length(self.conn_impl,
899899
metadata.dbtype,
@@ -909,7 +909,7 @@ cdef class MessageWithData(Message):
909909
else:
910910
obj_impl = buf.read_dbobject(typ_impl)
911911
if obj_impl is not None:
912-
if not self.in_fetch:
912+
if self.cursor_impl._statement._is_plsql:
913913
column_value = var_impl._values[pos]
914914
if column_value is not None:
915915
column_value._impl = obj_impl

tests/test_1600_dml_returning.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,68 @@ def test_1624(self):
554554
self.cursor.execute(sql, in_val=35, out_val=out_val)
555555
self.assertEqual(out_val.getvalue(), 70)
556556

557+
def test_1625(self):
558+
"1625 - test DML returning with multiple LOBs returned"
559+
lob_data = [
560+
"Short CLOB - 1625a",
561+
"Short CLOB - 1625b",
562+
"Short CLOB - 1625c",
563+
"Short CLOB - 1625d",
564+
]
565+
all_data = [(i + 1, d) for i, d in enumerate(lob_data)]
566+
self.cursor.execute("delete from TestCLOBs")
567+
self.cursor.executemany(
568+
"insert into TestCLOBs (IntCol, ClobCol) values (:1, :2)", all_data
569+
)
570+
ret_val = self.cursor.var(oracledb.DB_TYPE_CLOB)
571+
self.cursor.execute(
572+
"""
573+
update TestCLOBs set
574+
ExtraNumCol1 = 1
575+
where ExtraNumCol1 is null
576+
returning ClobCol into :ret_val
577+
""",
578+
[ret_val],
579+
)
580+
self.conn.commit()
581+
ret_lob_data = [v.read() for v in ret_val.getvalue()]
582+
ret_lob_data.sort()
583+
self.assertEqual(ret_lob_data, lob_data)
584+
585+
@unittest.skipUnless(test_env.get_is_thin(), "blocked by bug 37741324")
586+
def test_1626(self):
587+
"1626 - test DML returning with multiple DbObjects returned"
588+
arrays = [
589+
(1626, 1627, 1628),
590+
(1629, 1630, 1631),
591+
(1632, 1633, 1634),
592+
(1635, 1636, 1637),
593+
]
594+
all_data = [(i + 4, v[0], v[1], v[2]) for i, v in enumerate(arrays)]
595+
self.cursor.execute("delete from TestObjects where IntCol > 3")
596+
self.cursor.executemany(
597+
"""
598+
insert into TestObjects (IntCol, ArrayCol)
599+
values (:1, udt_Array(:1, :2, :3))
600+
""",
601+
all_data,
602+
)
603+
typ = self.conn.gettype("UDT_ARRAY")
604+
ret_val = self.cursor.var(typ)
605+
self.cursor.execute(
606+
"""
607+
update TestObjects set
608+
ObjectCol = null
609+
where IntCol > 3
610+
returning ArrayCol into :ret_val
611+
""",
612+
[ret_val],
613+
)
614+
self.conn.commit()
615+
ret_obj_data = [tuple(v) for v in ret_val.getvalue()]
616+
ret_obj_data.sort()
617+
self.assertEqual(ret_obj_data, arrays)
618+
557619

558620
if __name__ == "__main__":
559621
test_env.run_test_cases()

0 commit comments

Comments
 (0)