Refactor SQL error handling
This renames SqlException to SqlError (to be more consistent with how Python names exceptions), and adds an utility function which logs a few more useful details about errors. See #3004
This commit is contained in:
parent
eacdbe132e
commit
5af8a95c82
@ -425,7 +425,7 @@ def _init_modules(args, crash_handler):
|
|||||||
log.init.debug("Initializing sql...")
|
log.init.debug("Initializing sql...")
|
||||||
try:
|
try:
|
||||||
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
|
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
|
||||||
except sql.SqlException as e:
|
except sql.SqlError as e:
|
||||||
error.handle_fatal_exc(e, args, 'Error initializing SQL',
|
error.handle_fatal_exc(e, args, 'Error initializing SQL',
|
||||||
pre_text='Error initializing SQL')
|
pre_text='Error initializing SQL')
|
||||||
sys.exit(usertypes.Exit.err_init)
|
sys.exit(usertypes.Exit.err_init)
|
||||||
|
@ -22,12 +22,12 @@
|
|||||||
import collections
|
import collections
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtSignal
|
||||||
from PyQt5.QtSql import QSqlDatabase, QSqlQuery
|
from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlError
|
||||||
|
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log, debug
|
||||||
|
|
||||||
|
|
||||||
class SqlException(Exception):
|
class SqlError(Exception):
|
||||||
|
|
||||||
"""Raised on an error interacting with the SQL database."""
|
"""Raised on an error interacting with the SQL database."""
|
||||||
|
|
||||||
@ -38,12 +38,14 @@ def init(db_path):
|
|||||||
"""Initialize the SQL database connection."""
|
"""Initialize the SQL database connection."""
|
||||||
database = QSqlDatabase.addDatabase('QSQLITE')
|
database = QSqlDatabase.addDatabase('QSQLITE')
|
||||||
if not database.isValid():
|
if not database.isValid():
|
||||||
raise SqlException('Failed to add database. '
|
raise SqlError('Failed to add database. '
|
||||||
'Are sqlite and Qt sqlite support installed?')
|
'Are sqlite and Qt sqlite support installed?')
|
||||||
database.setDatabaseName(db_path)
|
database.setDatabaseName(db_path)
|
||||||
if not database.open():
|
if not database.open():
|
||||||
raise SqlException("Failed to open sqlite database at {}: {}"
|
error = database.lastError()
|
||||||
.format(db_path, database.lastError().text()))
|
_log_error(error)
|
||||||
|
raise SqlError("Failed to open sqlite database at {}: {}"
|
||||||
|
.format(db_path, error.text()))
|
||||||
|
|
||||||
|
|
||||||
def close():
|
def close():
|
||||||
@ -60,10 +62,32 @@ def version():
|
|||||||
close()
|
close()
|
||||||
return ver
|
return ver
|
||||||
return Query("select sqlite_version()").run().value()
|
return Query("select sqlite_version()").run().value()
|
||||||
except SqlException as e:
|
except SqlError as e:
|
||||||
return 'UNAVAILABLE ({})'.format(e)
|
return 'UNAVAILABLE ({})'.format(e)
|
||||||
|
|
||||||
|
|
||||||
|
def _log_error(error):
|
||||||
|
"""Log informations about a SQL error to the debug log."""
|
||||||
|
log.sql.debug("SQL error:")
|
||||||
|
log.sql.debug("type: {}".format(debug.qenum_key(QSqlError, error.type())))
|
||||||
|
log.sql.debug("database text: {}".format(error.databaseText()))
|
||||||
|
log.sql.debug("driver text: {}".format(error.driverText()))
|
||||||
|
log.sql.debug("error code: {}".format(error.nativeErrorCode()))
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_query_error(what, query, error):
|
||||||
|
"""Handle a sqlite error.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
what: What we were doing when the error happened.
|
||||||
|
query: The query which was executed.
|
||||||
|
error: The QSqlError object.
|
||||||
|
"""
|
||||||
|
_log_error(error)
|
||||||
|
msg = 'Failed to {} query "{}": "{}"'.format(what, query, error.text())
|
||||||
|
raise SqlError(msg)
|
||||||
|
|
||||||
|
|
||||||
class Query(QSqlQuery):
|
class Query(QSqlQuery):
|
||||||
|
|
||||||
"""A prepared SQL Query."""
|
"""A prepared SQL Query."""
|
||||||
@ -79,13 +103,12 @@ class Query(QSqlQuery):
|
|||||||
super().__init__(QSqlDatabase.database())
|
super().__init__(QSqlDatabase.database())
|
||||||
log.sql.debug('Preparing SQL query: "{}"'.format(querystr))
|
log.sql.debug('Preparing SQL query: "{}"'.format(querystr))
|
||||||
if not self.prepare(querystr):
|
if not self.prepare(querystr):
|
||||||
raise SqlException('Failed to prepare query "{}": "{}"'.format(
|
_handle_query_error('prepare', querystr, self.lastError())
|
||||||
querystr, self.lastError().text()))
|
|
||||||
self.setForwardOnly(forward_only)
|
self.setForwardOnly(forward_only)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
if not self.isActive():
|
if not self.isActive():
|
||||||
raise SqlException("Cannot iterate inactive query")
|
raise SqlError("Cannot iterate inactive query")
|
||||||
rec = self.record()
|
rec = self.record()
|
||||||
fields = [rec.fieldName(i) for i in range(rec.count())]
|
fields = [rec.fieldName(i) for i in range(rec.count())]
|
||||||
rowtype = collections.namedtuple('ResultRow', fields)
|
rowtype = collections.namedtuple('ResultRow', fields)
|
||||||
@ -101,14 +124,13 @@ class Query(QSqlQuery):
|
|||||||
self.bindValue(':{}'.format(key), val)
|
self.bindValue(':{}'.format(key), val)
|
||||||
log.sql.debug('query bindings: {}'.format(self.boundValues()))
|
log.sql.debug('query bindings: {}'.format(self.boundValues()))
|
||||||
if not self.exec_():
|
if not self.exec_():
|
||||||
raise SqlException('Failed to exec query "{}": "{}"'.format(
|
_handle_query_error('exec', self.lastQuery(), self.lastError())
|
||||||
self.lastQuery(), self.lastError().text()))
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def value(self):
|
def value(self):
|
||||||
"""Return the result of a single-value query (e.g. an EXISTS)."""
|
"""Return the result of a single-value query (e.g. an EXISTS)."""
|
||||||
if not self.next():
|
if not self.next():
|
||||||
raise SqlException("No result for single-result query")
|
raise SqlError("No result for single-result query")
|
||||||
return self.record().value(0)
|
return self.record().value(0)
|
||||||
|
|
||||||
|
|
||||||
@ -128,7 +150,7 @@ class SqlTable(QObject):
|
|||||||
def __init__(self, name, fields, constraints=None, parent=None):
|
def __init__(self, name, fields, constraints=None, parent=None):
|
||||||
"""Create a new table in the sql database.
|
"""Create a new table in the sql database.
|
||||||
|
|
||||||
Raises SqlException if the table already exists.
|
Raises SqlError if the table already exists.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Name of the table.
|
name: Name of the table.
|
||||||
@ -228,8 +250,7 @@ class SqlTable(QObject):
|
|||||||
db = QSqlDatabase.database()
|
db = QSqlDatabase.database()
|
||||||
db.transaction()
|
db.transaction()
|
||||||
if not q.execBatch():
|
if not q.execBatch():
|
||||||
raise SqlException('Failed to exec query "{}": "{}"'.format(
|
_handle_query_error('exec', q.lastQuery(), q.lastError())
|
||||||
q.lastQuery(), q.lastError().text()))
|
|
||||||
db.commit()
|
db.commit()
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ def test_insert_replace(qtbot):
|
|||||||
table.insert({'name': 'one', 'val': 11, 'lucky': True}, replace=True)
|
table.insert({'name': 'one', 'val': 11, 'lucky': True}, replace=True)
|
||||||
assert list(table) == [('one', 11, True)]
|
assert list(table) == [('one', 11, True)]
|
||||||
|
|
||||||
with pytest.raises(sql.SqlException):
|
with pytest.raises(sql.SqlError):
|
||||||
table.insert({'name': 'one', 'val': 11, 'lucky': True}, replace=False)
|
table.insert({'name': 'one', 'val': 11, 'lucky': True}, replace=False)
|
||||||
|
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ def test_insert_batch_replace(qtbot):
|
|||||||
('one', 11, True),
|
('one', 11, True),
|
||||||
('nine', 19, True)]
|
('nine', 19, True)]
|
||||||
|
|
||||||
with pytest.raises(sql.SqlException):
|
with pytest.raises(sql.SqlError):
|
||||||
table.insert_batch({'name': ['one', 'nine'],
|
table.insert_batch({'name': ['one', 'nine'],
|
||||||
'val': [11, 19],
|
'val': [11, 19],
|
||||||
'lucky': [True, True]})
|
'lucky': [True, True]})
|
||||||
|
Loading…
Reference in New Issue
Block a user