Skip to content

fix(search): use implicit AND in FTS queries, not the AND keyword - #2706

Open
PonceGL wants to merge 1 commit into
PixelPlayerHQ:masterfrom
PonceGL:bugfix/fts-multiword-search-and-keyword
Open

fix(search): use implicit AND in FTS queries, not the AND keyword#2706
PonceGL wants to merge 1 commit into
PixelPlayerHQ:masterfrom
PonceGL:bugfix/fts-multiword-search-and-keyword

Conversation

@PonceGL

@PonceGL PonceGL commented Aug 6, 2026

Copy link
Copy Markdown

The bug

buildSongSearchMatchQuery/buildSongTitleSearchMatchQuery in MusicDao.kt join query tokens with the literal " AND " keyword, e.g. searching "que ganas" builds the MATCH expression "que* AND ganas*".

That keyword syntax only behaves as a boolean operator on SQLite builds compiled with SQLITE_ENABLE_FTS3_PARENTHESIS. That's not guaranteed on every Android device/OEM SQLite build. Without it, AND is parsed as an ordinary search term (literally the word "and"), not an operator — so a multi-word query only matches rows that literally contain the word "and", silently breaking multi-word search on affected devices. Single-word searches are unaffected, which is presumably why this has gone unnoticed.

Repro

Confirmed on a real device (Galaxy S25 Ultra, Android's bundled SQLite 3.44.5, no FTS3_PARENTHESIS support): a song titled "Qué ganas de comerte" is unfindable by searching "que ganas" (or even "qué ganas" — this isn't about accents) — zero results — while it's trivially found by a single-word query like "ganas", and "que* AND ganas*" only matches rows that happen to also literally contain the word "and" somewhere in the title.

Fix

Join tokens with a plain space instead of AND. Implicit AND (space-separated terms) is base FTS3/4 query syntax, supported unconditionally regardless of which extensions the SQLite build includes — no functional change for builds that do have the extension, and it fixes multi-word search entirely for the ones that don't.

Also extracted the shared tokenization/joining logic into one buildFtsMatchQuery helper to remove the near-identical duplication between the two query builders (DRY), and made both functions internal so they're unit-testable directly.

Testing

  • MusicDaoQueryBuilderTest (new, unit): verifies the built MATCH query string no longer contains the literal "AND" keyword and uses implicit AND instead.
  • MusicDaoTest.searchSongs_multiWordQuery_matchesSongWithoutLiteralAndKeyword (new, instrumented): runs the real query against a real in-memory FTS4 table via Room — this is what actually caught the bug, since a plain string-comparison unit test wouldn't exercise the SQLite engine's query parser at all. Ran on the real device that reproduced the bug: fails on the old code, passes after the fix.

buildSongSearchMatchQuery/buildSongTitleSearchMatchQuery joined query
tokens with the literal " AND " keyword (e.g. "que* AND ganas*").
That keyword only behaves as a boolean operator on SQLite builds
compiled with SQLITE_ENABLE_FTS3_PARENTHESIS - not guaranteed on every
Android device. Without it, "AND" is parsed as an ordinary search
term, so a multi-word query would only match rows that literally
contained the word "and", silently breaking multi-word search
entirely on affected devices.

Confirmed on a real device (Galaxy S25 Ultra, SQLite 3.44.5, no
FTS3_PARENTHESIS support): searching a two-word query against a title
that doesn't contain "and" returned zero results, regardless of case
or accents, while the exact same content was trivially findable via a
single-token query or via the always-available implicit-AND syntax
(space-separated terms, no keyword).

Fix: join tokens with a plain space instead. Implicit AND is base FTS3/4
syntax, supported unconditionally on every SQLite build. Also extracts
the shared tokenization logic into one helper (buildFtsMatchQuery) to
remove the duplication between the two query builders.

Adds MusicDaoQueryBuilderTest (unit, verifies the query string shape)
and a MusicDaoTest regression case that runs the real query against a
real FTS4 table - which is what actually caught this, since a plain
string-comparison test wouldn't exercise the SQLite engine at all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant