refactor(sessions): export guard always counts every row; export_all reuses _active_clause

assert_export_safe grew an include_inactive flag whose only production caller
(the console export guard) always passes True, leaving the live-only branch
and its default unused. Drop the parameter and count every row, which is what
the transfer export materializes; the console call and docstring follow. The
existing guard tests seed live rows only and are unchanged.

export_all re-derived the live-row clause by hand; use the existing
_active_clause(include_inactive, False) so "live row" stays defined in one
place. Behaviour is unchanged.

Co-authored-by: joaomarcos <joaomarcosdias444@gmail.com>
This commit is contained in:
kshitijk4poor
2026-09-26 21:18:03 +05:30
committed by kshitij
parent deb7bc7343
commit 41303b379e
3 changed files with 6 additions and 8 deletions

View File

@@ -657,7 +657,7 @@ def _guard_exports(db, session_ids: list[str]) -> None:
return return
try: try:
for session_id in session_ids: for session_id in session_ids:
db.assert_export_safe(session_id, max_messages=limit, include_inactive=True) db.assert_export_safe(session_id, max_messages=limit)
except SessionExportTooLargeError as exc: except SessionExportTooLargeError as exc:
raise ConsoleCommandError( raise ConsoleCommandError(
f"Session '{exc.session_id}' has more than {limit:,} " f"Session '{exc.session_id}' has more than {limit:,} "

View File

@@ -320,7 +320,7 @@ class SessionPortabilityMixin:
return [self._with_messages(session, True, include_inactive) for session in sessions] return [self._with_messages(session, True, include_inactive) for session in sessions]
messages_by_session = {session["id"]: [] for session in sessions} messages_by_session = {session["id"]: [] for session in sessions}
session_ids = list(messages_by_session) session_ids = list(messages_by_session)
active_clause = "" if include_inactive else " AND active = 1" active_clause = self._active_clause(include_inactive, False)
# Stay below SQLite's legacy 999-variable limit while replacing the per-session N+1 reads. # Stay below SQLite's legacy 999-variable limit while replacing the per-session N+1 reads.
for start in range(0, len(session_ids), 900): for start in range(0, len(session_ids), 900):
chunk = session_ids[start:start + 900] chunk = session_ids[start:start + 900]

View File

@@ -1401,10 +1401,9 @@ class SessionSessionsMixin:
) )
return statuses return statuses
def assert_export_safe(self, session_id: str, max_messages: Optional[int] = None, def assert_export_safe(self, session_id: str, max_messages: Optional[int] = None) -> int:
include_inactive: bool = False) -> int: """Row count of this segment — every row, archived included, as the transfer export materializes
"""Row count of this segment — live rows, or every row with ``include_inactive``, matching what it — or raise SessionExportTooLargeError (the LIMITed subquery
the export materializes — or raise SessionExportTooLargeError (the LIMITed subquery
stops once the bound is exceeded). ``None`` resolves ``sessions.max_export_messages``; 0 disables stops once the bound is exceeded). ``None`` resolves ``sessions.max_export_messages``; 0 disables
the guard.""" the guard."""
from hermes_state import SessionExportTooLargeError, resolved_max_export_messages from hermes_state import SessionExportTooLargeError, resolved_max_export_messages
@@ -1414,9 +1413,8 @@ class SessionSessionsMixin:
raise ValueError("max_messages must be non-negative") raise ValueError("max_messages must be non-negative")
if max_messages == 0: if max_messages == 0:
return 0 return 0
active_clause = "" if include_inactive else " AND active = 1"
row = self._read_one( row = self._read_one(
f"SELECT COUNT(*) FROM (SELECT 1 FROM messages WHERE session_id = ?{active_clause} LIMIT ?)", "SELECT COUNT(*) FROM (SELECT 1 FROM messages WHERE session_id = ? LIMIT ?)",
(session_id, max_messages + 1), (session_id, max_messages + 1),
) )
message_count = int(row[0] if row else 0) message_count = int(row[0] if row else 0)