e0d66b2fd0
* Fix multi-term SQLite FTS5 search causing a 500 error When filtering cards by more than one term, `matching` was called once per term via an association join. Rails did not deduplicate association joins, resulting in two JOINs to `search_records_fts` in a single query. SQLite FTS5 then raised "ambiguous column name" when evaluating the MATCH condition. Fix by using a string join instead, which Rails deduplicates so only one JOIN to `search_records_fts` is generated regardless of how many terms are searched. Fixes: https://github.com/basecamp/fizzy/discussions/2354 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: verify multi-term search requires ALL terms to match --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Mike Dalessio <mike@37signals.com>
61 lines
1.7 KiB
Ruby
61 lines
1.7 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
|
|
|
|
after_save :upsert_to_fts5_table
|
|
|
|
scope :matching, ->(query, account_id) {
|
|
joins("INNER JOIN search_records_fts ON search_records_fts.rowid = #{table_name}.id")
|
|
.where("search_records_fts MATCH ?", query)
|
|
}
|
|
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 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
|