Full-Text Search with FTS5

Implement powerful text search using SQLite's FTS5 extension with BM25 ranking.

What is FTS5?

FTS5 (Full-Text Search 5) is SQLite's built-in full-text search engine. It provides:

Setting Up FTS5

local
    fts: SIMPLE_SQL_FTS5
do
    create fts.make (db)

    -- Create FTS5 virtual table
    fts.create_table ("articles_fts", <<"title", "content">>)
end

Indexing Content

-- Add documents to the index
fts.insert ("articles_fts", <<"Getting Started with Eiffel", "Eiffel is a programming language...">>)
fts.insert ("articles_fts", <<"Advanced Eiffel Patterns", "This article covers advanced topics...">>)
fts.insert ("articles_fts", <<"Database Access in Eiffel", "Learn how to use SIMPLE_SQL...">>)

Basic Search

local
    result: SIMPLE_SQL_RESULT
do
    -- Simple search
    result := fts.search ("articles_fts", "eiffel")

    across result as row loop
        print (row.string_value ("title") + "%N")
    end
end

Query Syntax

Query Meaning
word Documents containing "word"
word1 word2 Documents containing both words (AND)
word1 OR word2 Documents containing either word
"exact phrase" Documents containing the exact phrase
word* Prefix matching (word, words, wording...)
title:word Search only in title column
NOT word Exclude documents containing word

Example Queries

-- Phrase search
result := fts.search ("articles_fts", "%"getting started%"")

-- Boolean OR
result := fts.search ("articles_fts", "eiffel OR database")

-- Prefix search
result := fts.search ("articles_fts", "prog*")  -- programming, program, etc.

-- Exclude term
result := fts.search ("articles_fts", "eiffel NOT advanced")

-- Search specific column
result := fts.search ("articles_fts", "title:database")

Highlighted Results

-- Get results with highlighted matches
result := fts.search_with_highlights ("articles_fts", "eiffel", 1, "<mark>", "</mark>")

across result as row loop
    -- Content will have <mark>Eiffel</mark> around matches
    print (row.string_value ("content") + "%N")
end

Snippets (Context Around Matches)

-- Get snippets with context
result := fts.search_with_snippets ("articles_fts", "eiffel", 1, "<b>", "</b>", 20)

-- Result: "...programming language. <b>Eiffel</b> is known for..."

External Content Tables

Index an existing table without duplicating data:

-- Create FTS index linked to existing 'documents' table
fts.create_content_table ("documents_fts", "documents", <<"title", "body">>)

-- Rebuild index after data changes
fts.rebuild ("documents_fts")

Handling Special Characters

Apostrophes in Queries

Apostrophes need special handling. Wrap terms with apostrophes in double quotes:

-- WRONG: apostrophe breaks query
result := fts.search ("articles_fts", "O'Brien")  -- Error!

-- CORRECT: wrap in double quotes
result := fts.search ("articles_fts", "%"O'Brien%"")

Complete Search Feature

class DOCUMENT_SEARCH

feature

    db: SIMPLE_SQL_DATABASE
    fts: SIMPLE_SQL_FTS5

    setup
        do
            create fts.make (db)
            fts.create_content_table ("docs_fts", "documents", <<"title", "content">>)
        end

    search (a_query: STRING): ARRAYED_LIST [TUPLE [id: INTEGER_64; title: STRING_32; snippet: STRING_32]]
        local
            result: SIMPLE_SQL_RESULT
            escaped_query: STRING
        do
            create Result.make (10)

            -- Escape special characters
            escaped_query := escape_query (a_query)

            -- Search with snippets
            result := fts.search_with_snippets ("docs_fts", escaped_query, 1, "<mark>", "</mark>", 50)

            across result as row loop
                Result.extend ([
                    row.integer_value ("rowid"),
                    row.string_value ("title"),
                    row.string_value ("snippet")
                ])
            end
        end

    escape_query (a_query: STRING): STRING
        do
            Result := a_query.twin
            -- Wrap in quotes if contains special chars
            if Result.has (''') then
                Result := "%"" + Result + "%""
            end
        end

end

Next Steps