Skip to content

Commit 2fbdab4

Browse files
committed
Rebase
1 parent 7d0e21f commit 2fbdab4

File tree

2 files changed

+104
-58
lines changed

2 files changed

+104
-58
lines changed

mssql_python/cursor.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,53 @@ def next(self):
884884
"""
885885
return self.__next__()
886886

887+
def _buffer_intermediate_results(self):
888+
"""
889+
Buffer intermediate results automatically.
890+
891+
This method skips "rows affected" messages and empty result sets,
892+
positioning the cursor on the first meaningful result set that contains
893+
actual data. This eliminates the need for SET NOCOUNT ON detection.
894+
"""
895+
try:
896+
# Keep advancing through result sets until we find one with actual data
897+
# or reach the end
898+
while True:
899+
# Check if current result set has actual columns/data
900+
if self.description and len(self.description) > 0:
901+
# We have a meaningful result set with columns, stop here
902+
break
903+
904+
# Try to advance to next result set
905+
try:
906+
ret = ddbc_bindings.DDBCSQLMoreResults(self.hstmt)
907+
908+
# If no more result sets, we're done
909+
if ret == ddbc_sql_const.SQL_NO_DATA.value:
910+
break
911+
912+
# Check for errors
913+
check_error(ddbc_sql_const.SQL_HANDLE_STMT.value, self.hstmt, ret)
914+
915+
# Update description for the new result set
916+
column_metadata = []
917+
try:
918+
ddbc_bindings.DDBCSQLDescribeCol(self.hstmt, column_metadata)
919+
self._initialize_description(column_metadata)
920+
except Exception:
921+
# If describe fails, it's likely there are no results (e.g., for INSERT)
922+
self.description = None
923+
924+
except Exception:
925+
# If we can't advance further, stop
926+
break
927+
928+
except Exception as e:
929+
log('warning', "Exception occurred during `_buffer_intermediate_results` %s", e)
930+
# If anything goes wrong during buffering, continue with current state
931+
# This ensures we don't break existing functionality
932+
pass
933+
887934
def execute(
888935
self,
889936
operation: str,
@@ -1020,7 +1067,7 @@ def execute(
10201067
# If describe fails, it's likely there are no results (e.g., for INSERT)
10211068
self.description = None
10221069

1023-
# Buffer intermediate results automatically (pyODBC-style approach)
1070+
# Buffer intermediate results automatically
10241071
self._buffer_intermediate_results()
10251072

10261073
# Set final rowcount based on result type (preserve original rowcount for non-SELECT)

tests/test_004_cursor.py

Lines changed: 56 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10744,55 +10744,48 @@ def test_datetime_string_parameter_binding(cursor, db_connection):
1074410744
drop_table_if_exists(cursor, table_name)
1074510745
db_connection.commit()
1074610746

10747-
def test_close(db_connection):
10748-
"""Test closing the cursor"""
10749-
try:
10750-
cursor = db_connection.cursor()
10751-
cursor.close()
10752-
assert cursor.closed, "Cursor should be closed after calling close()"
10753-
except Exception as e:
10754-
pytest.fail(f"Cursor close test failed: {e}")
10755-
finally:
10756-
cursor = db_connection.cursor()
10757-
1075810747
def test_multi_statement_query(cursor, db_connection):
1075910748
"""Test multi-statement query with temp tables"""
10749+
table_name = "#temp1"
1076010750
try:
10751+
drop_table_if_exists(cursor, table_name)
1076110752
# Single SQL with multiple statements - tests pyODBC-style buffering
10762-
multi_statement_sql = """
10763-
SELECT 1 as col1, 'test' as col2 INTO #temp1;
10764-
SELECT * FROM #temp1;
10753+
multi_statement_sql = f"""
10754+
SELECT 1 as col1, 'test' as col2 INTO {table_name};
10755+
SELECT * FROM {table_name};
1076510756
"""
10766-
10757+
1076710758
cursor.execute(multi_statement_sql)
1076810759
results = cursor.fetchall()
10769-
10760+
1077010761
assert len(results) > 0, "Multi-statement query should return results"
10771-
assert results[0][1] == 'test', "Should return string- test"
10772-
10762+
assert results[0][1] == 'test', "Should return string 'test'"
10763+
1077310764
except Exception as e:
1077410765
pytest.fail(f"Multi-statement query test failed: {e}")
1077510766
finally:
10776-
try:
10777-
cursor.execute("DROP TABLE #temp1")
10778-
db_connection.commit()
10779-
except:
10780-
pass
10767+
drop_table_if_exists(cursor, table_name)
10768+
db_connection.commit()
1078110769

1078210770
def test_multiple_result_sets_with_nextset(cursor, db_connection):
1078310771
"""Test multiple result sets with multiple select statements on temp tables with nextset()"""
10772+
table_name1 = "#TempData1"
10773+
table_name2 = "#TempData2"
1078410774
try:
10775+
drop_table_if_exists(cursor, table_name1)
10776+
drop_table_if_exists(cursor, table_name2)
10777+
1078510778
# Create temp tables and execute multiple SELECT statements
10786-
multi_select_sql = """
10787-
CREATE TABLE #TempData1 (id INT, name NVARCHAR(50));
10788-
INSERT INTO #TempData1 VALUES (1, 'First'), (2, 'Second');
10779+
multi_select_sql = f"""
10780+
CREATE TABLE {table_name1} (id INT, name NVARCHAR(50));
10781+
INSERT INTO {table_name1} VALUES (1, 'First'), (2, 'Second');
1078910782
10790-
CREATE TABLE #TempData2 (id INT, value INT);
10791-
INSERT INTO #TempData2 VALUES (1, 100), (2, 200);
10783+
CREATE TABLE {table_name2} (id INT, value INT);
10784+
INSERT INTO {table_name2} VALUES (1, 100), (2, 200);
1079210785
10793-
SELECT id, name FROM #TempData1 ORDER BY id;
10794-
SELECT id, value FROM #TempData2 ORDER BY id;
10795-
SELECT t1.name, t2.value FROM #TempData1 t1 JOIN #TempData2 t2 ON t1.id = t2.id ORDER BY t1.id;
10786+
SELECT id, name FROM {table_name1} ORDER BY id;
10787+
SELECT id, value FROM {table_name2} ORDER BY id;
10788+
SELECT t1.name, t2.value FROM {table_name1} t1 JOIN {table_name2} t2 ON t1.id = t2.id ORDER BY t1.id;
1079610789
"""
1079710790

1079810791
cursor.execute(multi_select_sql)
@@ -10825,25 +10818,24 @@ def test_multiple_result_sets_with_nextset(cursor, db_connection):
1082510818
except Exception as e:
1082610819
pytest.fail(f"Multiple result sets with nextset test failed: {e}")
1082710820
finally:
10828-
try:
10829-
cursor.execute("DROP TABLE #TempData1")
10830-
cursor.execute("DROP TABLE #TempData2")
10831-
db_connection.commit()
10832-
except:
10833-
pass
10821+
drop_table_if_exists(cursor, table_name1)
10822+
drop_table_if_exists(cursor, table_name2)
10823+
db_connection.commit()
1083410824

1083510825
def test_semicolons_in_string_literals(cursor, db_connection):
1083610826
"""Test semicolons in string literals to ensure no false positives in buffering logic"""
10827+
table_name = "#StringTest"
1083710828
try:
10829+
drop_table_if_exists(cursor, table_name)
1083810830
# SQL with semicolons inside string literals - should not be treated as statement separators
10839-
sql_with_semicolons = """
10840-
CREATE TABLE #StringTest (id INT, data NVARCHAR(200));
10841-
INSERT INTO #StringTest VALUES
10831+
sql_with_semicolons = f"""
10832+
CREATE TABLE {table_name} (id INT, data NVARCHAR(200));
10833+
INSERT INTO {table_name} VALUES
1084210834
(1, 'Value with; semicolon inside'),
1084310835
(2, 'Another; value; with; multiple; semicolons'),
1084410836
(3, 'Normal value');
1084510837
SELECT id, data, 'Status: OK; Processing: Complete' as status_message
10846-
FROM #StringTest
10838+
FROM {table_name}
1084710839
WHERE data LIKE '%semicolon%' OR data = 'Normal value'
1084810840
ORDER BY id;
1084910841
"""
@@ -10859,21 +10851,20 @@ def test_semicolons_in_string_literals(cursor, db_connection):
1085910851
except Exception as e:
1086010852
pytest.fail(f"Semicolons in string literals test failed: {e}")
1086110853
finally:
10862-
try:
10863-
cursor.execute("DROP TABLE #StringTest")
10864-
db_connection.commit()
10865-
except:
10866-
pass
10854+
drop_table_if_exists(cursor, table_name)
10855+
db_connection.commit()
1086710856

1086810857
def test_multi_statement_batch_final_non_select(cursor, db_connection):
1086910858
"""Test multi-statement batch where the final statement is not a SELECT"""
10859+
table_name = "#BatchTest"
1087010860
try:
10861+
drop_table_if_exists(cursor, table_name)
1087110862
# Multi-statement batch ending with non-SELECT statement
10872-
multi_statement_non_select = """
10873-
CREATE TABLE #BatchTest (id INT, name NVARCHAR(50), created_at DATETIME);
10874-
INSERT INTO #BatchTest VALUES (1, 'Test1', GETDATE()), (2, 'Test2', GETDATE());
10875-
SELECT COUNT(*) as record_count FROM #BatchTest;
10876-
UPDATE #BatchTest SET name = name + '_updated' WHERE id IN (1, 2);
10863+
multi_statement_non_select = f"""
10864+
CREATE TABLE {table_name} (id INT, name NVARCHAR(50), created_at DATETIME);
10865+
INSERT INTO {table_name} VALUES (1, 'Test1', GETDATE()), (2, 'Test2', GETDATE());
10866+
SELECT COUNT(*) as record_count FROM {table_name};
10867+
UPDATE {table_name} SET name = name + '_updated' WHERE id IN (1, 2);
1087710868
"""
1087810869

1087910870
cursor.execute(multi_statement_non_select)
@@ -10884,7 +10875,7 @@ def test_multi_statement_batch_final_non_select(cursor, db_connection):
1088410875
assert results[0][0] == 2, "Should count 2 records"
1088510876

1088610877
# Verify the UPDATE was executed by checking the updated records
10887-
cursor.execute("SELECT name FROM #BatchTest ORDER BY id")
10878+
cursor.execute(f"SELECT name FROM {table_name} ORDER BY id")
1088810879
updated_results = cursor.fetchall()
1088910880
assert len(updated_results) == 2, "Should have 2 updated records"
1089010881
assert updated_results[0][0] == 'Test1_updated', "First record should be updated"
@@ -10893,8 +10884,16 @@ def test_multi_statement_batch_final_non_select(cursor, db_connection):
1089310884
except Exception as e:
1089410885
pytest.fail(f"Multi-statement batch with final non-SELECT test failed: {e}")
1089510886
finally:
10896-
try:
10897-
cursor.execute("DROP TABLE #BatchTest")
10898-
db_connection.commit()
10899-
except:
10900-
pass
10887+
drop_table_if_exists(cursor, table_name)
10888+
db_connection.commit()
10889+
10890+
def test_close(db_connection):
10891+
"""Test closing the cursor"""
10892+
try:
10893+
cursor = db_connection.cursor()
10894+
cursor.close()
10895+
assert cursor.closed, "Cursor should be closed after calling close()"
10896+
except Exception as e:
10897+
pytest.fail(f"Cursor close test failed: {e}")
10898+
finally:
10899+
cursor = db_connection.cursor()

0 commit comments

Comments
 (0)