Repository: dotnet/efcore

Issue:
Wrong ErrorCode reported by SqliteException
When using `sqlite3_limit` to limit length of string or blob (`SQLITE_LIMIT_LENGTH` from https://www.sqlite.org/c3ref/c_limit_attached.html) and running an insert statement with value larger than the limit, the `SqliteException` has wrong `SqliteErrorCode` and `SqliteExtendedErrorCode`

### Include your code

```csharp
var connection = new SqliteConnection("Data Source=sqlite_test.db;");

connection.Open();

var command = connection.CreateCommand();
command.CommandText = @"CREATE TABLE ""Products"" (
          ""Id"" INTEGER NOT NULL CONSTRAINT ""PK_Products"" PRIMARY KEY AUTOINCREMENT,
          ""Name"" TEXT NOT NULL
      );";
command.ExecuteNonQuery();

var limit = SetLimit(connection.Handle, 0, 10);

command.CommandText = @"INSERT INTO ""Products"" (""Name"")  VALUES (@p0);";
command.Parameters.Add("@p0", SqliteType.Text);
command.Parameters[0].Value = new string('A', 15);

try
{
    command.ExecuteReader();
}
catch (SqliteException ex)
{
    Console.WriteLine($"Sqlite error: {ex.SqliteErrorCode} {ex.SqliteExtendedErrorCode}");
}
finally
{
    connection.Close();
}

[DllImport("e_sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_limit")]
static extern int SetLimit(sqlite3 db, int id, int newVal);
```

This prints 19 and 1299 (`SQLITE_CONSTRAINT` and `SQLITE_CONSTRAINT_NOTNULL` respectively) instead of printing 18 and 18 (`SQLITE_TOOBIG`)

If I put the value directly in the SQL statement the correct error code is reported:

`command.CommandText = @"INSERT INTO ""Products"" (""Name"")  VALUES ('AAAAAAAAAAAAAAA');";`

Also, when using EF Core and SQLite together the behavior in EF Core 3.1.1 and Microsoft.Data.SQLite was that the insert statement was failing with the correct error code (`SQLITE_TOOBIG`). I have a unit test that verifies that behavior: https://github.com/Giorgi/EntityFramework.Exceptions/blob/main/EntityFramework.Exceptions.Tests/SqliteTests.cs#L24 but the test fails when I upgrade to EF Core 6. Did the interaction between EF Core and SQLite provider change so drasticly between these versions that result in the new behavior? For example, did the EF Core 3 not use parameterized insert statements or is there a change in the way parameters are passed to the SQLite driver?

Finally, the SQLite shell works as expected:

![image](https://user-images.githubusercontent.com/580749/157325636-b8d3f5a8-b611-40d7-9bd9-bf19320bbcc3.png)


### Version information

Microsoft.Data.Sqlite version: 6.0.3
Target framework: .Net 6
Operating system: Windows



Hints:
Also, if I explicitly set the parameter size to zero I get the `SQLITE_TOOBIG` error:

```csharp
command.CommandText = @"INSERT INTO ""Products"" (""Name"")  VALUES (@p0);";
command.Parameters.Add("@p0", SqliteType.Text, 0);
command.Parameters[0].Value = new string('A', 15);
```
The error code is `SQLITE_CONSTRAINT` and `SQLITE_CONSTRAINT_NOTNULL` in 3.1.3 version of the provider but when using EF Core, the error code is `SQLITE_TOOBIG` so something must have changed in the way EF Core and Microsoft.Data.SQLite interact.
@Giorgi Are you using the same version of the native SQLite library and the PCLraw package in both cases? Or are these also changing with EF version?
@ajcvickers I use the version that the Microsoft.Data.SQLite pulls as a dependency but I'm pretty sure that this isn't caused by SQLite because when I tried to reproduce it with plain ADO.NET with the version of `Microsoft.Data.SQLite` that is used by my test I couldn't get it to throw SqliteException with ErrorCode equal to `SQLITE_TOOBIG`. The only case when I can make SQLite throw `SQLITE_TOOBIG` is when I set the parameter size to zero in the second comment.

To summarize, the behavior does not change when using plain ADO.NET between the old version and new version of Microsoft.Data.SQLite. The behavior only changes depending how I set parameter or when I upgrade between EF Core versions
I tried doing running the same ado.net code with [System.Data.SQLite.Core](https://www.nuget.org/packages/System.Data.SQLite.Core/) and I get `string or blob too big` error even if I do not set the parameter size explicitly:

```cs
var connection = new SQLiteConnection("Data Source=sqlite_test.db;");

connection.Open();

var command = connection.CreateCommand();
command.CommandText = @"CREATE TABLE ""Products"" (
          ""Id"" INTEGER NOT NULL CONSTRAINT ""PK_Products"" PRIMARY KEY AUTOINCREMENT,
          ""Name"" TEXT NOT NULL
      );";
command.ExecuteNonQuery();

connection.SetLimitOption(SQLiteLimitOpsEnum.SQLITE_LIMIT_LENGTH, 10);

command.CommandText = @"INSERT INTO ""Products"" (""Name"")  VALUES (@p0);";
command.Parameters.Add("@p0", DbType.String);
command.Parameters[0].Value = new string('A', 15);

try
{
	command.ExecuteReader();
}
catch (SQLiteException ex)
{
	Console.WriteLine($"Sqlite error: {ex.ErrorCode} ");
}
finally
{
	connection.Close();
}
```

This results in **SQLite error: 18** 

If I do the same with [Microsoft.Data.Sqlite.Core](https://www.nuget.org/packages/Microsoft.Data.Sqlite.Core/) I get `SQLITE_CONSTRAINT` and `SQLITE_CONSTRAINT_NOTNULL`
@bricelam Any update on this bug?
I haven't looked into it yet. I'm focusing on my 7.0 enhancements right now. Will shift my focus to bugs closer to the end of the release.

Patch:
diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs b/src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs
index bbeb7ff3724..49dc290f878 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteCommand.cs
@@ -320,7 +320,7 @@ private IEnumerable<sqlite3_stmt> GetStatements()
                 ? PrepareAndEnumerateStatements()
                 : _preparedStatements)
             {
-                var boundParams = _parameters?.Bind(stmt) ?? 0;
+                var boundParams = _parameters?.Bind(stmt, Connection!.Handle!) ?? 0;
 
                 if (expectedParams != boundParams)
                 {
diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteParameter.cs b/src/Microsoft.Data.Sqlite.Core/SqliteParameter.cs
index e71073b8300..113d490ef6c 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteParameter.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteParameter.cs
@@ -203,7 +203,7 @@ public virtual void ResetSqliteType()
             _sqliteType = null;
         }
 
-        internal bool Bind(sqlite3_stmt stmt)
+        internal bool Bind(sqlite3_stmt stmt, sqlite3 handle)
         {
             if (string.IsNullOrEmpty(ParameterName))
             {
@@ -222,7 +222,7 @@ internal bool Bind(sqlite3_stmt stmt)
                 throw new InvalidOperationException(Resources.RequiresSet(nameof(Value)));
             }
 
-            new SqliteParameterBinder(stmt, index, _value, _size, _sqliteType).Bind();
+            new SqliteParameterBinder(stmt, handle, index, _value, _size, _sqliteType).Bind();
 
             return true;
         }
diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteParameterBinder.cs b/src/Microsoft.Data.Sqlite.Core/SqliteParameterBinder.cs
index ffbaefafcef..214bdb3e3cc 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteParameterBinder.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteParameterBinder.cs
@@ -10,13 +10,15 @@ namespace Microsoft.Data.Sqlite
     internal class SqliteParameterBinder : SqliteValueBinder
     {
         private readonly sqlite3_stmt _stmt;
+        private readonly sqlite3 _handle;
         private readonly int _index;
         private readonly int? _size;
 
-        public SqliteParameterBinder(sqlite3_stmt stmt, int index, object value, int? size, SqliteType? sqliteType)
+        public SqliteParameterBinder(sqlite3_stmt stmt, sqlite3 handle, int index, object value, int? size, SqliteType? sqliteType)
             : base(value, sqliteType)
         {
             _stmt = stmt;
+            _handle = handle;
             _index = index;
             _size = size;
         }
@@ -30,26 +32,43 @@ protected override void BindBlob(byte[] value)
                 Array.Copy(value, blob, _size.Value);
             }
 
-            sqlite3_bind_blob(_stmt, _index, blob);
+            var rc = sqlite3_bind_blob(_stmt, _index, blob);
+            SqliteException.ThrowExceptionForRC(rc, _handle);
         }
 
         protected override void BindDoubleCore(double value)
-            => sqlite3_bind_double(_stmt, _index, value);
+        {
+            var rc = sqlite3_bind_double(_stmt, _index, value);
+
+            SqliteException.ThrowExceptionForRC(rc, _handle);
+        }
 
         protected override void BindInt64(long value)
-            => sqlite3_bind_int64(_stmt, _index, value);
+        {
+            var rc = sqlite3_bind_int64(_stmt, _index, value);
+
+            SqliteException.ThrowExceptionForRC(rc, _handle);
+        }
 
         protected override void BindNull()
-            => sqlite3_bind_null(_stmt, _index);
+        {
+            var rc = sqlite3_bind_null(_stmt, _index);
+
+            SqliteException.ThrowExceptionForRC(rc, _handle);
+        }
 
         protected override void BindText(string value)
-            => sqlite3_bind_text(
+        {
+            var rc = sqlite3_bind_text(
                 _stmt,
                 _index,
                 ShouldTruncate(value.Length)
                     ? value.Substring(0, _size!.Value)
                     : value);
 
+            SqliteException.ThrowExceptionForRC(rc, _handle);
+        }
+
         private bool ShouldTruncate(int length)
             => _size.HasValue
                 && length > _size.Value
diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteParameterCollection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteParameterCollection.cs
index 6ec9439a7a3..5688af4f6d0 100644
--- a/src/Microsoft.Data.Sqlite.Core/SqliteParameterCollection.cs
+++ b/src/Microsoft.Data.Sqlite.Core/SqliteParameterCollection.cs
@@ -325,12 +325,12 @@ protected override void SetParameter(int index, DbParameter value)
         protected override void SetParameter(string parameterName, DbParameter value)
             => SetParameter(IndexOfChecked(parameterName), value);
 
-        internal int Bind(sqlite3_stmt stmt)
+        internal int Bind(sqlite3_stmt stmt, sqlite3 handle)
         {
             var bound = 0;
             foreach (var parameter in _parameters)
             {
-                if (parameter.Bind(stmt))
+                if (parameter.Bind(stmt, handle))
                 {
                     bound++;
                 }


Test patch:
diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteCommandTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteCommandTest.cs
index 02a7951defe..aab6d37a187 100644
--- a/test/Microsoft.Data.Sqlite.Tests/SqliteCommandTest.cs
+++ b/test/Microsoft.Data.Sqlite.Tests/SqliteCommandTest.cs
@@ -15,6 +15,62 @@ namespace Microsoft.Data.Sqlite;
 
 public class SqliteCommandTest
 {
+    [Theory]
+    [InlineData(true)]
+    [InlineData(false)]
+    public async Task Correct_error_code_is_returned_when_parameter_is_too_long(bool async) // Issue #27597
+    {
+        using var connection = new SqliteConnection("Data Source=:memory:");
+
+        if (async)
+        {
+            await connection.OpenAsync();
+        }
+        else
+        {
+            connection.Open();
+        }
+
+        using var command = connection.CreateCommand();
+        command.CommandText = """
+CREATE TABLE "Products" (
+          "Id" INTEGER NOT NULL CONSTRAINT "PK_Products" PRIMARY KEY AUTOINCREMENT,
+          "Name" TEXT NOT NULL
+      );
+""";
+        _ = async ? await command.ExecuteNonQueryAsync() : command.ExecuteNonQuery();
+
+        sqlite3_limit(connection.Handle!, 0, 10);
+
+        command.CommandText = @"INSERT INTO ""Products"" (""Name"")  VALUES (@p0);";
+        command.Parameters.Add("@p0", SqliteType.Text);
+        command.Parameters[0].Value = new string('A', 15);
+
+        try
+        {
+            _ = async ? await command.ExecuteReaderAsync() : command.ExecuteReader();
+        }
+        catch (SqliteException ex)
+        {
+            Assert.Equal(18, ex.SqliteErrorCode);
+        }
+        finally
+        {
+#if NET5_0_OR_GREATER
+            if (async)
+            {
+                await connection.CloseAsync();
+            }
+            else
+            {
+                connection.Close();
+            }
+#else
+            connection.Close();
+#endif
+        }
+    }
+
     [Fact]
     public void Ctor_sets_values()
     {