Skip to content

"not an error" exception thrown on startup #80

@drew-fc

Description

@drew-fc

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

  1. Create a new SQLite database using SQLiteConnection
  2. Call CreateTable() for multiple entity types
  3. Run on a device (more likely to occur than Editor)
  4. 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:

  1. SQLite3.Step(stmt) executes and returns a result code r
  2. Finalize(stmt) is called, which resets SQLite's error state to OK/"not an error"
  3. The code checks r and may fall through to the final throw
  4. SQLite3.GetErrmsg() is called AFTER Finalize(), but Finalize() already reset it
  5. sqlite3_errmsg() returns "not an error" (the literal string for success code 0)
  6. 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:

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions