6ad581d178
## Summary Fixes #12962 - Adds a two-pass ILIKE fallback to the global search service (`search.service.ts`) - **Fast path**: runs the tsvector query first (uses GIN index, sub-millisecond) - **Fallback**: only if tsvector returns fewer results than the limit, runs an ILIKE query on `searchVector::text` to catch cases where PostgreSQL's `simple` text search config fails to tokenize (continuous CJK text, etc.) - Zero performance impact for the common case (Latin text where tsvector works) - Also adds `escapeForIlike` utility to properly escape `%`, `_`, `\` in user input ### Why tsvector fails for CJK PostgreSQL's `simple` config treats continuous CJK text as a single lexeme: - `to_tsvector('simple', '示例商业线索')` → `'示例商业线索':1` - Searching `商业:*` only prefix-matches from the start, so it misses `商业` in the middle The ILIKE fallback catches these substring matches when the tsvector path can't. ### What this fixes - Global search (command menu / sidebar) - Relation picker (single and multi-object) - Morph relation picker All three use `search.service.ts` under the hood. Co-authored-by: mykh-hailo (original direction in #18021) ## Test plan - [ ] Search `示例` with records `示例商业线索` and `示例-商业-线索` → both should appear - [ ] Search `商业` → both should appear (previously only the hyphenated one did) - [ ] Search for Latin text (e.g. `john`) → same performance, results unchanged - [ ] Relation picker search with CJK text → results appear - [ ] Search input with special chars like `%` or `_` → no SQL injection, results correct Made with [Cursor](https://cursor.com) --------- Co-authored-by: mykh-hailo <mykh-hailo@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>