Zalgorithm

Searching a Database With Fts5 Queries and English Stemming

Continued from FTS5 search with English stemming and AND matching GitHub repo: https://github.com/scossar/find_command/blob/search-with-fts5-query-syntax/search_fts_query.py

For completeness, here’s an implementation of FTS5 search that passes unmodified FTS5 queries to the MATCH operator. Queries are run against the tmux key binding data supplied by tmux_key_bindings.json

"""Search descriptions using an unmodified FTS5 query and English stemming."""

import argparse
import sqlite3
from contextlib import closing
from pathlib import Path


def search(database: Path, query: str) -> list[tuple[str, str]]:
    """Pass the query directly to MATCH, preserving FTS5 syntax."""
    with closing(
        sqlite3.connect(database.resolve().as_uri() + "?mode=ro", uri=True)
    ) as connection:
        # As in search_fts.py, this index lasts only for this connection.
        connection.execute("""
            CREATE VIRTUAL TABLE temp.key_bindings_fts USING fts5(
                key UNINDEXED,
                description,
                tokenize = 'porter unicode61'
            )
        """)
        connection.execute("""
            INSERT INTO temp.key_bindings_fts (rowid, key, description)
            SELECT id, key, description FROM main.key_bindings
        """)
        # Binding protects the SQL statement while deliberately allowing FTS5
        # to interpret operators, phrases, prefixes, and other query syntax.
        return connection.execute(
            """
            SELECT key, description
            FROM temp.key_bindings_fts
            WHERE key_bindings_fts MATCH ?
            ORDER BY rowid
            """,
            (query,),
        ).fetchall()


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("query", help="FTS5 query, including any operators or quotes")
    parser.add_argument(
        "--database",
        type=Path,
        default=Path("tmux_key_bindings.sqlite3"),
        help="SQLite file to search (default: tmux_key_bindings.sqlite3)",
    )
    args = parser.parse_args()
    try:
        rows = search(args.database, args.query)
    except sqlite3.Error as error:
        parser.exit(1, f"Search failed: {error}\n")
    if not rows:
        print("No matches found.")
    for key, description in rows:
        print(f"{key}\t{description}")


if __name__ == "__main__":
    main()

Example queries: #

find-command ❯ uv run python search_fts_query.py 'pane AND window'
C-o	Rotate the panes in the current window forwards.
!	Break the current pane out of the window.
o	Select the next pane in the current window.
M-o	Rotate the panes in the current window backwards.

find-command ❯ uv run python search_fts_query.py 'window NOT pane'
&	Kill the current window.
'	Prompt for a window index to select.
,	Rename the current window.
.	Prompt for an index to move the current window.
0 to 9	Select windows 0 to 9.
c	Create a new window.
f	Prompt to search for text in open windows.
i	Display some information about the current window.
l	Move to the previously selected window.
n	Change to the next window.
p	Change to the previous window.
w	Choose the current window interactively.
Space	Arrange the current window in the next preset layout.
M-n	Move to the next window with a bell or activity marker.
M-p	Move to the previous window with a bell or activity marker.

find-command ❯ uv run python search_fts_query.py '"window pane"'
No matches found.

find-command ❯ uv run python search_fts_query.py 'NEAR(prompt window, 6)'
'	Prompt for a window index to select.
f	Prompt to search for text in open windows.

find_command search-with-fts5-query-syntax
find-command ❯ uv run python search_fts_query.py 'NEAR(prompt window, 5)'
'	Prompt for a window index to select.