Skip to content

Commit 7ef7b85

Browse files
Fixed bug when calling Cursor.executemany() with all of the values in at
least one column being null followed by a second call where at least the first row also has a null value but some of the later values are not null (#291).
1 parent a7617cf commit 7ef7b85

File tree

5 files changed

+79
-3
lines changed

5 files changed

+79
-3
lines changed

doc/src/release_notes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ Common Changes
4141

4242
#) Fixed regression with contents of :data:`Cursor.description` when calling
4343
:meth:`Cursor.parse()` with a query that returns LOBs.
44+
#) Fixed bug when calling :meth:`Cursor.executemany()` with all of the values
45+
in at least one column being null followed by a second call where at least
46+
the first row also has a null value but some of the later values are not
47+
null
48+
(`issue 291 <https://github.com/oracle/python-oracledb/issues/291>`__).
4449
#) Updated the `Jupyter notebook samples <https://github.com/oracle/
4550
python-oracledb/tree/main/samples/notebooks>`__ to cover recent
4651
python-oracledb features.

src/oracledb/impl/base/batch_load_manager.pyx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,13 @@ cdef class FullDataBatchLoadManager(BatchLoadManager):
304304
Goes to the next batch of data.
305305
"""
306306
cdef:
307-
bint defer_type_assignment = (self.offset == 0)
307+
bint defer_type_assignment
308308
object row
309309
ssize_t i
310310
self._calculate_num_rows_in_batch(self.total_num_rows)
311311
if self.cursor_impl is not None:
312312
self.cursor_impl._reset_bind_vars(self.offset, self.num_rows)
313+
defer_type_assignment = True
313314
for i in range(self.num_rows):
314315
if i == self.num_rows - 1:
315316
defer_type_assignment = False

src/oracledb/impl/base/bind_var.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ cdef class BindVar:
136136
return self.var_impl._check_and_set_value(row_num, value, NULL)
137137
self.var_impl._check_and_set_value(row_num, value, &was_set)
138138
if was_set:
139-
self.has_value = True
139+
self.has_value = not defer_type_assignment
140140
return 0
141141
self.var_impl = None
142142
self.var = None

tests/test_4000_cursor_executemany.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"""
2828

2929
import decimal
30+
import itertools
3031

3132
import oracledb
3233
import pytest
@@ -475,7 +476,7 @@ def test_4030(cursor):
475476

476477
@pytest.mark.parametrize("batch_size", [1, 5, 99, 199, 200])
477478
def test_4031(batch_size, conn, cursor, empty_tab, round_trip_checker):
478-
"4030 - test executemany with various batch sizes"
479+
"4031 - test executemany with various batch sizes"
479480
rows = [(i + 1, f"String for row {i + 1}") for i in range(200)]
480481
cursor.executemany(
481482
"insert into TestTempTable (IntCol, StringCol1) values (:1, :2)",
@@ -491,3 +492,37 @@ def test_4031(batch_size, conn, cursor, empty_tab, round_trip_checker):
491492
"select IntCol, StringCol1 from TestTempTable order by IntCol"
492493
)
493494
assert cursor.fetchall() == rows
495+
496+
497+
def test_4032(conn, cursor, empty_tab):
498+
"4032 - test consecutive executemany() with all values null in first call"
499+
rows = [(i + 1, None) for i in range(10)] + [
500+
(i + 11, (i + 11) * 0.25) for i in range(10)
501+
]
502+
for chunk in itertools.batched(rows, 4):
503+
cursor.executemany(
504+
"insert into TestTempTable (IntCol, NumberCol) values (:1, :2)",
505+
list(chunk),
506+
)
507+
conn.commit()
508+
cursor.execute(
509+
"select IntCol, NumberCol from TestTempTable order by IntCol"
510+
)
511+
assert cursor.fetchall() == rows
512+
513+
514+
def test_4033(conn, cursor, empty_tab):
515+
"4033 - test batched executemany() with all values null in first chunks"
516+
rows = [(i + 1, None) for i in range(10)] + [
517+
(i + 11, (i + 11) * 0.25) for i in range(10)
518+
]
519+
cursor.executemany(
520+
"insert into TestTempTable (IntCol, NumberCol) values (:1, :2)",
521+
rows,
522+
batch_size=4,
523+
)
524+
conn.commit()
525+
cursor.execute(
526+
"select IntCol, NumberCol from TestTempTable order by IntCol"
527+
)
528+
assert cursor.fetchall() == rows

tests/test_6100_cursor_executemany_async.py

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

2929
import decimal
30+
import itertools
3031

3132
import oracledb
3233
import pytest
@@ -436,3 +437,37 @@ async def test_6127(
436437
"select IntCol, StringCol1 from TestTempTable order by IntCol"
437438
)
438439
assert await async_cursor.fetchall() == rows
440+
441+
442+
async def test_6128(async_conn, async_cursor, empty_tab):
443+
"6128 - test consecutive executemany() with all values null in first call"
444+
rows = [(i + 1, None) for i in range(10)] + [
445+
(i + 11, (i + 11) * 0.25) for i in range(10)
446+
]
447+
for chunk in itertools.batched(rows, 4):
448+
await async_cursor.executemany(
449+
"insert into TestTempTable (IntCol, NumberCol) values (:1, :2)",
450+
list(chunk),
451+
)
452+
await async_conn.commit()
453+
await async_cursor.execute(
454+
"select IntCol, NumberCol from TestTempTable order by IntCol"
455+
)
456+
assert await async_cursor.fetchall() == rows
457+
458+
459+
async def test_6129(async_conn, async_cursor, empty_tab):
460+
"6129 - test batched executemany() with all values null in first chunks"
461+
rows = [(i + 1, None) for i in range(10)] + [
462+
(i + 11, (i + 11) * 0.25) for i in range(10)
463+
]
464+
await async_cursor.executemany(
465+
"insert into TestTempTable (IntCol, NumberCol) values (:1, :2)",
466+
rows,
467+
batch_size=4,
468+
)
469+
await async_conn.commit()
470+
await async_cursor.execute(
471+
"select IntCol, NumberCol from TestTempTable order by IntCol"
472+
)
473+
assert await async_cursor.fetchall() == rows

0 commit comments

Comments
 (0)