Skip to content

Commit afbf5a1

Browse files
Fixed bug in the calculation of Cursor.rowcount under some
circumstances.
1 parent 25f5d47 commit afbf5a1

File tree

6 files changed

+51
-42
lines changed

6 files changed

+51
-42
lines changed

doc/src/release_notes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Common Changes
5353
mode would raise ``DPI-1040: LOB was already closed``.
5454
#) Fixed bug in :meth:`ConnectParams.get_connect_string()` when a value for
5555
the connection parameter ``purity`` has been specified.
56+
#) Fixed bug in the calculation of :data:`Cursor.rowcount` under some
57+
circumstances.
5658
#) Connection parameters that are strings now treat an empty string in the
5759
same way as the value ``None``.
5860

src/oracledb/base_impl.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ cdef class BaseCursorImpl:
526526
bint cache_statement) except -1
527527
cdef int _reset_bind_vars(self, uint32_t num_rows) except -1
528528
cdef int _verify_var(self, object var) except -1
529+
cdef int bind_many(self, object cursor, list parameters) except -1
529530
cdef int bind_one(self, object cursor, object parameters) except -1
530531

531532

src/oracledb/cursor.py

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -744,26 +744,10 @@ def executemany(
744744
None is assumed to be a string of length 1 so any values that are later
745745
bound as numbers or dates will raise a TypeError exception.
746746
"""
747-
# verify parameters
748-
if statement is None and self._impl.statement is None:
749-
errors._raise_err(errors.ERR_NO_STATEMENT)
750-
if not isinstance(parameters, (list, int)):
751-
errors._raise_err(errors.ERR_WRONG_EXECUTEMANY_PARAMETERS_TYPE)
752-
753-
# prepare statement, if necessary
754747
self._verify_open()
755-
if statement and statement != self._impl.statement:
756-
self._prepare(statement)
757-
758-
# perform bind and execute
759-
self._impl.set_input_sizes = False
760-
if isinstance(parameters, int):
761-
num_execs = parameters
762-
else:
763-
num_execs = len(parameters)
764-
if num_execs > 0:
765-
self._impl.bind_many(self, parameters)
766-
self._impl.warning = None
748+
num_execs = self._impl._prepare_for_executemany(
749+
self, statement, parameters
750+
)
767751
self._impl.executemany(
768752
self, num_execs, bool(batcherrors), bool(arraydmlrowcounts)
769753
)
@@ -1010,26 +994,10 @@ async def executemany(
1010994
None is assumed to be a string of length 1 so any values that are later
1011995
bound as numbers or dates will raise a TypeError exception.
1012996
"""
1013-
# verify parameters
1014-
if statement is None and self._impl.statement is None:
1015-
errors._raise_err(errors.ERR_NO_STATEMENT)
1016-
if not isinstance(parameters, (list, int)):
1017-
errors._raise_err(errors.ERR_WRONG_EXECUTEMANY_PARAMETERS_TYPE)
1018-
1019-
# prepare statement, if necessary
1020997
self._verify_open()
1021-
if statement and statement != self._impl.statement:
1022-
self._prepare(statement)
1023-
1024-
# perform bind and execute
1025-
self._impl.set_input_sizes = False
1026-
if isinstance(parameters, int):
1027-
num_execs = parameters
1028-
else:
1029-
num_execs = len(parameters)
1030-
if num_execs > 0:
1031-
self._impl.bind_many(self, parameters)
1032-
self._impl.warning = None
998+
num_execs = self._impl._prepare_for_executemany(
999+
self, statement, parameters
1000+
)
10331001
await self._impl.executemany(
10341002
self, num_execs, bool(batcherrors), bool(arraydmlrowcounts)
10351003
)

src/oracledb/impl/base/cursor.pyx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,38 @@ cdef class BaseCursorImpl:
391391
if parameters is not None:
392392
self.bind_one(cursor, parameters)
393393

394-
# clear any warning
394+
# clear any warning and reset rowcount
395395
self.warning = None
396+
self.rowcount = 0
397+
398+
def _prepare_for_executemany(self, object cursor, str statement,
399+
object parameters):
400+
"""
401+
Internal method for preparing a statement for execution multiple times.
402+
"""
403+
404+
# prepare statement, if necessary
405+
if statement is None and self.statement is None:
406+
errors._raise_err(errors.ERR_NO_STATEMENT)
407+
elif statement is not None and statement != self.statement:
408+
self._prepare(statement, None, True)
409+
410+
# perform bind, if applicable
411+
self.set_input_sizes = False
412+
if isinstance(parameters, int):
413+
num_execs = parameters
414+
elif isinstance(parameters, list):
415+
num_execs = len(parameters)
416+
if parameters:
417+
self.bind_many(cursor, parameters)
418+
else:
419+
errors._raise_err(errors.ERR_WRONG_EXECUTEMANY_PARAMETERS_TYPE)
420+
421+
# clear any warning and reset rowcount
422+
self.warning = None
423+
self.rowcount = 0
424+
425+
return num_execs
396426

397427
cdef int _reset_bind_vars(self, uint32_t num_rows) except -1:
398428
"""
@@ -427,7 +457,7 @@ cdef class BaseCursorImpl:
427457
var_arraysize=var.num_elements,
428458
required_arraysize=self.arraysize)
429459

430-
def bind_many(self, object cursor, list parameters):
460+
cdef int bind_many(self, object cursor, list parameters) except -1:
431461
"""
432462
Internal method used for binding multiple rows of data.
433463
"""

src/oracledb/impl/thin/cursor.pyx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ cdef class ThinCursorImpl(BaseThinCursorImpl):
178178
protocol._process_single_message(message)
179179
self.warning = message.warning
180180
if self._statement._is_query:
181-
self.rowcount = 0
182181
if message.type_cache is not None:
183182
message.type_cache.populate_partial_types(conn)
184183

@@ -262,7 +261,6 @@ cdef class AsyncThinCursorImpl(BaseThinCursorImpl):
262261
await protocol._process_single_message(message)
263262
self.warning = message.warning
264263
if self._statement._is_query:
265-
self.rowcount = 0
266264
if message.type_cache is not None:
267265
await message.type_cache.populate_partial_types(conn)
268266

tests/test_3900_cursor_execute.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,16 @@ def test_3933(self):
593593
(fetch_info,) = self.cursor.description
594594
self.assertEqual(fetch_info[1:3], (oracledb.DB_TYPE_NUMBER, 10))
595595

596+
def test_3934(self):
597+
"3934 - test rowcount is zero for PL/SQL"
598+
self.cursor.execute("begin null; end;")
599+
self.assertEqual(self.cursor.rowcount, 0)
600+
self.cursor.execute("select user from dual")
601+
self.cursor.fetchall()
602+
self.assertEqual(self.cursor.rowcount, 1)
603+
self.cursor.execute("begin null; end;")
604+
self.assertEqual(self.cursor.rowcount, 0)
605+
596606

597607
if __name__ == "__main__":
598608
test_env.run_test_cases()

0 commit comments

Comments
 (0)