def _rebuild_completion(self): data = {'url': [], 'title': [], 'last_atime': []} # select the latest entry for each url q = sql.Query('SELECT url, title, max(atime) AS atime FROM History ' 'WHERE NOT redirect and url NOT LIKE "glimpse://back%" ' 'GROUP BY url ORDER BY atime asc') entries = list(q.run()) if len(entries) > self._PROGRESS_THRESHOLD: self._progress.start("Rebuilding completion...", len(entries)) for entry in entries: self._progress.tick() url = QUrl(entry.url) if self._is_excluded(url): continue data['url'].append(self._format_completion_url(url)) data['title'].append(entry.title) data['last_atime'].append(entry.atime) self._progress.finish() self.completion.insert_batch(data, replace=True) sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
def test_prepare_error(self): with pytest.raises(sql.BugError) as excinfo: sql.Query('invalid') expected = ('Failed to prepare query "invalid": "near "invalid": ' 'syntax error Unable to execute statement"') assert str(excinfo.value) == expected
def __init__(self, progress, parent=None): super().__init__("History", ['url', 'title', 'atime', 'redirect'], constraints={ 'url': 'NOT NULL', 'title': 'NOT NULL', 'atime': 'NOT NULL', 'redirect': 'NOT NULL' }, parent=parent) self._progress = progress # Store the last saved url to avoid duplicate immedate saves. self._last_url = None self.completion = CompletionHistory(parent=self) self.metainfo = CompletionMetaInfo(parent=self) if sql.Query('pragma user_version').run().value() < _USER_VERSION: self.completion.delete_all() if self.metainfo['force_rebuild']: self.completion.delete_all() self.metainfo['force_rebuild'] = False if not self.completion: # either the table is out-of-date or the user wiped it manually self._rebuild_completion() self.create_index('HistoryIndex', 'url') self.create_index('HistoryAtimeIndex', 'atime') self._contains_query = self.contains_query('url') self._between_query = sql.Query('SELECT * FROM History ' 'where not redirect ' 'and not url like "glimpse://%" ' 'and atime > :earliest ' 'and atime <= :latest ' 'ORDER BY atime desc') self._before_query = sql.Query('SELECT * FROM History ' 'where not redirect ' 'and not url like "glimpse://%" ' 'and atime <= :latest ' 'ORDER BY atime desc ' 'limit :limit offset :offset') config.instance.changed.connect(self._on_config_changed)
def _atime_expr(self): """If max_items is set, return an expression to limit the query.""" max_items = config.val.completion.web_history.max_items # HistoryCategory should not be added to the completion in that case. assert max_items != 0 if max_items < 0: return '' min_atime = sql.Query(' '.join([ 'SELECT min(last_atime) FROM', '(SELECT last_atime FROM CompletionHistory', 'ORDER BY last_atime DESC LIMIT :limit)', ])).run(limit=max_items).value() if not min_atime: # if there are no history items, min_atime may be '' (issue #2849) return '' return "AND last_atime >= {}".format(min_atime)
def test_bound_values(self): q = sql.Query('SELECT :answer') q.run(answer=42) assert q.bound_values() == {':answer': 42}
def test_num_rows_affected(self): q = sql.Query('SELECT 0') q.run() assert q.rows_affected() == 0
def test_value_missing(self): q = sql.Query('SELECT 0 WHERE 0') q.run() with pytest.raises(sql.BugError, match='No result for single-result query'): q.value()
def test_run_batch_missing_binding(self): q = sql.Query('SELECT :answer') with pytest.raises(sql.BugError, match='Missing bound values!'): q.run_batch(values={})
def test_run_batch(self): q = sql.Query('SELECT :answer') q.run_batch(values={'answer': [42]}) assert q.value() == 42
def test_run_binding(self): q = sql.Query('SELECT :answer') q.run(answer=42) assert q.value() == 42
def test_iter(self): q = sql.Query('SELECT 0 AS col') q.run() result = next(iter(q)) assert result.col == 0
def test_iter_empty(self): q = sql.Query('SELECT 0 AS col WHERE 0') q.run() with pytest.raises(StopIteration): next(iter(q))
def test_iter_inactive(self): q = sql.Query('SELECT 0') with pytest.raises(sql.BugError, match='Cannot iterate inactive query'): next(iter(q))
def test_forward_only(self, forward_only): q = sql.Query('SELECT 0 WHERE 0', forward_only=forward_only) assert q.query.isForwardOnly() == forward_only
def __getitem__(self, key): self._check_key(key) query = sql.Query('SELECT value FROM CompletionMetaInfo ' 'WHERE key = :key') return query.run(key=key).value()
def set_pattern(self, pattern): """Set the pattern used to filter results. Args: pattern: string pattern to filter by. """ raw_pattern = pattern if (self._empty_prefix is not None and raw_pattern.startswith(self._empty_prefix)): log.sql.debug('Skipping query on {} due to ' 'prefix {} returning nothing.'.format( raw_pattern, self._empty_prefix)) return self._empty_prefix = None # escape to treat a user input % or _ as a literal, not a wildcard pattern = pattern.replace('%', '\\%') pattern = pattern.replace('_', '\\_') words = ['%{}%'.format(w) for w in pattern.split(' ')] # build a where clause to match all of the words in any order # given the search term "a b", the WHERE clause would be: # (url LIKE '%a%' OR title LIKE '%a%') AND # (url LIKE '%b%' OR title LIKE '%b%') where_clause = ' AND '.join( "(url LIKE :{val} escape '\\' OR title LIKE :{val} escape '\\')". format(val=i) for i in range(len(words))) # replace ' in timestamp-format to avoid breaking the query timestamp_format = config.val.completion.timestamp_format or '' timefmt = ( "strftime('{}', last_atime, 'unixepoch', 'localtime')".format( timestamp_format.replace("'", "`"))) try: if (not self._query or len(words) != len(self._query.bound_values())): # if the number of words changed, we need to generate a new # query otherwise, we can reuse the prepared query for # performance self._query = sql.Query( ' '.join([ "SELECT url, title, {}".format(timefmt), "FROM CompletionHistory", # the incoming pattern will have literal % and _ escaped we # need to tell SQL to treat '\' as an escape character 'WHERE ({})'.format(where_clause), self._atime_expr(), "ORDER BY last_atime DESC", ]), forward_only=False) with debug.log_time('sql', 'Running completion query'): self._query.run(**{str(i): w for i, w in enumerate(words)}) except sql.KnownError as e: # Sometimes, the query we built up was invalid, for example, # due to a large amount of words. # Also catches failures in the DB we can't solve. message.error("Error with SQL query: {}".format(e.text())) return self.setQuery(self._query.query) if not self.rowCount() and not self.canFetchMore(): self._empty_prefix = raw_pattern
def test_iter_multiple(self): q = sql.Query('VALUES (1), (2), (3);') res = list(q.run()) assert len(res) == 3 assert res[0].column1 == 1