-
-
Notifications
You must be signed in to change notification settings - Fork 37
Description
When using SQLiteConnection with concurrent database access or during initialization, a SQLiteException is thrown with
the message "not an error" and Result.OK (0). This is a false-positive exception - the operation actually succeeded,
but the library incorrectly throws an exception.
Environment
- unity-sqlite-net version: 1.3.2
- Unity version: 6000.0.60f1
- Platform: iOS, Android, Windows (reproducible on all)
- Scenario: Database initialization with CreateTable(), concurrent access from multiple threads
Steps to Reproduce
- Create a new SQLite database using SQLiteConnection
- Call CreateTable() for multiple entity types
- Run on a device (more likely to occur than Editor)
- Observe SQLiteException: not an error thrown intermittently
var connection = new SQLiteConnection(dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create |
SQLiteOpenFlags.FullMutex);
connection.CreateTable(); // Throws "not an error" intermittently
Root Cause Analysis
The bug is in SQLite.cs in the ExecuteNonQuery() method (lines 3066-3091):
public int ExecuteNonQuery()
{
var r = SQLite3.Result.OK;
var stmt = Prepare();
r = SQLite3.Step(stmt);
Finalize(stmt); // ← Problem: This resets the error state
if (r == SQLite3.Result.Done) {
return SQLite3.Changes(_conn.Handle);
}
else if (r == SQLite3.Result.Error) {
string msg = SQLite3.GetErrmsg(_conn.Handle);
throw SQLiteException.New(r, msg);
}
// ... other checks ...
throw SQLiteException.New(r, SQLite3.GetErrmsg(_conn.Handle)); // ← Gets wrong message
}
The problem:
- SQLite3.Step(stmt) executes and returns a result code r
- Finalize(stmt) is called, which resets SQLite's error state to OK/"not an error"
- The code checks r and may fall through to the final throw
- SQLite3.GetErrmsg() is called AFTER Finalize(), but Finalize() already reset it
- sqlite3_errmsg() returns "not an error" (the literal string for success code 0)
- An exception is thrown with a misleading message
This is exacerbated by concurrent access where:
- Thread A executes Step() → gets error
- Thread B executes something that succeeds → resets error state
- Thread A calls GetErrmsg() → gets "not an error" from Thread B's success
Proposed Fix
Capture the error message BEFORE calling Finalize():
public int ExecuteNonQuery()
{
var r = SQLite3.Result.OK;
var stmt = Prepare();
r = SQLite3.Step(stmt);
// Capture error message BEFORE Finalize resets it
string errorMsg = null;
if (r != SQLite3.Result.Done && r != SQLite3.Result.Row) {
errorMsg = SQLite3.GetErrmsg(_conn.Handle);
}
Finalize(stmt);
if (r == SQLite3.Result.Done) {
return SQLite3.Changes(_conn.Handle);
}
else if (r == SQLite3.Result.Error) {
throw SQLiteException.New(r, errorMsg);
}
else if (r == SQLite3.Result.Constraint) {
if (SQLite3.ExtendedErrCode(_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) {
throw NotNullConstraintViolationException.New(r, errorMsg);
}
}
throw SQLiteException.New(r, errorMsg ?? "Unknown error");
}
The same pattern should be applied to:
- PreparedSqlLiteInsertCommand.ExecuteNonQuery() (line 3741)
- Any other location that calls GetErrmsg() after Finalize()
Current Workaround
We detect and ignore these false-positive exceptions:
private bool IsFalsePositiveException(SQLiteException ex)
{
// Result.OK = 0 means the operation actually succeeded
if (ex.Result == SQLite3.Result.OK)
return true;
// "not an error" is the literal string from sqlite3_errmsg() for success
if (ex.Message == "not an error")
return true;
return false;
}
// Usage:
try
{
connection.CreateTable();
}
catch (SQLiteException ex) when (IsFalsePositiveException(ex))
{
// False positive - operation actually succeeded
}
Related Issues
This is a known issue in the upstream sqlite-net library and related projects:
- iOS + SQLCipher = SQLite.SQLiteException: not an error praeclarum/sqlite-net#1041
- SqliteException - SQLite Error 1: 'not an error'. dotnet/efcore#31139
Impact
- High for apps with concurrent SQLite access
- Medium for apps with complex initialization
- Causes spurious crashes/errors that are difficult to debug due to misleading message