diff --git a/mycli/client_commands.py b/mycli/client_commands.py index 3a89068c0..cbac0f96d 100644 --- a/mycli/client_commands.py +++ b/mycli/client_commands.py @@ -17,6 +17,7 @@ from mycli.packages.batch_utils import statements_from_filehandle from mycli.packages.filepaths import dir_path_exists from mycli.packages.interactive_utils import confirm_destructive_query +from mycli.packages.ptoolkit.history import FileHistoryWithTimestamp from mycli.packages.special import main as special_main from mycli.packages.special.iocommands import expand_favorite_query from mycli.packages.special.main import ArgType, SpecialCommandAlias @@ -222,6 +223,7 @@ class ClientCommandsMixin: destructive_keywords: Any config: Any myclirc_path: str + prompt_session: Any prompt_format: str def refresh_completions(self, reset: bool = False) -> list[SQLResult]: ... @@ -245,7 +247,7 @@ def register_special_commands(self) -> None: aliases=[SpecialCommandAlias("\\r", case_sensitive=True)], ) special.register_special_command( - self.refresh_completions, + self.rehash, "rehash", "/rehash", "Refresh auto-completions.", @@ -302,6 +304,13 @@ def manual_reconnect(self, arg: str = "", **_) -> Generator[SQLResult, None, Non else: yield self.change_db(arg).send(None) + def rehash(self) -> list[SQLResult]: + prompt_session = getattr(self, 'prompt_session', None) + history = getattr(prompt_session, 'history', None) + if isinstance(history, FileHistoryWithTimestamp): + history.refresh_frecency() + return self.refresh_completions() + def change_table_format(self, arg: str, **_) -> Generator[SQLResult, None, None]: try: self.main_formatter.format_name = arg diff --git a/mycli/packages/ptoolkit/history.py b/mycli/packages/ptoolkit/history.py index 78b2ba083..037622f76 100644 --- a/mycli/packages/ptoolkit/history.py +++ b/mycli/packages/ptoolkit/history.py @@ -118,6 +118,14 @@ def _request_frecency_refresh(self) -> None: self._frecency_thread = None logger.exception('Failed to start history frecency calculation.') + def refresh_frecency(self) -> None: + """Request an immediate background refresh of history frecency.""" + if not self.frecency_history_entries: + return + with self._frecency_lock: + self._frecency_entries_since_refresh = 0 + self._request_frecency_refresh() + def _refresh_frecency(self) -> None: while True: with self._frecency_lock: diff --git a/test/pytests/test_client_commands.py b/test/pytests/test_client_commands.py index 7dcab4d4b..a9942b733 100644 --- a/test/pytests/test_client_commands.py +++ b/test/pytests/test_client_commands.py @@ -4,6 +4,7 @@ from io import StringIO import logging from pathlib import Path +from types import SimpleNamespace from typing import Any from configobj import ConfigObj @@ -181,7 +182,7 @@ def test_register_special_commands_registers_expected_commands(monkeypatch: pyte ] assert calls[0][0] == client.change_db assert calls[1][0] == client.manual_reconnect - assert calls[2][0] == client.refresh_completions + assert calls[2][0] == client.rehash assert calls[3][0] == client.change_table_format assert calls[4][0] == client.change_redirect_format assert calls[5][0] == client.execute_from_file @@ -192,6 +193,30 @@ def test_register_special_commands_registers_expected_commands(monkeypatch: pyte assert calls[7][2:4] == ('/config [key]', 'Inspect settings from config files.') +def test_rehash_refreshes_frecency_and_completions(monkeypatch: pytest.MonkeyPatch) -> None: + class FakeHistory: + def __init__(self) -> None: + self.refresh_calls = 0 + + def refresh_frecency(self) -> None: + self.refresh_calls += 1 + + monkeypatch.setattr(client_commands, 'FileHistoryWithTimestamp', FakeHistory) + client = DummyClient() + history = FakeHistory() + client.prompt_session = SimpleNamespace(history=history) + + assert result_statuses(client.rehash()) == ['refresh False'] + assert history.refresh_calls == 1 + + +def test_rehash_without_file_history_still_refreshes_completions() -> None: + client = DummyClient() + client.prompt_session = SimpleNamespace(history=object()) + + assert result_statuses(client.rehash()) == ['refresh False'] + + def test_manual_reconnect_reports_not_connected() -> None: client = DummyClient() diff --git a/test/pytests/test_ptoolkit_history.py b/test/pytests/test_ptoolkit_history.py index a50fa99e3..b3f253fb8 100644 --- a/test/pytests/test_ptoolkit_history.py +++ b/test/pytests/test_ptoolkit_history.py @@ -112,6 +112,7 @@ def test_nonpositive_history_entry_count_disables_frecency( history = FileHistoryWithTimestamp(history_path, frecency_history_entries=history_entries, frecency_refresh_interval=1) history.append_string('SELECT new_token') + history.refresh_frecency() assert history.frecency_history_entries == 0 assert history.frecency == {} @@ -189,6 +190,20 @@ def test_history_frecency_uses_configured_refresh_interval(tmp_path: Path) -> No assert history.frecency['first_token'] == 0.5 +def test_manual_frecency_refresh_recomputes_before_interval(tmp_path: Path) -> None: + history = FileHistoryWithTimestamp(tmp_path / 'history.txt') + wait_for_frecency_refresh(history) + + history.append_string('SELECT manual_token') + assert history.frecency == {} + + history.refresh_frecency() + wait_for_frecency_refresh(history) + + assert history.frecency['manual_token'] == 1.0 + assert history._frecency_entries_since_refresh == 0 + + @pytest.mark.parametrize('refresh_interval', [0, -1]) def test_nonpositive_refresh_interval_disables_periodic_refresh(tmp_path: Path, refresh_interval: int) -> None: history = FileHistoryWithTimestamp(tmp_path / 'history.txt', frecency_refresh_interval=refresh_interval)