Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion mycli/client_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]: ...
Expand All @@ -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.",
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions mycli/packages/ptoolkit/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
27 changes: 26 additions & 1 deletion test/pytests/test_client_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -192,6 +193,30 @@ def test_register_special_commands_registers_expected_commands(monkeypatch: pyte
assert calls[7][2:4] == ('/config <help|get|search|edit> [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()

Expand Down
15 changes: 15 additions & 0 deletions test/pytests/test_ptoolkit_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 == {}
Expand Down Expand Up @@ -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)
Expand Down
Loading