1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "sql/sqlite_result_code.h"
7 #include <ostream> // Needed to compile NOTREACHED() with operator <<.
11 #include "base/check_op.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/notreached.h"
14 #include "base/ranges/algorithm.h"
15 #include "base/strings/string_piece.h"
16 #include "sql/sqlite_result_code_values.h"
17 #include "third_party/sqlite/sqlite3.h"
23 // The highly packed representation minimizes binary size and memory usage.
24 struct SqliteResultCodeMappingEntry {
25 unsigned result_code : 16;
26 unsigned logged_code : 8;
28 // The remaining bits will be used to encode the result values of helpers that
29 // indicate corruption handling.
32 constexpr SqliteResultCodeMappingEntry kResultCodeMapping[] = {
33 // Entries are ordered by SQLite result code value. This should match the
34 // ordering in https://www.sqlite.org/rescode.html.
36 {SQLITE_OK, static_cast<int>(SqliteLoggedResultCode::kNoError)},
37 {SQLITE_ERROR, static_cast<int>(SqliteLoggedResultCode::kGeneric)},
38 {SQLITE_INTERNAL, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
39 {SQLITE_PERM, static_cast<int>(SqliteLoggedResultCode::kPermission)},
40 {SQLITE_ABORT, static_cast<int>(SqliteLoggedResultCode::kAbort)},
41 {SQLITE_BUSY, static_cast<int>(SqliteLoggedResultCode::kBusy)},
43 // Chrome features shouldn't execute conflicting statements concurrently.
44 {SQLITE_LOCKED, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
46 // Chrome should crash on OOM.
47 {SQLITE_NOMEM, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
49 {SQLITE_READONLY, static_cast<int>(SqliteLoggedResultCode::kReadOnly)},
51 // Chrome doesn't use sqlite3_interrupt().
52 {SQLITE_INTERRUPT, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
54 {SQLITE_IOERR, static_cast<int>(SqliteLoggedResultCode::kIo)},
55 {SQLITE_CORRUPT, static_cast<int>(SqliteLoggedResultCode::kCorrupt)},
57 // Chrome only passes a few known-good opcodes to sqlite3_file_control().
58 {SQLITE_NOTFOUND, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
60 {SQLITE_FULL, static_cast<int>(SqliteLoggedResultCode::kFullDisk)},
61 {SQLITE_CANTOPEN, static_cast<int>(SqliteLoggedResultCode::kCantOpen)},
63 static_cast<int>(SqliteLoggedResultCode::kLockingProtocol)},
64 {SQLITE_EMPTY, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
65 {SQLITE_SCHEMA, static_cast<int>(SqliteLoggedResultCode::kSchemaChanged)},
66 {SQLITE_TOOBIG, static_cast<int>(SqliteLoggedResultCode::kTooBig)},
67 {SQLITE_CONSTRAINT, static_cast<int>(SqliteLoggedResultCode::kConstraint)},
68 {SQLITE_MISMATCH, static_cast<int>(SqliteLoggedResultCode::kTypeMismatch)},
70 // Chrome should not misuse SQLite's API.
71 {SQLITE_MISUSE, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
74 static_cast<int>(SqliteLoggedResultCode::kNoLargeFileSupport)},
76 // Chrome does not set an authorizer callback.
77 {SQLITE_AUTH, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
79 {SQLITE_FORMAT, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
81 // Chrome should not use invalid column indexes in sqlite3_{bind,column}*().
82 {SQLITE_RANGE, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
84 {SQLITE_NOTADB, static_cast<int>(SqliteLoggedResultCode::kNotADatabase)},
85 {SQLITE_NOTICE, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
86 {SQLITE_WARNING, static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
87 {SQLITE_ROW, static_cast<int>(SqliteLoggedResultCode::kNoError)},
88 {SQLITE_DONE, static_cast<int>(SqliteLoggedResultCode::kNoError)},
89 {SQLITE_OK_LOAD_PERMANENTLY,
90 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
92 // Chrome should not use collating sequence names in SQL statements.
93 {SQLITE_ERROR_MISSING_COLLSEQ,
94 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
96 {SQLITE_BUSY_RECOVERY,
97 static_cast<int>(SqliteLoggedResultCode::kBusyRecovery)},
99 // Chrome does not use a shared page cache.
100 {SQLITE_LOCKED_SHAREDCACHE,
101 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
103 {SQLITE_READONLY_RECOVERY,
104 static_cast<int>(SqliteLoggedResultCode::kReadOnlyRecovery)},
105 {SQLITE_IOERR_READ, static_cast<int>(SqliteLoggedResultCode::kIoRead)},
107 // Chrome does not use a virtual table that signals corruption. We only use
109 // virtual table code for recovery. That code does not use this error.
110 {SQLITE_CORRUPT_VTAB,
111 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
113 {SQLITE_CANTOPEN_NOTEMPDIR,
114 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
115 {SQLITE_CONSTRAINT_CHECK,
116 static_cast<int>(SqliteLoggedResultCode::kConstraintCheck)},
118 // Chrome does not set an authorizer callback.
119 {SQLITE_AUTH_USER, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
121 {SQLITE_NOTICE_RECOVER_WAL,
122 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
123 {SQLITE_WARNING_AUTOINDEX,
124 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
126 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
127 {SQLITE_ABORT_ROLLBACK,
128 static_cast<int>(SqliteLoggedResultCode::kAbortRollback)},
129 {SQLITE_BUSY_SNAPSHOT,
130 static_cast<int>(SqliteLoggedResultCode::kBusySnapshot)},
132 // Chrome does not use a virtual table that signals conflicts. We only use a
133 // virtual table code for recovery. That code does not use this error.
135 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
137 {SQLITE_READONLY_CANTLOCK,
138 static_cast<int>(SqliteLoggedResultCode::kReadOnlyCantLock)},
139 {SQLITE_IOERR_SHORT_READ,
140 static_cast<int>(SqliteLoggedResultCode::kIoShortRead)},
141 {SQLITE_CORRUPT_SEQUENCE,
142 static_cast<int>(SqliteLoggedResultCode::kCorruptSequence)},
143 {SQLITE_CANTOPEN_ISDIR,
144 static_cast<int>(SqliteLoggedResultCode::kCantOpenIsDir)},
146 // Chrome does not use commit hook callbacks.
147 {SQLITE_CONSTRAINT_COMMITHOOK,
148 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
150 {SQLITE_NOTICE_RECOVER_ROLLBACK,
151 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
153 // Chrome does not use sqlite3_snapshot_open().
154 {SQLITE_ERROR_SNAPSHOT,
155 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
156 #ifdef SQLITE_ENABLE_SNAPSHOT
157 #error "This code assumes that Chrome does not use sqlite3_snapshot_open()"
160 // Chrome does not use blocking Posix advisory file lock requests.
161 {SQLITE_BUSY_TIMEOUT,
162 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
163 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
164 #error "This code assumes that Chrome does not use
167 {SQLITE_READONLY_ROLLBACK,
168 static_cast<int>(SqliteLoggedResultCode::kReadOnlyRollback)},
169 {SQLITE_IOERR_WRITE, static_cast<int>(SqliteLoggedResultCode::kIoWrite)},
170 {SQLITE_CORRUPT_INDEX,
171 static_cast<int>(SqliteLoggedResultCode::kCorruptIndex)},
173 // Chrome should always pass full paths to SQLite.
174 {SQLITE_CANTOPEN_FULLPATH,
175 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
177 {SQLITE_CONSTRAINT_FOREIGNKEY,
178 static_cast<int>(SqliteLoggedResultCode::kConstraintForeignKey)},
179 {SQLITE_READONLY_DBMOVED,
180 static_cast<int>(SqliteLoggedResultCode::kReadOnlyDbMoved)},
181 {SQLITE_IOERR_FSYNC, static_cast<int>(SqliteLoggedResultCode::kIoFsync)},
183 // Chrome does not support Cygwin and does not use its VFS.
184 {SQLITE_CANTOPEN_CONVPATH,
185 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
187 // Chrome does not use extension functions.
188 {SQLITE_CONSTRAINT_FUNCTION,
189 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
191 {SQLITE_READONLY_CANTINIT,
192 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
193 {SQLITE_IOERR_DIR_FSYNC,
194 static_cast<int>(SqliteLoggedResultCode::kIoDirFsync)},
195 {SQLITE_CANTOPEN_DIRTYWAL,
196 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
197 {SQLITE_CONSTRAINT_NOTNULL,
198 static_cast<int>(SqliteLoggedResultCode::kConstraintNotNull)},
199 {SQLITE_READONLY_DIRECTORY,
200 static_cast<int>(SqliteLoggedResultCode::kReadOnlyDirectory)},
201 {SQLITE_IOERR_TRUNCATE,
202 static_cast<int>(SqliteLoggedResultCode::kIoTruncate)},
204 // Chrome does not use the SQLITE_OPEN_NOFOLLOW flag.
205 {SQLITE_CANTOPEN_SYMLINK,
206 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
208 {SQLITE_CONSTRAINT_PRIMARYKEY,
209 static_cast<int>(SqliteLoggedResultCode::kConstraintPrimaryKey)},
210 {SQLITE_IOERR_FSTAT, static_cast<int>(SqliteLoggedResultCode::kIoFstat)},
212 // Chrome unconditionally disables database triggers via
213 // sqlite3_db_config(SQLITE_DBCONFIG_ENABLE_TRIGGER).
214 {SQLITE_CONSTRAINT_TRIGGER,
215 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
217 {SQLITE_IOERR_UNLOCK, static_cast<int>(SqliteLoggedResultCode::kIoUnlock)},
218 {SQLITE_CONSTRAINT_UNIQUE,
219 static_cast<int>(SqliteLoggedResultCode::kConstraintUnique)},
220 {SQLITE_IOERR_RDLOCK,
221 static_cast<int>(SqliteLoggedResultCode::kIoReadLock)},
223 // Chrome does not use a virtual table that signals constraints. We only use
224 // a virtual table code for recovery. That code does not use this error.
225 {SQLITE_CONSTRAINT_VTAB,
226 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
228 {SQLITE_IOERR_DELETE, static_cast<int>(SqliteLoggedResultCode::kIoDelete)},
229 {SQLITE_CONSTRAINT_ROWID,
230 static_cast<int>(SqliteLoggedResultCode::kConstraintRowId)},
231 {SQLITE_IOERR_BLOCKED,
232 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
234 // Chrome unconditionally disables database triggers via
235 // sqlite3_db_config(SQLITE_DBCONFIG_ENABLE_TRIGGER).
236 {SQLITE_CONSTRAINT_PINNED,
237 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
239 // The SQLite docus claim that this error code is "normally" converted to
240 // SQLITE_NOMEM. This doesn't seem 100% categorical, so we're flagging this
241 // as "unused in Chrome" per the same rationale as SQLITE_NOMEM.
243 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
245 {SQLITE_CONSTRAINT_DATATYPE,
246 static_cast<int>(SqliteLoggedResultCode::kConstraintDataType)},
247 {SQLITE_IOERR_ACCESS, static_cast<int>(SqliteLoggedResultCode::kIoAccess)},
248 {SQLITE_IOERR_CHECKRESERVEDLOCK,
249 static_cast<int>(SqliteLoggedResultCode::kIoCheckReservedLock)},
250 {SQLITE_IOERR_LOCK, static_cast<int>(SqliteLoggedResultCode::kIoLock)},
251 {SQLITE_IOERR_CLOSE, static_cast<int>(SqliteLoggedResultCode::kIoClose)},
252 {SQLITE_IOERR_DIR_CLOSE,
253 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
255 // Chrome will only allow enabling WAL on databases with exclusive locking.
256 {SQLITE_IOERR_SHMOPEN,
257 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
259 // Chrome will only allow enabling WAL on databases with exclusive locking.
260 {SQLITE_IOERR_SHMSIZE,
261 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
263 {SQLITE_IOERR_SHMLOCK,
264 static_cast<int>(SqliteLoggedResultCode::kUnusedSqlite)},
266 // Chrome will only allow enabling WAL on databases with exclusive locking.
267 {SQLITE_IOERR_SHMMAP,
268 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
270 {SQLITE_IOERR_SEEK, static_cast<int>(SqliteLoggedResultCode::kIoSeek)},
271 {SQLITE_IOERR_DELETE_NOENT,
272 static_cast<int>(SqliteLoggedResultCode::kIoDeleteNoEntry)},
274 static_cast<int>(SqliteLoggedResultCode::kIoMemoryMapping)},
275 {SQLITE_IOERR_GETTEMPPATH,
276 static_cast<int>(SqliteLoggedResultCode::kIoGetTemporaryPath)},
278 // Chrome does not support Cygwin and does not use its VFS.
279 {SQLITE_IOERR_CONVPATH,
280 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
282 // Chrome does not use SQLite extensions.
284 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
286 // Chrome does not use SQLite extensions.
288 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
290 {SQLITE_IOERR_BEGIN_ATOMIC,
291 static_cast<int>(SqliteLoggedResultCode::kIoBeginAtomic)},
292 {SQLITE_IOERR_COMMIT_ATOMIC,
293 static_cast<int>(SqliteLoggedResultCode::kIoCommitAtomic)},
294 {SQLITE_IOERR_ROLLBACK_ATOMIC,
295 static_cast<int>(SqliteLoggedResultCode::kIoRollbackAtomic)},
297 // Chrome does not use the checksum VFS shim.
299 static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)},
301 {SQLITE_IOERR_CORRUPTFS,
302 static_cast<int>(SqliteLoggedResultCode::kIoCorruptFileSystem)},
305 // Describes the handling of unknown SQLite error codes.
306 constexpr SqliteResultCodeMappingEntry kUnknownResultCodeMappingEntry = {
307 0, static_cast<int>(SqliteLoggedResultCode::kUnusedChrome)};
309 // Looks up a `sqlite_result_code` in the mapping tables.
311 // Returns an entry in kResultCodeMapping or kUnknownResultCodeMappingEntry.
312 // DCHECKs if the `sqlite_result_code` is not in the mapping table.
313 SqliteResultCodeMappingEntry FindResultCode(int sqlite_result_code) {
314 const auto* mapping_it = base::ranges::find_if(
316 [&sqlite_result_code](SqliteResultCodeMappingEntry rhs) {
317 return sqlite_result_code == rhs.result_code;
320 if (mapping_it == base::ranges::end(kResultCodeMapping)) {
321 NOTREACHED() << "Unsupported SQLite result code: " << sqlite_result_code;
322 return kUnknownResultCodeMappingEntry;
331 SqliteResultCode ToSqliteResultCode(int sqlite_result_code) {
332 SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>(
333 FindResultCode(sqlite_result_code).logged_code);
335 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite)
336 << "SQLite reported code marked for internal use: " << sqlite_result_code;
337 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome)
338 << "SQLite reported code that should never show up in Chrome: "
339 << sqlite_result_code;
341 return static_cast<SqliteResultCode>(sqlite_result_code);
344 SqliteErrorCode ToSqliteErrorCode(SqliteResultCode sqlite_error_code) {
345 SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>(
346 FindResultCode(static_cast<int>(sqlite_error_code)).logged_code);
348 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite)
349 << "SQLite reported code marked for internal use: " << sqlite_error_code;
350 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome)
351 << "SQLite reported code that should never show up in Chrome: "
352 << sqlite_error_code;
353 DCHECK_NE(logged_code, SqliteLoggedResultCode::kNoError)
355 << " called with non-error result code: " << sqlite_error_code;
357 return static_cast<SqliteErrorCode>(sqlite_error_code);
360 #endif // DCHECK_IS_ON()
362 bool IsSqliteSuccessCode(SqliteResultCode sqlite_result_code) {
363 // https://www.sqlite.org/rescode.html lists the result codes that are not
365 bool is_success = (sqlite_result_code == SqliteResultCode::kOk) ||
366 (sqlite_result_code == SqliteResultCode::kRow) ||
367 (sqlite_result_code == SqliteResultCode::kDone);
370 SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>(
371 FindResultCode(static_cast<int>(sqlite_result_code)).logged_code);
373 DCHECK_EQ(is_success, logged_code == SqliteLoggedResultCode::kNoError)
374 << __func__ << " logic disagrees with the code mapping for "
375 << sqlite_result_code;
377 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite)
378 << "SQLite reported code marked for internal use: " << sqlite_result_code;
379 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome)
380 << "SQLite reported code that should never show up in Chrome: "
381 << sqlite_result_code;
382 #endif // DCHECK_IS_ON()
387 SqliteLoggedResultCode ToSqliteLoggedResultCode(int sqlite_result_code) {
388 SqliteLoggedResultCode logged_code = static_cast<SqliteLoggedResultCode>(
389 FindResultCode(sqlite_result_code).logged_code);
391 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedSqlite)
392 << "SQLite reported code marked for internal use: " << sqlite_result_code;
393 DCHECK_NE(logged_code, SqliteLoggedResultCode::kUnusedChrome)
394 << "SQLite reported code that should never show up in Chrome: "
395 << sqlite_result_code;
399 void UmaHistogramSqliteResult(const char* histogram_name,
400 int sqlite_result_code) {
401 auto logged_code = ToSqliteLoggedResultCode(sqlite_result_code);
402 base::UmaHistogramEnumeration(histogram_name, logged_code);
405 std::ostream& operator<<(std::ostream& os,
406 SqliteResultCode sqlite_result_code) {
407 return os << static_cast<int>(sqlite_result_code);
410 std::ostream& operator<<(std::ostream& os, SqliteErrorCode sqlite_error_code) {
411 return os << static_cast<SqliteResultCode>(sqlite_error_code);
414 void CheckSqliteLoggedResultCodeForTesting() {
415 // Ensure that error codes are alphabetical.
416 const auto* unordered_it = base::ranges::adjacent_find(
418 [](SqliteResultCodeMappingEntry lhs, SqliteResultCodeMappingEntry rhs) {
419 return lhs.result_code >= rhs.result_code;
421 DCHECK_EQ(unordered_it, base::ranges::end(kResultCodeMapping))
422 << "Mapping ordering broken at {" << unordered_it->result_code << ", "
423 << static_cast<int>(unordered_it->logged_code) << "}";
425 std::set<int> sqlite_result_codes;
426 for (auto& mapping_entry : kResultCodeMapping)
427 sqlite_result_codes.insert(mapping_entry.result_code);
429 // SQLite doesn't have special messages for extended errors.
430 // At the time of this writing, sqlite3_errstr() has a string table for
431 // primary result codes, and uses it for extended error codes as well.
433 // So, we can only use sqlite3_errstr() to check for holes in the primary
435 for (int result_code = 0; result_code <= 256; ++result_code) {
436 if (sqlite_result_codes.count(result_code) != 0)
439 const char* error_message = sqlite3_errstr(result_code);
441 static constexpr base::StringPiece kUnknownErrorMessage("unknown error");
442 DCHECK_EQ(kUnknownErrorMessage.compare(error_message), 0)
443 << "Unmapped SQLite result code: " << result_code
444 << " SQLite message: " << error_message;
447 // Number of #defines in https://www.sqlite.org/c3ref/c_abort.html
449 // This number is also stated at
450 // https://www.sqlite.org/rescode.html#primary_result_code_list
451 static constexpr int kPrimaryResultCodes = 31;
453 // Number of #defines in https://www.sqlite.org/c3ref/c_abort_rollback.html
455 // This number is also stated at
456 // https://www.sqlite.org/rescode.html#extended_result_code_list
457 static constexpr int kExtendedResultCodes = 74;
459 DCHECK_EQ(std::size(kResultCodeMapping),
460 size_t{kPrimaryResultCodes + kExtendedResultCodes})
461 << "Mapping table has incorrect number of entries";