SQLITE异常处理:为什么在外键冲突后必须重置语句两次?

quhf5bfb  于 2023-01-05  发布在  SQLite
关注(0)|答案(1)|浏览(138)

我使用的是SQLITE 3.8.7,并尝试在语句未通过某个约束(如违反外键约束)或sqlite3_step语句返回SQLITE_MISUSE时创建一个自定义异常处理类。
在析构函数中,我执行1)回滚; 2)清理; 3)记录异常。

~SQLException()
{
    try
    {
        _db->Execute("ROLLBACK");
        try
        {
           _db->ResetStatement();
        }
        catch (sql_exception anotherException)
        {
           _db->ResetStatement();
        }

        LOG_ERROR("sql_exception: %d %s\n", _e.code, _e.message.c_str());
    }
    catch (sql_execption ex)
    {
        LOG_ERROR("sql_exception: %d %s\n", ex.code, ex.message.c_str());
    }

Execute()只是sqlite3_exec的 Package 器,而ResetStatement()是sqlite3_reset()的 Package 器。

void DatabaseConnection::Execute(const char* text)
{
   ASSERT(_handle);

   auto const result = sqlite3_exec(_handle.get(), text, nullptr, nullptr, nullptr);

   if (SQLITE_OK != result)
   {
      throw sql_exception(result, sqlite3_errmsg(_handle.get()));
   }
}

void DatabaseConnection::ResetStatement()
{
   auto const result = sqlite3_reset(_stmt.get());

   if (SQLITE_OK != result)
   {
      throw sql_exception(result, sqlite3_errmsg(sqlite3_db_handle(_stmt.get())));
   }
}

我写了一个单元测试,触发了一个外键约束冲突(这触发了这个清理代码),然后尝试执行额外的插入,就像什么也没发生一样。回滚工作正常,但是第一次重置抛出了同样的外键约束冲突异常。只有当我第二次调用ResetStatement()时,事情才被清理干净,我才能继续执行额外的插入等等。

    • 为什么需要调用两次reset语句?**

谢谢。
UPDATE:下面是一个简单的示例,它会导致两个ResetStatements()都被命中...

Table 1: Teams
TeamID INTEGER NOT NULL,
Name TEXT NOT NULL,
PRIMARY KEY(TeamID)

Table 2: Players
PlayerID INTEGER NOT NULL,
Name TEXT NOT NULL,
TeamID INTEGER NOT NULL,
PRIMARY KEY(PlayerID),
FOREIGN KEY(TeamID) REFERENCES Teams(TeamID)

try
{
    INSERT INTO Players VALUES(1, "Joe Montana", 1) -->causes expected Foreign Key constraint violation and you cannot proceed with another insert until ResetStatement() is called twice.
}
catch (...)
{
    SQLException();
}

到目前为止,这似乎只发生在外键约束冲突。我抛出的其他异常似乎只在一次调用后就被正确重置。
这是原始数据库代码。注解行保留了第一次传递时的外键约束冲突(rc == 19),但在第二次传递时仍存在(rc == 0)。

SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt){
  int rc;
  if( pStmt==0 ){
    rc = SQLITE_OK;
  }else{
    Vdbe *v = (Vdbe*)pStmt;
    sqlite3_mutex_enter(v->db->mutex);
    rc = sqlite3VdbeReset(v); //first pass rc = 19, second pass rc = 0
    sqlite3VdbeRewind(v);
    assert( (rc & (v->db->errMask))==rc );
    rc = sqlite3ApiExit(v->db, rc);
    sqlite3_mutex_leave(v->db->mutex);
  }
  return rc;
}

在SQLite的最新版本3.8.11.1中存在相同的行为。

r6l8ljro

r6l8ljro1#

所描述的sqlite3_reset的行为现在是documented
如果最近一次为预准备语句S调用sqlite3_step(S)时返回了SQLITE_ROW或SQLITE_DONE,或者以前从未在S上调用过sqlite3_step(S),则sqlite3_reset(S)返回SQLITE_OK。
如果最近一次为预准备语句S调用sqlite3_step(S)时指示错误,则sqlite3_reset(S)返回相应的错误代码。
这可能是sqlite3_step的初始goofy interface的遗留问题,它只返回一般的SQLITE_ERROR代码,并且需要调用sqlite3_resetsqlite3_finalize才能找到特定的错误代码,这更好地描述了错误。从那时起,接口已经更新,允许sqlite3_step直接返回特定的错误代码。但是sqlite3_reset仍然以旧的方式工作。

相关问题