Skip to content

Commit aa04e25

Browse files
authored
Fix RecordsAffected incorrectly accumulating counts from DDL statements (#37397)
1 parent 9a5d5f4 commit aa04e25

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

src/Microsoft.Data.Sqlite.Core/SqliteDataReader.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ public override bool NextResult()
154154
{
155155
stmt = _stmtEnumerator.Current;
156156

157+
var connectionHandle = _command.Connection!.Handle;
158+
var totalChangesBefore = sqlite3_total_changes(connectionHandle);
159+
157160
var timer = SharedStopwatch.StartNew();
158161

159162
while (IsBusy(rc = sqlite3_step(stmt)))
@@ -172,7 +175,7 @@ public override bool NextResult()
172175

173176
_totalElapsedTime += timer.Elapsed;
174177

175-
SqliteException.ThrowExceptionForRC(rc, _command.Connection!.Handle);
178+
SqliteException.ThrowExceptionForRC(rc, connectionHandle);
176179

177180
// It's a SELECT statement
178181
if (sqlite3_column_count(stmt) != 0)
@@ -185,13 +188,26 @@ public override bool NextResult()
185188
while (rc != SQLITE_DONE)
186189
{
187190
rc = sqlite3_step(stmt);
188-
SqliteException.ThrowExceptionForRC(rc, _command.Connection.Handle);
191+
SqliteException.ThrowExceptionForRC(rc, connectionHandle);
189192
}
190193

191194
sqlite3_reset(stmt);
192195

193-
var changes = sqlite3_changes(_command.Connection.Handle);
194-
AddChanges(changes);
196+
// sqlite3_changes() returns the row count from the most recent INSERT, UPDATE, or DELETE
197+
// and incorrectly persists across DDL statements. Use sqlite3_total_changes() before and after
198+
// to calculate the actual delta for this statement, ensuring DDL statements don't add stale counts.
199+
var totalChangesAfter = sqlite3_total_changes(connectionHandle);
200+
var changes = totalChangesAfter - totalChangesBefore;
201+
// sqlite3_total_changes, unfortunately, counts also changes from triggers, etc. which is not what we want.
202+
// So we use it only to detect changes and if so, use sqlite3_changes.
203+
if (changes > 0)
204+
{
205+
AddChanges(sqlite3_changes(connectionHandle));
206+
}
207+
else
208+
{
209+
AddChanges(0);
210+
}
195211
}
196212
catch
197213
{

test/Microsoft.Data.Sqlite.Tests/SqliteConnectionTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,7 @@ public void CreateFunction_deterministic_param_works()
812812
connection.ExecuteNonQuery("CREATE TABLE Data (Value); INSERT INTO Data VALUES (0);");
813813
connection.CreateFunction("test", (double x) => x, true);
814814

815-
Assert.Equal(1, connection.ExecuteNonQuery("CREATE INDEX InvalidIndex ON Data (Value) WHERE test(Value) = 0;"));
815+
Assert.Equal(0, connection.ExecuteNonQuery("CREATE INDEX InvalidIndex ON Data (Value) WHERE test(Value) = 0;"));
816816
}
817817

818818
[Fact]

test/Microsoft.Data.Sqlite.Tests/SqliteDataReaderTest.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,45 @@ public void RecordsAffected_works_with_returning_multiple()
19701970
}
19711971
}
19721972

1973+
[Fact]
1974+
public void RecordsAffected_not_affected_by_DDL_statements()
1975+
{
1976+
using (var connection = new SqliteConnection("Data Source=:memory:"))
1977+
{
1978+
connection.Open();
1979+
1980+
using (var reader = connection.ExecuteReader(
1981+
@"CREATE TABLE foo(bar TEXT NOT NULL);
1982+
CREATE TABLE xyz(aaa TEXT NOT NULL);
1983+
INSERT INTO foo(bar) VALUES('baz');
1984+
INSERT INTO foo(bar) VALUES('baz2');
1985+
DROP TABLE xyz;"))
1986+
{
1987+
Assert.Equal(2, reader.RecordsAffected);
1988+
}
1989+
}
1990+
}
1991+
1992+
[Fact]
1993+
public void RecordsAffected_not_affected_by_DDL_statements_with_drop_and_create()
1994+
{
1995+
using (var connection = new SqliteConnection("Data Source=:memory:"))
1996+
{
1997+
connection.Open();
1998+
1999+
using (var reader = connection.ExecuteReader(
2000+
@"CREATE TABLE foo(bar TEXT NOT NULL);
2001+
CREATE TABLE xyz(aaa TEXT NOT NULL);
2002+
INSERT INTO foo(bar) VALUES('baz');
2003+
INSERT INTO foo(bar) VALUES('baz2');
2004+
DROP TABLE xyz;
2005+
CREATE TABLE xyz(aaa TEXT NOT NULL);"))
2006+
{
2007+
Assert.Equal(2, reader.RecordsAffected);
2008+
}
2009+
}
2010+
}
2011+
19732012
[Fact]
19742013
public void GetSchemaTable_works()
19752014
{

0 commit comments

Comments
 (0)