Cursor.nextset() does not update rowcount for the next result
Mantenedores costumam responder em até 2 dias
Avaliação
- Dificuldade
- 2/5
- Tempo estimado
- 1-3 horas
- Facilidade para iniciantes
- 82/100
Direção de pesquisa
Comece por Cursor_nextset e pela implementação de execute() para comparar como cada um atualiza rowcount; a issue nomeia esses pontos de entrada, mas não os caminhos dos arquivos. Em seguida, leia os testes existentes do cursor em tests/test_004_cursor.py e adicione cobertura para contagens sucessivas de linhas afetadas e um conjunto de resultados. O trabalho está concluído quando rowcount refletir cada resultado após nextset() e for -1 para um conjunto de resultados.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Describe the bug
After Cursor.nextset() moves to the next result of a multi-statement batch, Cursor.rowcount still holds the value set by execute() for the first statement. The affected-row count of every later INSERT/UPDATE/DELETE in the batch therefore cannot be read through the DB API.
nextset() calls SQLMoreResults and refreshes description and messages, but it never calls SQLRowCount, unlike execute(). pyodbc refreshes rowcount there (Cursor_nextset calls SQLRowCount after SQLMoreResults), and nextset()'s docstring cites pyodbc parity.
This matters most for T-SQL batches with control flow. A WHILE loop that inserts rows sends one result per INSERT, and the only way to count the rows the batch changed is to read rowcount after each nextset(). Tools that report "N rows affected" per batch (migration runners, deployment CLIs) under-report, and a loop that inserted 5 rows shows 0 or 1.
No exception is raised.
To reproduce
import mssql_python
from mssql_python import ddbc_bindings
conn = mssql_python.connect(
"Server=localhost,1433;Database=tempdb;UID=sa;PWD=<password>;"
"Encrypt=yes;TrustServerCertificate=yes;"
)
cur = conn.cursor()
cur.execute("CREATE TABLE #t (id INT)")
cur.execute("""
INSERT INTO #t VALUES (1);
INSERT INTO #t VALUES (2), (3);
UPDATE #t SET id = id + 10;
""")
seen = [cur.rowcount]
native = [ddbc_bindings.DDBCSQLRowCount(cur.hstmt)]
while cur.nextset():
seen.append(cur.rowcount)
native.append(ddbc_bindings.DDBCSQLRowCount(cur.hstmt))
print("cursor.rowcount:", seen) # [1, 1, 1]
print("SQLRowCount: ", native) # [1, 2, 3]
With a loop, every INSERT of the WHILE is its own result:
cur.execute("""
DECLARE @i INT = 1;
WHILE @i <= 3
BEGIN
INSERT INTO #t SELECT TOP (@i) n FROM (VALUES (1), (2), (3)) AS v(n);
SET @i = @i + 1;
END;
""")
counts = [cur.rowcount]
while cur.nextset():
counts.append(cur.rowcount)
print(counts) # [1, 1, 1] -- expected [1, 2, 3]
Expected behavior
After each nextset() that returns True, rowcount reflects the result the cursor is now on, the same way it does after execute():
- the affected-row count for an INSERT/UPDATE/DELETE/MERGE result;
-1for a result set, whichfetch*()then updates as it does today.
For the first example that is [1, 2, 3]. This matches pyodbc on the same batch.
Further technical details
- Python version: 3.12
- mssql-python: 1.16.0 (also seen on 1.4.0; the code on
mainat bd0aaf89 is the same) - SQL Server version: SQL Server 2022 (16.0.4305.5, RTM-CU27-GDR)
- Operating system: Debian 12 (bookworm), in a container
Additional context
A fix is ready, and I can open a PR for it. After the describe step, nextset() would set rowcount the same way execute() does: -1 when the new result has columns, otherwise DDBCSQLRowCount(self.hstmt). It comes with two regression tests in tests/test_004_cursor.py.
I did not look at AsyncCursor.nextset(), which goes through py-core and may need its own check.
- Linguagem predominante
- Python
- Estrelas
- 481
- Forks
- 62
- Merge médio
- 2d 9h
- PRs com merge (30d)
- 11
Preparar o ambiente
Inicia o contêiner de desenvolvimento do projeto no navegador, com a sua própria conta do GitHub.
- Sem Dockerfile nem arquivo Docker Compose
- Tem um modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de microsoft/mssql-python
-
triage needed
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
microsoft/mssql-python#862 · 2 comentários ·
Mantenedores costumam responder em até 2 dias
-
Pooled connection checkout has no timeout: connect() hangs ~16 min on a dead socketTalvez já em andamento @jahnvi480 assumiu há 4 dias. Abertaarea: connectivity-auth bug inADO triage done under development
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
microsoft/mssql-python#854 · 3 comentários · 1 responsável ·
Mantenedores costumam responder em até 2 dias
-
triage needed
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 38/100
microsoft/mssql-python#850 · 2 comentários · 1 reação ·
Mantenedores costumam responder em até 2 dias
-
bulkcopy() & bulkcopy_arrow() fail with Schannel AcquireCredentialsHandleW error 0x8009030D (SEC_E_UNKNOWN_CREDENTIALS)Talvez já em andamento @jahnvi480 assumiu há 6 dias. Abertaarea: bulk-copy bug inADO triage done
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 20/100
microsoft/mssql-python#848 · 5 comentários · 1 responsável ·
Mantenedores costumam responder em até 2 dias
-
macOS: encrypted connections fail after Homebrew moved the `openssl` alias to `openssl@4`Talvez já em andamento @bewithgaurav assumiu há 26 dias. Abertaarea: packaging-platform bug triage needed
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 55/100
microsoft/mssql-python#826 · 2 comentários · 3 reações · 1 responsável ·
Mantenedores costumam responder em até 2 dias
Todas as issues de microsoft/mssql-python
Issues semelhantes
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
scikit-learn-contrib/category_encoders#553 ·
Mantenedores costumam responder em até 1 dia
-
tests/strategies_test.py:174 goes uncovered on about 1 run in 100, failing the coverage ratchetAbertaflaky-watch
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 88/100
btclib-org/btclib#2839 ·
Mantenedores costumam responder em até 1 dia
-
BarrelList constructor drops false-valued iterable contentsTalvez já em andamento @Gambit-Checkmate assumiu hoje. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
Mantenedores costumam responder em até 19 dias
-
[Performance]: Redundant object-storage pull when model and tokenizer share the same RunAI URIAberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 77/100
vllm-project/vllm#63863 ·
Mantenedores costumam responder em até 1 dia