Search for operator words instead of parsing them

The search box keeps only word characters, but FTS5 still reads AND, OR,
NOT and NEAR in the query as operators, so searching for "AND" or for
"salt AND" raised "fts5: syntax error" and answered with a 500. Quoting
each word makes FTS5 look for it as text, which is how it already treats
the same words in lower case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuTtwJKhb77Dv7EQqv2C3C
This commit is contained in:
Marcello Costagliola
2026-10-05 01:08:53 +02:00
parent 254dd1d46f
commit 6cdcb459b9
3 changed files with 33 additions and 1 deletions
+9 -1
View File
@@ -6,7 +6,15 @@ module Message::Searchable
after_update_commit :update_in_index
after_destroy_commit :remove_from_index
scope :search, ->(query) { joins("join message_search_index idx on messages.id = idx.rowid").where("idx.body match ?", query).ordered }
scope :search, ->(query) { joins("join message_search_index idx on messages.id = idx.rowid").where("idx.body match ?", match_terms(query)).ordered }
end
class_methods do
# Quotes each word, so that FTS5 searches for AND, OR, NOT and NEAR rather than
# parsing them as operators, which fails on a query like "AND" or "salt AND".
def match_terms(query)
query.split.map { |word| %("#{word.gsub('"', '""')}") }.join(" ")
end
end
private
@@ -20,6 +20,24 @@ class SearchesControllerTest < ActionDispatch::IntegrationTest
assert_select ".message", text: /Hello world!/
end
test "operator words are searched for as words" do
rooms(:designers).messages.create! body: "Salt and pepper", client_message_id: "operators", creator: users(:david)
get searches_url, params: { q: "salt AND" }
assert_response :success
assert_select ".message", text: /Salt and pepper/
end
test "searching for an operator word alone doesn't fail" do
%w[ AND OR NOT ].each do |word|
get searches_url, params: { q: word }
assert_response :success
assert_select ".message", count: 0
end
end
test "unreachable messages are not found" do
memberships(:david_designers).destroy!
+6
View File
@@ -65,6 +65,12 @@ class MessageTest < ActiveSupport::TestCase
assert_equal room.messages.ordered.first(Message::Pagination::PAGE_SIZE).map(&:id), room.messages.first_page.map(&:id)
end
test "search reads quotes and operator words in the query as text" do
message = rooms(:designers).messages.create!(body: "Say hi, NOT bye", client_message_id: "quoted", creator: users(:david))
assert_equal [ message ], Message.search(%(say "hi NOT)).to_a
end
private
def create_new_message_in(room)
room.messages.create!(creator: users(:jason), body: "Hello", client_message_id: "123")