diff --git a/hermes_state_schema.py b/hermes_state_schema.py index 5b860b514e..684facc904 100644 --- a/hermes_state_schema.py +++ b/hermes_state_schema.py @@ -838,29 +838,10 @@ class SessionSchemaMixin: def _run_data_migrations(self, cursor: sqlite3.Cursor, current_version: int, fts5_available: bool) -> None: """Version-gated chain for DATA migrations only (row backfills, version-specific index changes); column additions never belong here. Advances schema_version at - the end unless FTS work could not complete.""" + the end unless FTS5 is unavailable.""" # Renew the lease: the chain can rewrite whole tables on large DBs. report_startup_progress(600.0, phase="state_db_data_migrations") - fts_migrations_complete = True - if current_version < 10 and SCHEMA_VERSION == 10: - # v10: one-time trigram backfill. Only when v10 itself is the target: v11+ - # drops and rebuilds both FTS tables, so the backfill would only burn startup - # time and WAL space. - if fts5_available: - _fts_trigram_exists = self._fts_table_probe(cursor, "messages_fts_trigram") - if _fts_trigram_exists is False: - if self._ensure_fts_schema(cursor, "messages_fts_trigram", FTS_TRIGRAM_SQL): - cursor.execute( - "INSERT INTO messages_fts_trigram(rowid, content) " - "SELECT id, content FROM messages WHERE content IS NOT NULL" - ) - else: - fts_migrations_complete = False - elif _fts_trigram_exists is None: - fts_migrations_complete = False - else: - fts_migrations_complete = False - # (v11 inline FTS re-index was superseded by v23 and removed.) + # (v10 trigram backfill and v11 inline FTS re-index were superseded by v23 and removed.) if current_version < 16: # v16: tag delegate subagent rows so pickers stay clean after parent deletes # orphan them. The shared predicate excludes user-visible reset children. @@ -936,7 +917,7 @@ class SessionSchemaMixin: # Advance schema_version — deliberately NOT gated on the FTS opt-in (that would # block every future migration for a user who never optimizes). FTS5 unavailable # is the one skip: claiming current would lie. - if current_version < SCHEMA_VERSION and fts_migrations_complete and fts5_available: + if current_version < SCHEMA_VERSION and fts5_available: cursor.execute("UPDATE schema_version SET version = ?", (SCHEMA_VERSION,)) def _migrate_v22_session_model_usage(self, cursor: sqlite3.Cursor) -> None: diff --git a/hermes_state_search.py b/hermes_state_search.py index b1cc26b2ec..8c33c867a6 100644 --- a/hermes_state_search.py +++ b/hermes_state_search.py @@ -132,6 +132,20 @@ def _positive_int(name: str, value: Any) -> None: raise ValueError(f"{name} must be greater than zero") +def _search_select_sql(snippet_sql: str, from_sql: str, where: List[str], order_by: str, limit_sql: str) -> str: + """Result-row SELECT shared by the FTS and LIKE routes (SQL text is pinned).""" + return f""" + SELECT m.id, m.session_id, m.role, + {snippet_sql}, + {_SEARCH_SELECT_TAIL} + FROM {from_sql} + JOIN sessions s ON s.id = m.session_id + WHERE {' AND '.join(where)} + {order_by} + {limit_sql} + """ + + def _search_filter_clauses( where: List[str], params: list, *, include_inactive: bool, source_filter: Optional[List[str]], exclude_sources: Optional[List[str]], role_filter: Optional[List[str]], @@ -918,17 +932,10 @@ class SessionSearchMixin: exclude_sources=exclude_sources, role_filter=role_filter, ) params.extend([limit, offset]) - sql = f""" - SELECT m.id, m.session_id, m.role, - snippet({table}, -1, '>>>', '<<<', '...', 40) AS snippet, - {_SEARCH_SELECT_TAIL} - FROM {table} - JOIN messages m ON m.id = {table}.rowid - JOIN sessions s ON s.id = m.session_id - WHERE {' AND '.join(where)} - {order_by_sql} - LIMIT ? OFFSET ? - """ + sql = _search_select_sql( + f"snippet({table}, -1, '>>>', '<<<', '...', 40) AS snippet", + f"{table}\n JOIN messages m ON m.id = {table}.rowid", where, order_by_sql, "LIMIT ? OFFSET ?", + ) return sql, params def _match_rows( @@ -959,16 +966,7 @@ class SessionSearchMixin: def _like_rows(self, where: List[str], params: list, *, order_by: str, limit_sql: str) -> List[Dict[str, Any]]: """Canonical-table LIKE scan; ``params[0]`` is the snippet anchor term.""" - sql = f""" - SELECT m.id, m.session_id, m.role, - {_LIKE_SNIPPET_SQL}, - {_SEARCH_SELECT_TAIL} - FROM messages m - JOIN sessions s ON s.id = m.session_id - WHERE {' AND '.join(where)} - {order_by} - {limit_sql} - """ + sql = _search_select_sql(_LIKE_SNIPPET_SQL, "messages m", where, order_by, limit_sql) return [dict(row) for row in self._read_all(sql, params)] @staticmethod