| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sql/sqlite_result_code.h" |
| |
| #include <ostream> // Needed to compile NOTREACHED() with operator <<. |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notreached.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/string_piece.h" |
| #include "sql/sqlite_result_code_values.h" |
| #include "third_party/sqlite/sqlite3.h" |
| |
| namespace sql { |
| |
| namespace { |
| |
| // The highly packed representation minimizes binary size and memory usage. |
| struct SqliteResultCodeMappingEntry { |
| unsigned result_code : 16; |
| unsigned logged_code : 8; |
| |
| // The remaining bits will be used to encode the result values of helpers that |
| // indicate corruption handling. |
| }; |
| |
| constexpr SqliteResultCodeMappingEntry kResultCodeMapping[] = { |
| // Entries are ordered by SQLite result code value. This should match the |
| // ordering in https://www.sqlite.org/rescode.html. |
| |
| {SQLITE_OK, static_cast<int>(SqliteLoggedResultCode::kNoError)}, |
| {SQLITE_ERROR, static_cast<int>(SqliteLoggedResultCode::kGeneric)}, |
| {SQLITE_INTERNAL, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_PERM, static_cast<int>(SqliteLoggedResultCode::kPermission)}, |
| {SQLITE_ABORT, static_cast<int>(SqliteLoggedResultCode::kAbort)}, |
| {SQLITE_BUSY, static_cast<int>(SqliteLoggedResultCode::kBusy)}, |
| |
| // Chrome features shouldn't execute conflicting statements concurrently. |
| {SQLITE_LOCKED, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| // Chrome should crash on OOM. |
| {SQLITE_NOMEM, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_READONLY, static_cast<int>(SqliteLoggedResultCode::kReadOnly)}, |
| |
| // Chrome doesn't use sqlite3_interrupt(). |
| {SQLITE_INTERRUPT, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_IOERR, static_cast<int>(SqliteLoggedResultCode::kIo)}, |
| {SQLITE_CORRUPT, static_cast<int>(SqliteLoggedResultCode::kCorrupt)}, |
| |
| // Chrome only passes a few known-good opcodes to sqlite3_file_control(). |
| {SQLITE_NOTFOUND, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_FULL, static_cast<int>(SqliteLoggedResultCode::kFullDisk)}, |
| {SQLITE_CANTOPEN, static_cast<int>(SqliteLoggedResultCode::kCantOpen)}, |
| {SQLITE_PROTOCOL, |
| static_cast<int>(SqliteLoggedResultCode::kLockingProtocol)}, |
| {SQLITE_EMPTY, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_SCHEMA, static_cast<int>(SqliteLoggedResultCode::kSchemaChanged)}, |
| {SQLITE_TOOBIG, static_cast<int>(SqliteLoggedResultCode::kTooBig)}, |
| {SQLITE_CONSTRAINT, static_cast<int>(SqliteLoggedResultCode::kConstraint)}, |
| {SQLITE_MISMATCH, static_cast<int>(SqliteLoggedResultCode::kTypeMismatch)}, |
| |
| // Chrome should not misuse SQLite's API. |
| {SQLITE_MISUSE, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_NOLFS, |
| static_cast<int>(SqliteLoggedResultCode::kNoLargeFileSupport)}, |
| |
| // Chrome does not set an authorizer callback. |
| {SQLITE_AUTH, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_FORMAT, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| |
| // Chrome should not use invalid column indexes in sqlite3_{bind,column}*(). |
| {SQLITE_RANGE, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_NOTADB, static_cast<int>(SqliteLoggedResultCode::kNotADatabase)}, |
| {SQLITE_NOTICE, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_WARNING, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_ROW, static_cast<int>(SqliteLoggedResultCode::kNoError)}, |
| {SQLITE_DONE, static_cast<int>(SqliteLoggedResultCode::kNoError)}, |
| {SQLITE_OK_LOAD_PERMANENTLY, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| |
| // Chrome should not use collating sequence names in SQL statements. |
| {SQLITE_ERROR_MISSING_COLLSEQ, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_BUSY_RECOVERY, |
| static_cast<int>(SqliteLoggedResultCode::kBusyRecovery)}, |
| |
| // Chrome does not use a shared page cache. |
| {SQLITE_LOCKED_SHAREDCACHE, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_READONLY_RECOVERY, |
| static_cast<int>(SqliteLoggedResultCode::kReadOnlyRecovery)}, |
| {SQLITE_IOERR_READ, static_cast<int>(SqliteLoggedResultCode::kIoRead)}, |
| |
| // Chrome does not use a virtual table that signals corruption. We only use |
| // a |
| // virtual table code for recovery. That code does not use this error. |
| {SQLITE_CORRUPT_VTAB, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_CANTOPEN_NOTEMPDIR, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_CONSTRAINT_CHECK, |
| static_cast<int>(SqliteLoggedResultCode::kConstraintCheck)}, |
| |
| // Chrome does not set an authorizer callback. |
| {SQLITE_AUTH_USER, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_NOTICE_RECOVER_WAL, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_WARNING_AUTOINDEX, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_ERROR_RETRY, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_ABORT_ROLLBACK, |
| static_cast<int>(SqliteLoggedResultCode::kAbortRollback)}, |
| {SQLITE_BUSY_SNAPSHOT, |
| static_cast<int>(SqliteLoggedResultCode::kBusySnapshot)}, |
| |
| // Chrome does not use a virtual table that signals conflicts. We only use a |
| // virtual table code for recovery. That code does not use this error. |
| {SQLITE_LOCKED_VTAB, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_READONLY_CANTLOCK, |
| static_cast<int>(SqliteLoggedResultCode::kReadOnlyCantLock)}, |
| {SQLITE_IOERR_SHORT_READ, |
| static_cast<int>(SqliteLoggedResultCode::kIoShortRead)}, |
| {SQLITE_CORRUPT_SEQUENCE, |
| static_cast<int>(SqliteLoggedResultCode::kCorruptSequence)}, |
| {SQLITE_CANTOPEN_ISDIR, |
| static_cast<int>(SqliteLoggedResultCode::kCantOpenIsDir)}, |
| |
| // Chrome does not use commit hook callbacks. |
| {SQLITE_CONSTRAINT_COMMITHOOK, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_NOTICE_RECOVER_ROLLBACK, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| |
| // Chrome does not use sqlite3_snapshot_open(). |
| {SQLITE_ERROR_SNAPSHOT, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| #ifdef SQLITE_ENABLE_SNAPSHOT |
| #error "This code assumes that Chrome does not use sqlite3_snapshot_open()" |
| #endif |
| |
| // Chrome does not use blocking Posix advisory file lock requests. |
| {SQLITE_BUSY_TIMEOUT, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| #ifdef SQLITE_ENABLE_SETLK_TIMEOUT |
| #error "This code assumes that Chrome does not use blocking Posix advisory \ |
| file lock requests" |
| #endif |
| |
| {SQLITE_READONLY_ROLLBACK, |
| static_cast<int>(SqliteLoggedResultCode::kReadOnlyRollback)}, |
| {SQLITE_IOERR_WRITE, static_cast<int>(SqliteLoggedResultCode::kIoWrite)}, |
| {SQLITE_CORRUPT_INDEX, |
| static_cast<int>(SqliteLoggedResultCode::kCorruptIndex)}, |
| |
| // Chrome should always pass full paths to SQLite. |
| {SQLITE_CANTOPEN_FULLPATH, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_CONSTRAINT_FOREIGNKEY, |
| static_cast<int>(SqliteLoggedResultCode::kConstraintForeignKey)}, |
| {SQLITE_READONLY_DBMOVED, |
| static_cast<int>(SqliteLoggedResultCode::kReadOnlyDbMoved)}, |
| {SQLITE_IOERR_FSYNC, static_cast<int>(SqliteLoggedResultCode::kIoFsync)}, |
| |
| // Chrome does not support Cygwin and does not use its VFS. |
| {SQLITE_CANTOPEN_CONVPATH, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| // Chrome does not use extension functions. |
| {SQLITE_CONSTRAINT_FUNCTION, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_READONLY_CANTINIT, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_IOERR_DIR_FSYNC, |
| static_cast<int>(SqliteLoggedResultCode::kIoDirFsync)}, |
| {SQLITE_CANTOPEN_DIRTYWAL, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| {SQLITE_CONSTRAINT_NOTNULL, |
| static_cast<int>(SqliteLoggedResultCode::kConstraintNotNull)}, |
| {SQLITE_READONLY_DIRECTORY, |
| static_cast<int>(SqliteLoggedResultCode::kReadOnlyDirectory)}, |
| {SQLITE_IOERR_TRUNCATE, |
| static_cast<int>(SqliteLoggedResultCode::kIoTruncate)}, |
| |
| // Chrome does not use the SQLITE_OPEN_NOFOLLOW flag. |
| {SQLITE_CANTOPEN_SYMLINK, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_CONSTRAINT_PRIMARYKEY, |
| static_cast<int>(SqliteLoggedResultCode::kConstraintPrimaryKey)}, |
| {SQLITE_IOERR_FSTAT, static_cast<int>(SqliteLoggedResultCode::kIoFstat)}, |
| |
| // Chrome unconditionally disables database triggers via |
| // sqlite3_db_config(SQLITE_DBCONFIG_ENABLE_TRIGGER). |
| {SQLITE_CONSTRAINT_TRIGGER, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_IOERR_UNLOCK, static_cast<int>(SqliteLoggedResultCode::kIoUnlock)}, |
| {SQLITE_CONSTRAINT_UNIQUE, |
| static_cast<int>(SqliteLoggedResultCode::kConstraintUnique)}, |
| {SQLITE_IOERR_RDLOCK, |
| static_cast<int>(SqliteLoggedResultCode::kIoReadLock)}, |
| |
| // Chrome does not use a virtual table that signals constraints. We only use |
| // a virtual table code for recovery. That code does not use this error. |
| {SQLITE_CONSTRAINT_VTAB, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_IOERR_DELETE, static_cast<int>(SqliteLoggedResultCode::kIoDelete)}, |
| {SQLITE_CONSTRAINT_ROWID, |
| static_cast<int>(SqliteLoggedResultCode::kConstraintRowId)}, |
| {SQLITE_IOERR_BLOCKED, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| |
| // Chrome unconditionally disables database triggers via |
| // sqlite3_db_config(SQLITE_DBCONFIG_ENABLE_TRIGGER). |
| {SQLITE_CONSTRAINT_PINNED, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| // The SQLite docus claim that this error code is "normally" converted to |
| // SQLITE_NOMEM. This doesn't seem 100% categorical, so we're flagging this |
| // as "unused in Chrome" per the same rationale as SQLITE_NOMEM. |
| {SQLITE_IOERR_NOMEM, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_CONSTRAINT_DATATYPE, |
| static_cast<int>(SqliteLoggedResultCode::kConstraintDataType)}, |
| {SQLITE_IOERR_ACCESS, static_cast<int>(SqliteLoggedResultCode::kIoAccess)}, |
| {SQLITE_IOERR_CHECKRESERVEDLOCK, |
| static_cast<int>(SqliteLoggedResultCode::kIoCheckReservedLock)}, |
| {SQLITE_IOERR_LOCK, static_cast<int>(SqliteLoggedResultCode::kIoLock)}, |
| {SQLITE_IOERR_CLOSE, static_cast<int>(SqliteLoggedResultCode::kIoClose)}, |
| {SQLITE_IOERR_DIR_CLOSE, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| |
| // Chrome will only allow enabling WAL on databases with exclusive locking. |
| {SQLITE_IOERR_SHMOPEN, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| // Chrome will only allow enabling WAL on databases with exclusive locking. |
| {SQLITE_IOERR_SHMSIZE, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_IOERR_SHMLOCK, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)}, |
| |
| // Chrome will only allow enabling WAL on databases with exclusive locking. |
| {SQLITE_IOERR_SHMMAP, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_IOERR_SEEK, static_cast<int>(SqliteLoggedResultCode::kIoSeek)}, |
| {SQLITE_IOERR_DELETE_NOENT, |
| static_cast<int>(SqliteLoggedResultCode::kIoDeleteNoEntry)}, |
| {SQLITE_IOERR_MMAP, |
| static_cast<int>(SqliteLoggedResultCode::kIoMemoryMapping)}, |
| {SQLITE_IOERR_GETTEMPPATH, |
| static_cast<int>(SqliteLoggedResultCode::kIoGetTemporaryPath)}, |
| |
| // Chrome does not support Cygwin and does not use its VFS. |
| {SQLITE_IOERR_CONVPATH, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| // Chrome does not use SQLite extensions. |
| {SQLITE_IOERR_VNODE, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| // Chrome does not use SQLite extensions. |
| {SQLITE_IOERR_AUTH, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_IOERR_BEGIN_ATOMIC, |
| static_cast<int>(SqliteLoggedResultCode::kIoBeginAtomic)}, |
| {SQLITE_IOERR_COMMIT_ATOMIC, |
| static_cast<int>(SqliteLoggedResultCode::kIoCommitAtomic)}, |
| {SQLITE_IOERR_ROLLBACK_ATOMIC, |
| static_cast<int>(SqliteLoggedResultCode::kIoRollbackAtomic)}, |
| |
| // Chrome does not use the checksum VFS shim. |
| {SQLITE_IOERR_DATA, |
| static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}, |
| |
| {SQLITE_IOERR_CORRUPTFS, |
| static_cast<int>(SqliteLoggedResultCode::kIoCorruptFileSystem)}, |
| }; |
| |
| // Describes the handling of unknown SQLite error codes. |
| constexpr SqliteResultCodeMappingEntry kUnknownResultCodeMappingEntry = { |
| 0, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)}; |
| |
| // Looks up a `sqlite_result_code` in the mapping tables. |
| // |
| // Returns an entry in kResultCodeMapping or kUnknownResultCodeMappingEntry. |
| // DCHECKs if the `sqlite_result_code` is not in the mapping table. |
| SqliteResultCodeMappingEntry FindResultCode(int sqlite_result_code) { |
| const auto* mapping_it = base::ranges::find_if( |
| kResultCodeMapping, |
| [&sqlite_result_code](SqliteResultCodeMappingEntry rhs) { |
| return sqlite_result_code == rhs.result_code; |
| }); |
| |
| if (mapping_it == base::ranges::end(kResultCodeMapping)) { |
| NOTREACHED() << "Unsupported SQLite result code: " << sqlite_result_code; |
| return kUnknownResultCodeMappingEntry; |
| } |
| return *mapping_it; |
| } |
| |
| } // namespace |
| |
| #if DCHECK_IS_ON() |
| |
| SqliteResultCode ToSqliteResultCode(int sqlite_result_code) { |
| SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>( |
| FindResultCode(sqlite_result_code).logged_code); |
| |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite) |
| << "SQLite reported code marked for internal use: " << sqlite_result_code; |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome) |
| << "SQLite reported code that should never show up in Chrome: " |
| << sqlite_result_code; |
| |
| return static_cast<SqliteResultCode>(sqlite_result_code); |
| } |
| |
| SqliteErrorCode ToSqliteErrorCode(SqliteResultCode sqlite_error_code) { |
| SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>( |
| FindResultCode(static_cast<int>(sqlite_error_code)).logged_code); |
| |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite) |
| << "SQLite reported code marked for internal use: " << sqlite_error_code; |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome) |
| << "SQLite reported code that should never show up in Chrome: " |
| << sqlite_error_code; |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kNoError) |
| << __func__ |
| << " called with non-error result code: " << sqlite_error_code; |
| |
| return static_cast<SqliteErrorCode>(sqlite_error_code); |
| } |
| |
| #endif // DCHECK_IS_ON() |
| |
| bool IsSqliteSuccessCode(SqliteResultCode sqlite_result_code) { |
| // https://www.sqlite.org/rescode.html lists the result codes that are not |
| // errors. |
| bool is_success = (sqlite_result_code == SqliteResultCode::kOk) || |
| (sqlite_result_code == SqliteResultCode::kRow) || |
| (sqlite_result_code == SqliteResultCode::kDone); |
| |
| #if DCHECK_IS_ON() |
| SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>( |
| FindResultCode(static_cast<int>(sqlite_result_code)).logged_code); |
| |
| DCHECK_EQ(is_success, logged_code == SqliteLoggedResultCode::kNoError) |
| << __func__ << " logic disagrees with the code mapping for " |
| << sqlite_result_code; |
| |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite) |
| << "SQLite reported code marked for internal use: " << sqlite_result_code; |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome) |
| << "SQLite reported code that should never show up in Chrome: " |
| << sqlite_result_code; |
| #endif // DCHECK_IS_ON() |
| |
| return is_success; |
| } |
| |
| SqliteLoggedResultCode ToSqliteLoggedResultCode(int sqlite_result_code) { |
| SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>( |
| FindResultCode(sqlite_result_code).logged_code); |
| |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite) |
| << "SQLite reported code marked for internal use: " << sqlite_result_code; |
| DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome) |
| << "SQLite reported code that should never show up in Chrome: " |
| << sqlite_result_code; |
| return logged_code; |
| } |
| |
| void UmaHistogramSqliteResult(const std::string& histogram_name, |
| int sqlite_result_code) { |
| auto logged_code = ToSqliteLoggedResultCode(sqlite_result_code); |
| base::UmaHistogramEnumeration(histogram_name, logged_code); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, |
| SqliteResultCode sqlite_result_code) { |
| return os << static_cast<int>(sqlite_result_code); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, SqliteErrorCode sqlite_error_code) { |
| return os << static_cast<SqliteResultCode>(sqlite_error_code); |
| } |
| |
| void CheckSqliteLoggedResultCodeForTesting() { |
| // Ensure that error codes are alphabetical. |
| const auto* unordered_it = base::ranges::adjacent_find( |
| kResultCodeMapping, |
| [](SqliteResultCodeMappingEntry lhs, SqliteResultCodeMappingEntry rhs) { |
| return lhs.result_code >= rhs.result_code; |
| }); |
| DCHECK_EQ(unordered_it, base::ranges::end(kResultCodeMapping)) |
| << "Mapping ordering broken at {" << unordered_it->result_code << ", " |
| << static_cast<int>(unordered_it->logged_code) << "}"; |
| |
| std::set<int> sqlite_result_codes; |
| for (auto& mapping_entry : kResultCodeMapping) |
| sqlite_result_codes.insert(mapping_entry.result_code); |
| |
| // SQLite doesn't have special messages for extended errors. |
| // At the time of this writing, sqlite3_errstr() has a string table for |
| // primary result codes, and uses it for extended error codes as well. |
| // |
| // So, we can only use sqlite3_errstr() to check for holes in the primary |
| // message table. |
| for (int result_code = 0; result_code <= 256; ++result_code) { |
| if (sqlite_result_codes.count(result_code) != 0) |
| continue; |
| |
| const char* error_message = sqlite3_errstr(result_code); |
| |
| static constexpr base::StringPiece kUnknownErrorMessage("unknown error"); |
| DCHECK_EQ(kUnknownErrorMessage.compare(error_message), 0) |
| << "Unmapped SQLite result code: " << result_code |
| << " SQLite message: " << error_message; |
| } |
| |
| // Number of #defines in https://www.sqlite.org/c3ref/c_abort.html |
| // |
| // This number is also stated at |
| // https://www.sqlite.org/rescode.html#primary_result_code_list |
| static constexpr int kPrimaryResultCodes = 31; |
| |
| // Number of #defines in https://www.sqlite.org/c3ref/c_abort_rollback.html |
| // |
| // This number is also stated at |
| // https://www.sqlite.org/rescode.html#extended_result_code_list |
| static constexpr int kExtendedResultCodes = 74; |
| |
| DCHECK_EQ(std::size(kResultCodeMapping), |
| size_t{kPrimaryResultCodes + kExtendedResultCodes}) |
| << "Mapping table has incorrect number of entries"; |
| } |
| |
| } // namespace sql |