Add search passthrough to the advanced-search work items finder
What does this MR do and why?
ee/lib/ee/search/advanced_finders/work_items_finder.rb hardcoded the Elasticsearch query string to '*', a match-all. It now passes params[:search] through when present and falls back to '*' when not.
QUERY is renamed DEFAULT_QUERY to say what it actually is.
:search becomes an allowed Elasticsearch filter, behind a new feature flag (see below). Without it in the allowed list, the supported-fields check fails and the query falls back to the Postgres finder.
Feature flag
New flag work_items_search_term_es_integration, type gitlab_com_derisk, default_enabled: false, milestone 19.6.
Why it exists, raised by @rkumar555 in review: reaching this finder at all is gated by glql_es_integration, which is an ops flag with default_enabled: true and has been on in production since April 2026. WorkItemsResolver#choose_finder checks glql_es_integration OR work_items_list_es_integration, so that gate is already open. Without a flag of its own, merging this would move every GLQL work-item search with a term from Postgres to Elasticsearch in one step, on GitLab.com and every self-managed instance with advanced search, and the only way back would be disabling glql_es_integration entirely.
The flag gates only the allowed-filters entry. With it off, use_elasticsearch_finder? returns false and the query stays on Postgres exactly as today.
Because a feature flag can't be evaluated when a class-level constant is defined, :search is appended by an allowed_es_filters method rather than living in the frozen ALLOWED_ES_FILTERS array.
Behaviour change
Global Search own ee/lib/ee/search, so the switch itself is theirs to sign off.
How Elasticsearch and Postgres results differ
- Fields searched. Postgres covers
titleanddescriptiononly (Issuable::SEARCHABLE_FIELDS). Elasticsearch coversiid^50 title^2 description, andWorkItemQueryBuilder::IID_REGEXturns a term like#1234into an exact iid lookup. Sosearch = "#1234"finds that work item on Elasticsearch and nothing on Postgres. - Query syntax in the term, raised by @rkumar555.
Search::Elastic::Queries.by_full_textsends any term matchingADVANCED_QUERY_SYNTAX_REGEXtosimple_query_string. That regex is/[*"~#!()|]/plus/(?:^|\s)[+\-]/, sosearch = "memory -leak"excludesleak, and*, quotes,|and the rest act as operators. Postgres treats all of them as literal text. Literal matching on Elasticsearch would need the term escaped. - Two Postgres paths.
IssuableFinder#by_searchpickspg_full_text_search(tsvector, title weight A, description weight B) only when the term matchesFULL_TEXT_SEARCH_TERM_REGEX, which covers Latin ranges. Anything else, such as CJK or Cyrillic, falls through tofuzzy_search, i.e. ILIKE. Elasticsearch applies its analyzer uniformly. - Missing search data.
pg_full_text_searchinner-joinsissue_search_data. A work item with no row there, whichPgFullTextSearchablenotes happens when the tsvector is too long, is excluded from Postgres results entirely while Elasticsearch still finds it. - Ranking. Field boosts and relevance scoring on Elasticsearch versus tsvector A and B weights on Postgres.
Review feedback addressed
Thanks @rkumar555 for the review.
- Blocking: added the rollout flag, as above.
- Added
#use_elasticsearch_finder?specs covering the routing itself: a term returns true, a term with the flag off returns false, and a term combined within:returns false becauseinis not an allowed Elasticsearch filter. - Added the query-syntax difference to the behaviour notes above.
- Moved the stale comment:
DEFAULT_QUERYnow describes only the match-all fallback, and the keyword behaviour is documented onsearch_query. - Removed an incorrect bullet from this description claiming the MR bumps
gitlab_query_languageto~> 0.42.0. Master already pins0.42.0, bumped independently, and this diff doesn't touch the Gemfile.
How to set up and validate locally
- Elasticsearch enabled in the GDK —
elasticsearch.enabled: trueingdk.yml, service running, work item index populated. - Enable the flag in a Rails console:
Feature.enable(:work_items_search_term_es_integration) - Run the spec:
bundle exec rspec ee/spec/lib/ee/search/advanced_finders/work_items_finder_spec.rb - To confirm the gem compiles the filter:
bundle exec ruby -e 'require "gitlab_query_language"; puts ::Glql.compile(%q{type = Issue and search = "memory leak"}, { "project" => "gitlab-org/gitlab", "fields" => "id, title" })["output"]'— the generated query should carrysearch: "memory leak". NoteGlql.compiletakes and returns a Hash, not JSON.
Testing
New routing specs for use_elasticsearch_finder?, plus the three #execute contexts for the passthrough: term supplied, blank term, no term. RuboCop clean. Verified against the released 0.42.0 gem that search compiles to the search: argument on work items, merge requests and projects.
Related to #631271