Skip to content

Commit b5dbe34

Browse files
Suggested changes from code review
1 parent e135e40 commit b5dbe34

File tree

2 files changed

+214
-1
lines changed

2 files changed

+214
-1
lines changed

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3869,7 +3869,8 @@ SQLRETURN FetchMany_wrap(SqlHandlePtr StatementHandle, py::list& rows, int fetch
38693869
lobColumns.push_back(i + 1); // 1-based
38703870
}
38713871
}
3872-
3872+
3873+
// Initialized to 0 for LOB path counter; overwritten by ODBC in non-LOB path;
38733874
SQLULEN numRowsFetched = 0;
38743875
// If we have LOBs → fall back to row-by-row fetch + SQLGetData_wrap
38753876
if (!lobColumns.empty()) {

tests/test_004_cursor.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,218 @@ def test_fetchmany_lob_with_arraysize(cursor, db_connection):
988988
assert len(rows) == 3, "fetchmany_lob with arraysize returned incorrect number of rows"
989989

990990

991+
def test_fetchmany_size_zero_lob(cursor, db_connection):
992+
"""Test fetchmany with size=0 for LOB columns"""
993+
try:
994+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_lob")
995+
cursor.execute(
996+
"""
997+
CREATE TABLE #test_fetchmany_lob (
998+
id INT PRIMARY KEY,
999+
lob_data NVARCHAR(MAX)
1000+
)
1001+
"""
1002+
)
1003+
1004+
# Insert test data
1005+
test_data = [(1, "First LOB data"), (2, "Second LOB data"), (3, "Third LOB data")]
1006+
cursor.executemany(
1007+
"INSERT INTO #test_fetchmany_lob (id, lob_data) VALUES (?, ?)", test_data
1008+
)
1009+
db_connection.commit()
1010+
1011+
# Test fetchmany with size=0
1012+
cursor.execute("SELECT * FROM #test_fetchmany_lob ORDER BY id")
1013+
rows = cursor.fetchmany(0)
1014+
1015+
assert isinstance(rows, list), "fetchmany should return a list"
1016+
assert len(rows) == 0, "fetchmany(0) should return empty list"
1017+
1018+
finally:
1019+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_lob")
1020+
db_connection.commit()
1021+
1022+
1023+
def test_fetchmany_more_than_exist_lob(cursor, db_connection):
1024+
"""Test fetchmany requesting more rows than exist with LOB columns"""
1025+
try:
1026+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_lob_more")
1027+
cursor.execute(
1028+
"""
1029+
CREATE TABLE #test_fetchmany_lob_more (
1030+
id INT PRIMARY KEY,
1031+
lob_data NVARCHAR(MAX)
1032+
)
1033+
"""
1034+
)
1035+
1036+
# Insert only 3 rows
1037+
test_data = [(1, "First LOB data"), (2, "Second LOB data"), (3, "Third LOB data")]
1038+
cursor.executemany(
1039+
"INSERT INTO #test_fetchmany_lob_more (id, lob_data) VALUES (?, ?)", test_data
1040+
)
1041+
db_connection.commit()
1042+
1043+
# Request 10 rows but only 3 exist
1044+
cursor.execute("SELECT * FROM #test_fetchmany_lob_more ORDER BY id")
1045+
rows = cursor.fetchmany(10)
1046+
1047+
assert isinstance(rows, list), "fetchmany should return a list"
1048+
assert len(rows) == 3, "fetchmany should return all 3 available rows"
1049+
1050+
# Verify data
1051+
for i, row in enumerate(rows):
1052+
assert row[0] == i + 1, f"Row {i} id mismatch"
1053+
assert row[1] == test_data[i][1], f"Row {i} LOB data mismatch"
1054+
1055+
# Second call should return empty
1056+
rows2 = cursor.fetchmany(10)
1057+
assert len(rows2) == 0, "Second fetchmany should return empty list"
1058+
1059+
finally:
1060+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_lob_more")
1061+
db_connection.commit()
1062+
1063+
1064+
def test_fetchmany_empty_result_lob(cursor, db_connection):
1065+
"""Test fetchmany on empty result set with LOB columns"""
1066+
try:
1067+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_lob_empty")
1068+
cursor.execute(
1069+
"""
1070+
CREATE TABLE #test_fetchmany_lob_empty (
1071+
id INT PRIMARY KEY,
1072+
lob_data NVARCHAR(MAX)
1073+
)
1074+
"""
1075+
)
1076+
db_connection.commit()
1077+
1078+
# Query empty table
1079+
cursor.execute("SELECT * FROM #test_fetchmany_lob_empty")
1080+
rows = cursor.fetchmany(5)
1081+
1082+
assert isinstance(rows, list), "fetchmany should return a list"
1083+
assert len(rows) == 0, "fetchmany on empty result should return empty list"
1084+
1085+
# Multiple calls on empty result
1086+
rows2 = cursor.fetchmany(5)
1087+
assert len(rows2) == 0, "Subsequent fetchmany should also return empty list"
1088+
1089+
finally:
1090+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_lob_empty")
1091+
db_connection.commit()
1092+
1093+
1094+
def test_fetchmany_very_large_lob(cursor, db_connection):
1095+
"""Test fetchmany with very large LOB column data"""
1096+
try:
1097+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_large_lob")
1098+
cursor.execute(
1099+
"""
1100+
CREATE TABLE #test_fetchmany_large_lob (
1101+
id INT PRIMARY KEY,
1102+
large_lob NVARCHAR(MAX)
1103+
)
1104+
"""
1105+
)
1106+
1107+
# Create very large data (10000 characters)
1108+
large_data = "x" * 10000
1109+
1110+
# Insert multiple rows with large LOB data
1111+
test_data = [
1112+
(1, large_data),
1113+
(2, large_data + "y" * 100), # Slightly different
1114+
(3, large_data + "z" * 200),
1115+
(4, "Small data"),
1116+
(5, large_data),
1117+
]
1118+
cursor.executemany(
1119+
"INSERT INTO #test_fetchmany_large_lob (id, large_lob) VALUES (?, ?)", test_data
1120+
)
1121+
db_connection.commit()
1122+
1123+
# Test fetchmany with large LOB data
1124+
cursor.execute("SELECT * FROM #test_fetchmany_large_lob ORDER BY id")
1125+
1126+
# Fetch 2 rows at a time
1127+
batch1 = cursor.fetchmany(2)
1128+
assert len(batch1) == 2, "First batch should have 2 rows"
1129+
assert len(batch1[0][1]) == 10000, "First row LOB size mismatch"
1130+
assert len(batch1[1][1]) == 10100, "Second row LOB size mismatch"
1131+
assert batch1[0][1] == large_data, "First row LOB data mismatch"
1132+
1133+
batch2 = cursor.fetchmany(2)
1134+
assert len(batch2) == 2, "Second batch should have 2 rows"
1135+
assert len(batch2[0][1]) == 10200, "Third row LOB size mismatch"
1136+
assert batch2[1][1] == "Small data", "Fourth row data mismatch"
1137+
1138+
batch3 = cursor.fetchmany(2)
1139+
assert len(batch3) == 1, "Third batch should have 1 remaining row"
1140+
assert len(batch3[0][1]) == 10000, "Fifth row LOB size mismatch"
1141+
1142+
# Verify no more data
1143+
batch4 = cursor.fetchmany(2)
1144+
assert len(batch4) == 0, "Should have no more rows"
1145+
1146+
finally:
1147+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_large_lob")
1148+
db_connection.commit()
1149+
1150+
1151+
def test_fetchmany_mixed_lob_sizes(cursor, db_connection):
1152+
"""Test fetchmany with mixed LOB sizes including empty and NULL"""
1153+
try:
1154+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_mixed_lob")
1155+
cursor.execute(
1156+
"""
1157+
CREATE TABLE #test_fetchmany_mixed_lob (
1158+
id INT PRIMARY KEY,
1159+
mixed_lob NVARCHAR(MAX)
1160+
)
1161+
"""
1162+
)
1163+
1164+
# Mix of sizes: empty, NULL, small, medium, large
1165+
test_data = [
1166+
(1, ""), # Empty string
1167+
(2, None), # NULL
1168+
(3, "Small"),
1169+
(4, "x" * 1000), # Medium
1170+
(5, "y" * 10000), # Large
1171+
(6, ""), # Empty again
1172+
(7, "z" * 5000), # Another large
1173+
]
1174+
cursor.executemany(
1175+
"INSERT INTO #test_fetchmany_mixed_lob (id, mixed_lob) VALUES (?, ?)", test_data
1176+
)
1177+
db_connection.commit()
1178+
1179+
# Fetch all with fetchmany
1180+
cursor.execute("SELECT * FROM #test_fetchmany_mixed_lob ORDER BY id")
1181+
rows = cursor.fetchmany(3)
1182+
1183+
assert len(rows) == 3, "First batch should have 3 rows"
1184+
assert rows[0][1] == "", "First row should be empty string"
1185+
assert rows[1][1] is None, "Second row should be NULL"
1186+
assert rows[2][1] == "Small", "Third row should be 'Small'"
1187+
1188+
rows2 = cursor.fetchmany(3)
1189+
assert len(rows2) == 3, "Second batch should have 3 rows"
1190+
assert len(rows2[0][1]) == 1000, "Fourth row LOB size mismatch"
1191+
assert len(rows2[1][1]) == 10000, "Fifth row LOB size mismatch"
1192+
assert rows2[2][1] == "", "Sixth row should be empty string"
1193+
1194+
rows3 = cursor.fetchmany(3)
1195+
assert len(rows3) == 1, "Third batch should have 1 remaining row"
1196+
assert len(rows3[0][1]) == 5000, "Seventh row LOB size mismatch"
1197+
1198+
finally:
1199+
cursor.execute("DROP TABLE IF EXISTS #test_fetchmany_mixed_lob")
1200+
db_connection.commit()
1201+
1202+
9911203
def test_fetchall(cursor):
9921204
"""Test fetching all rows"""
9931205
cursor.execute(

0 commit comments

Comments
 (0)