sql: Handle sqlite extended error codes

Qt 6.1 switched to using extended sqlite error codes:
https://codereview.qt-project.org/c/qt/qtbase/+/329472

This is neat because it gives us more granularity for errors, but it
also means that we now need to account for that by masking the error
codes accordingly for checking their category.

See unhandled I/O error here:
https://crashes.qutebrowser.org/view/d9b26c2f
This commit is contained in:
Florian Bruhin 2022-08-26 17:15:04 +02:00
parent 815374c6b6
commit 6ffc5174ea
2 changed files with 27 additions and 15 deletions

View File

@ -77,16 +77,16 @@ class SqliteErrorCode:
in qutebrowser here.
"""
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
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
class Error(Exception):
@ -128,6 +128,12 @@ class BugError(Error):
def raise_sqlite_error(msg: str, error: QSqlError) -> None:
"""Raise either a BugError or KnownError."""
error_code = error.nativeErrorCode()
try:
# https://sqlite.org/rescode.html#pve
primary_error_code = int(error_code) & 0xff
except ValueError:
primary_error_code = None
database_text = error.databaseText()
driver_text = error.driverText()
@ -135,7 +141,7 @@ def raise_sqlite_error(msg: str, error: QSqlError) -> None:
log.sql.debug(f"type: {debug.qenum_key(QSqlError, error.type())}")
log.sql.debug(f"database text: {database_text}")
log.sql.debug(f"driver text: {driver_text}")
log.sql.debug(f"error code: {error_code}")
log.sql.debug(f"error code: {error_code} -> {primary_error_code}")
known_errors = [
SqliteErrorCode.BUSY,
@ -151,12 +157,12 @@ def raise_sqlite_error(msg: str, error: QSqlError) -> None:
# https://github.com/qutebrowser/qutebrowser/issues/4681
# If the query we built was too long
too_long_err = (
error_code == SqliteErrorCode.ERROR and
primary_error_code == SqliteErrorCode.ERROR and
(database_text.startswith("Expression tree is too large") or
database_text in ["too many SQL variables",
"LIKE or GLOB pattern too complex"]))
if error_code in known_errors or too_long_err:
if primary_error_code in known_errors or too_long_err:
raise KnownError(msg, error)
raise BugError(msg, error)

View File

@ -93,10 +93,16 @@ class TestSqlError:
@pytest.mark.parametrize('error_code, exception', [
(sql.SqliteErrorCode.BUSY, sql.KnownError),
(sql.SqliteErrorCode.CONSTRAINT, sql.BugError),
# extended error codes
(sql.SqliteErrorCode.IOERR | (1<<8), sql.KnownError), # SQLITE_IOERR_READ
(
sql.SqliteErrorCode.CONSTRAINT | (1<<8), # SQLITE_CONSTRAINT_CHECK
sql.BugError
),
])
def test_known(self, error_code, exception):
sql_err = QSqlError("driver text", "db text", QSqlError.ErrorType.UnknownError,
error_code)
str(error_code))
with pytest.raises(exception):
sql.raise_sqlite_error("Message", sql_err)
@ -109,7 +115,7 @@ class TestSqlError:
'type: UnknownError',
'database text: db text',
'driver text: driver text',
'error code: 23']
'error code: 23 -> 23']
assert caplog.messages == expected