sql: Add *all* primary sqlite result codes
For three reasons: - There are only 31 of them, and we don't really expect any more to turn up (last happened in 2013, and we have a test for it happening) - It makes for nicer debug output - It always felt strange to only have a small subset in the enum
This commit is contained in:
parent
6ffc5174ea
commit
ee4d6e0396
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
"""Provides access to sqlite databases."""
|
||||
|
||||
import enum
|
||||
import collections
|
||||
import contextlib
|
||||
import dataclasses
|
||||
|
|
@ -69,24 +70,45 @@ class UserVersion:
|
|||
return f'{self.major}.{self.minor}'
|
||||
|
||||
|
||||
class SqliteErrorCode:
|
||||
class SqliteErrorCode(enum.Enum):
|
||||
"""Primary error codes as used by sqlite.
|
||||
|
||||
"""Error codes as used by sqlite.
|
||||
|
||||
See https://sqlite.org/rescode.html - note we only define the codes we use
|
||||
in qutebrowser here.
|
||||
See https://sqlite.org/rescode.html
|
||||
"""
|
||||
|
||||
ERROR = 1 # generic error code
|
||||
BUSY = 5 # database is locked
|
||||
READONLY = 8 # attempt to write a readonly database
|
||||
IOERR = 10 # disk I/O error
|
||||
CORRUPT = 11 # database disk image is malformed
|
||||
FULL = 13 # database or disk is full
|
||||
CANTOPEN = 14 # unable to open database file
|
||||
PROTOCOL = 15 # locking protocol error
|
||||
CONSTRAINT = 19 # UNIQUE constraint failed
|
||||
NOTADB = 26 # file is not a database
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
OK = 0 # Successful result
|
||||
ERROR = 1 # Generic error
|
||||
INTERNAL = 2 # Internal logic error in SQLite
|
||||
PERM = 3 # Access permission denied
|
||||
ABORT = 4 # Callback routine requested an abort
|
||||
BUSY = 5 # The database file is locked
|
||||
LOCKED = 6 # A table in the database is locked
|
||||
NOMEM = 7 # A malloc() failed
|
||||
READONLY = 8 # Attempt to write a readonly database
|
||||
INTERRUPT = 9 # Operation terminated by sqlite3_interrupt()*/
|
||||
IOERR = 10 # Some kind of disk I/O error occurred
|
||||
CORRUPT = 11 # The database disk image is malformed
|
||||
NOTFOUND = 12 # Unknown opcode in sqlite3_file_control()
|
||||
FULL = 13 # Insertion failed because database is full
|
||||
CANTOPEN = 14 # Unable to open the database file
|
||||
PROTOCOL = 15 # Database lock protocol error
|
||||
EMPTY = 16 # Internal use only
|
||||
SCHEMA = 17 # The database schema changed
|
||||
TOOBIG = 18 # String or BLOB exceeds size limit
|
||||
CONSTRAINT = 19 # Abort due to constraint violation
|
||||
MISMATCH = 20 # Data type mismatch
|
||||
MISUSE = 21 # Library used incorrectly
|
||||
NOLFS = 22 # Uses OS features not supported on host
|
||||
AUTH = 23 # Authorization denied
|
||||
FORMAT = 24 # Not used
|
||||
RANGE = 25 # 2nd parameter to sqlite3_bind out of range
|
||||
NOTADB = 26 # File opened that is not a database file
|
||||
NOTICE = 27 # Notifications from sqlite3_log()
|
||||
WARNING = 28 # Warnings from sqlite3_log()
|
||||
ROW = 100 # sqlite3_step() has another row ready
|
||||
DONE = 101 # sqlite3_step() has finished executing
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
|
@ -104,8 +126,7 @@ class Error(Exception):
|
|||
"""
|
||||
if self.error is None:
|
||||
return str(self)
|
||||
else:
|
||||
return self.error.databaseText()
|
||||
return self.error.databaseText()
|
||||
|
||||
|
||||
class KnownError(Error):
|
||||
|
|
@ -130,9 +151,10 @@ def raise_sqlite_error(msg: str, error: QSqlError) -> None:
|
|||
error_code = error.nativeErrorCode()
|
||||
try:
|
||||
# https://sqlite.org/rescode.html#pve
|
||||
primary_error_code = int(error_code) & 0xff
|
||||
primary_error_code = SqliteErrorCode(int(error_code) & 0xff)
|
||||
except ValueError:
|
||||
primary_error_code = None
|
||||
# not an int, or unknown error code -> fall back to string
|
||||
primary_error_code = error_code
|
||||
|
||||
database_text = error.databaseText()
|
||||
driver_text = error.driverText()
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import vulture
|
|||
|
||||
import qutebrowser.app # pylint: disable=unused-import
|
||||
from qutebrowser.extensions import loader
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.misc import objects, sql
|
||||
from qutebrowser.utils import utils, version, qtutils
|
||||
# To run the decorators from there
|
||||
# pylint: disable=unused-import
|
||||
|
|
@ -150,6 +150,9 @@ def whitelist_generator(): # noqa: C901
|
|||
for name in list(qtutils.LibraryPath):
|
||||
yield f'qutebrowser.utils.qtutils.LibraryPath.{name}'
|
||||
|
||||
for name in list(sql.SqliteErrorCode):
|
||||
yield f'qutebrowser.misc.sql.SqliteErrorCode.{name}'
|
||||
|
||||
|
||||
def filter_func(item):
|
||||
"""Check if a missing function should be filtered or not.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
"""Test the SQL API."""
|
||||
|
||||
import sys
|
||||
import sqlite3
|
||||
import pytest
|
||||
|
||||
import hypothesis
|
||||
|
|
@ -91,12 +93,15 @@ def test_sqlerror(klass):
|
|||
class TestSqlError:
|
||||
|
||||
@pytest.mark.parametrize('error_code, exception', [
|
||||
(sql.SqliteErrorCode.BUSY, sql.KnownError),
|
||||
(sql.SqliteErrorCode.CONSTRAINT, sql.BugError),
|
||||
(sql.SqliteErrorCode.BUSY.value, sql.KnownError),
|
||||
(sql.SqliteErrorCode.CONSTRAINT.value, sql.BugError),
|
||||
# extended error codes
|
||||
(sql.SqliteErrorCode.IOERR | (1<<8), sql.KnownError), # SQLITE_IOERR_READ
|
||||
(
|
||||
sql.SqliteErrorCode.CONSTRAINT | (1<<8), # SQLITE_CONSTRAINT_CHECK
|
||||
sql.SqliteErrorCode.IOERR.value | (1 << 8), # SQLITE_IOERR_READ
|
||||
sql.KnownError
|
||||
),
|
||||
(
|
||||
sql.SqliteErrorCode.CONSTRAINT.value | (1 << 8), # SQLITE_CONSTRAINT_CHECK
|
||||
sql.BugError
|
||||
),
|
||||
])
|
||||
|
|
@ -115,7 +120,7 @@ class TestSqlError:
|
|||
'type: UnknownError',
|
||||
'database text: db text',
|
||||
'driver text: driver text',
|
||||
'error code: 23 -> 23']
|
||||
'error code: 23 -> SqliteErrorCode.AUTH']
|
||||
|
||||
assert caplog.messages == expected
|
||||
|
||||
|
|
@ -125,6 +130,62 @@ class TestSqlError:
|
|||
err = klass("Message", sql_err)
|
||||
assert err.text() == "db text"
|
||||
|
||||
@pytest.mark.parametrize("code", list(sql.SqliteErrorCode))
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 11),
|
||||
reason="sqlite error code constants added in Python 3.11",
|
||||
)
|
||||
def test_sqlite_error_codes(self, code):
|
||||
"""Cross check our error codes with the ones in Python 3.11+.
|
||||
|
||||
See https://github.com/python/cpython/commit/86d8b465231
|
||||
"""
|
||||
pyvalue = getattr(sqlite3, f"SQLITE_{code.name}")
|
||||
assert pyvalue == code.value
|
||||
|
||||
def test_sqlite_error_codes_reverse(self):
|
||||
"""Check if we have all error codes defined that Python has.
|
||||
|
||||
It would be nice if this was easier (and less guesswork).
|
||||
However, the error codes are simply added as ints to the sqlite3 module
|
||||
namespace (PyModule_AddIntConstant), and lots of other constants are there too.
|
||||
"""
|
||||
# Start with all SQLITE_* names in the sqlite3 modules
|
||||
consts = {n for n in dir(sqlite3) if n.startswith("SQLITE_")}
|
||||
# All error codes we know about (tested above)
|
||||
consts -= {f"SQLITE_{m.name}" for m in sql.SqliteErrorCode}
|
||||
# Extended error codes or other constants. From the sqlite docs:
|
||||
#
|
||||
# Primary result code symbolic names are of the form "SQLITE_XXXXXX"
|
||||
# where XXXXXX is a sequence of uppercase alphabetic characters.
|
||||
# Extended result code names are of the form "SQLITE_XXXXXX_YYYYYYY"
|
||||
# where the XXXXXX part is the corresponding primary result code and the
|
||||
# YYYYYYY is an extension that further classifies the result code.
|
||||
consts -= {c for c in consts if c.count("_") >= 2}
|
||||
# All remaining sqlite constants which are *not* error codes.
|
||||
consts -= {
|
||||
"SQLITE_ANALYZE",
|
||||
"SQLITE_ATTACH",
|
||||
"SQLITE_DELETE",
|
||||
"SQLITE_DENY",
|
||||
"SQLITE_DETACH",
|
||||
"SQLITE_FUNCTION",
|
||||
"SQLITE_IGNORE",
|
||||
"SQLITE_INSERT",
|
||||
"SQLITE_PRAGMA",
|
||||
"SQLITE_READ",
|
||||
"SQLITE_RECURSIVE",
|
||||
"SQLITE_REINDEX",
|
||||
"SQLITE_SAVEPOINT",
|
||||
"SQLITE_SELECT",
|
||||
"SQLITE_TRANSACTION",
|
||||
"SQLITE_UPDATE",
|
||||
}
|
||||
# If there is anything remaining here, either a new Python version added a new
|
||||
# sqlite constant which is *not* an error, or there was a new error code added.
|
||||
# Either add it to the set above, or to SqliteErrorCode.
|
||||
assert not consts
|
||||
|
||||
|
||||
def test_init_table(database):
|
||||
database.table('Foo', ['name', 'val', 'lucky'])
|
||||
|
|
|
|||
Loading…
Reference in New Issue