From 254b21f3ecc43d4d844e6ded55378673b913b5c8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 18 Mar 2021 10:28:53 +0100 Subject: Try to recover from CompletionMetaInfo with unexpected structure Fixes #6302 --- qutebrowser/browser/history.py | 24 +++++++++++++++++++++--- qutebrowser/misc/sql.py | 8 ++++---- tests/unit/browser/test_history.py | 22 ++++++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index ef4650a35..9eaa88760 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -92,8 +92,11 @@ class CompletionMetaInfo(sql.SqlTable): } def __init__(self, parent=None): - super().__init__("CompletionMetaInfo", ['key', 'value'], - constraints={'key': 'PRIMARY KEY'}) + self._fields = ['key', 'value'] + self._constraints = {'key': 'PRIMARY KEY'} + super().__init__( + "CompletionMetaInfo", self._fields, constraints=self._constraints) + if sql.user_version_changed(): self._init_default_values() @@ -101,6 +104,15 @@ class CompletionMetaInfo(sql.SqlTable): if key not in self.KEYS: raise KeyError(key) + def try_recover(self): + """Try recovering the table structure. + + This should be called if getting a value via __getattr__ failed. In theory, this + should never happen, in practice, it does. + """ + self._create_table(self._fields, constraints=self._constraints, force=True) + self._init_default_values() + def _init_default_values(self): for key, default in self.KEYS.items(): if key not in self: @@ -164,7 +176,13 @@ class WebHistory(sql.SqlTable): self.completion = CompletionHistory(parent=self) self.metainfo = CompletionMetaInfo(parent=self) - rebuild_completion = self.metainfo['force_rebuild'] + try: + rebuild_completion = self.metainfo['force_rebuild'] + except sql.BugError: + log.sql.warning("Failed to access meta info, trying to recover...", + exc_info=True) + self.metainfo.try_recover() + rebuild_completion = self.metainfo['force_rebuild'] if sql.user_version_changed(): # If the DB user version changed, run a full cleanup and rebuild the diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py index 7a3626f6e..68c0fd538 100644 --- a/qutebrowser/misc/sql.py +++ b/qutebrowser/misc/sql.py @@ -351,13 +351,13 @@ class SqlTable(QObject): self._name = name self._create_table(fields, constraints) - def _create_table(self, fields, constraints): + def _create_table(self, fields, constraints, *, force=False): """Create the table if the database is uninitialized. - If the table already exists, this does nothing, so it can e.g. be called on - every user_version change. + If the table already exists, this does nothing (except with force=True), so it + can e.g. be called on every user_version change. """ - if not user_version_changed(): + if not user_version_changed() and not force: return constraints = constraints or {} diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 9b08de30d..1ca708ec8 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -508,6 +508,28 @@ class TestCompletionMetaInfo: metainfo['excluded_patterns'] = value assert metainfo['excluded_patterns'] == value + # FIXME: It'd be good to test those two things via WebHistory (and not just + # CompletionMetaInfo in isolation), but we can't do that right now - see the + # docstring of TestRebuild for details. + + def test_recovery_no_key(self, metainfo): + metainfo.delete('key', 'force_rebuild') + + with pytest.raises(sql.BugError, match='No result for single-result query'): + metainfo['force_rebuild'] + + metainfo.try_recover() + assert not metainfo['force_rebuild'] + + def test_recovery_no_table(self, metainfo): + sql.Query("DROP TABLE CompletionMetaInfo").run() + + with pytest.raises(sql.BugError, match='no such table: CompletionMetaInfo'): + metainfo['force_rebuild'] + + metainfo.try_recover() + assert not metainfo['force_rebuild'] + class TestHistoryProgress: -- cgit v1.2.3-54-g00ecf