Files
fizzy/app/models/search/record/sqlite.rb
T
LU 366588e263 Add SQLite FTS5 support for CJK search
- Split each CJK character into individual tokens for FTS5 indexing
- Add stem_content callback to SQLite adapter to pre-process content
- Stem search queries before matching against FTS5 index
- Update stemmer tests to reflect character-splitting behavior

This enables CJK search on SQLite installations where the FTS5
tokenizer cannot natively segment CJK text.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 17:51:20 +09:00

64 lines
1.9 KiB
Ruby

module Search::Record::SQLite
extend ActiveSupport::Concern
included do
attribute :result_title, :string
attribute :result_content, :string
has_one :search_records_fts, -> { with_rowid },
class_name: "Search::Record::SQLite::Fts", foreign_key: :rowid, primary_key: :id, dependent: :destroy
before_save :stem_content
after_save :upsert_to_fts5_table
scope :matching, ->(query, account_id) { joins(:search_records_fts).where("search_records_fts MATCH ?", Search::Stemmer.stem(query.to_s)) }
end
class_methods do
def search_fields(query)
opening_mark = connection.quote(Search::Highlighter::OPENING_MARK)
closing_mark = connection.quote(Search::Highlighter::CLOSING_MARK)
ellipsis = connection.quote(Search::Highlighter::ELIPSIS)
[ "highlight(search_records_fts, 0, #{opening_mark}, #{closing_mark}) AS result_title",
"snippet(search_records_fts, 1, #{opening_mark}, #{closing_mark}, #{ellipsis}, 20) AS result_content",
"#{connection.quote(query.terms)} AS query" ]
end
def for(account_id)
self
end
end
def card_title
escape_fts_highlight(result_title || card.title)
end
def card_description
escape_fts_highlight(result_content) unless comment
end
def comment_body
escape_fts_highlight(result_content) if comment
end
private
def stem_content
self.title = Search::Stemmer.stem(title) if title_changed?
self.content = Search::Stemmer.stem(content) if content_changed?
end
def escape_fts_highlight(html)
return nil unless html.present?
CGI.escapeHTML(html)
.gsub(CGI.escapeHTML(Search::Highlighter::OPENING_MARK), Search::Highlighter::OPENING_MARK)
.gsub(CGI.escapeHTML(Search::Highlighter::CLOSING_MARK), Search::Highlighter::CLOSING_MARK)
.html_safe
end
def upsert_to_fts5_table
Fts.upsert(id, title, content)
end
end