Skip to content

MB-65016: More Like This query - #2411

Open
maneuvertomars wants to merge 1 commit into
masterfrom
MB-65016
Open

maneuvertomars wants to merge 1 commit into
masterfrom
MB-65016

Conversation

@maneuvertomars

@maneuvertomars maneuvertomars commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Add more_like_this query

A new query type that finds documents similar to given documents (like_ids) or to raw text (like_text), in the spirit of Lucene's MoreLikeThis / Elasticsearch's more_like_this.

How it works

  1. The source documents' terms in the compared fields are read from doc values, each term's tf comes from its posting entry for the document, df from the term dictionary, N from the reader.
  2. Terms are ranked by tf·idf (the idf of the index's scoring model, tf-idf or BM25), filtered by min_term_freq / min_doc_freq, and the top max_query_terms are kept.
  3. The node is rewritten into standard queries: a disjunction of term queries (min applied), wrapped in a boolean with a must_not on like_ids, so the source documents are excluded.

On a single index this happens in SearchInContext (and in Searcher()). On an index alias it rides on the existing preSearch round: a fourth preSearchFlags entry asks every member for the term stats of each node (SearchResult.MLTStats, keyed by the node's position in the query tree), the alias merges them order-independently (a source document replicated in two members is counted once; df and N are summed per contributing member), substitutes the nodes, and MultiSearch runs the plain query. The caller's request is never mutated: the rewritten query travels through multiSearchParams, so score fusion and the request echo keep working.

API

{ "query": { "more_like_this": {
    "fields": ["description"],
    "like_ids": ["hotel_10025"],
    "min_term_freq": 1, "min_doc_freq": 1, "max_query_terms": 25,
    "min": "30%", "boost_terms": 0 } } }

@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 52.122% (+0.5%) from 51.659% — MB-65016 into master

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved query-rewrite issues can admit synonym-only matches, silently discard nested-field sources, and make alias term selection depend on index grouping or response order.

5 open issues
What changed in this PR

Adds more_like_this similarity queries to Bleve, supporting source document IDs or raw text across individual indexes and index aliases.

Changes:

  • Collects term statistics, ranks candidates by TF-IDF or BM25, and rewrites similarity queries into standard queries.
  • Integrates distributed statistics collection and merging into alias preSearch.
  • Adds rewrite-only responses, explanatory notes, and unit and integration tests.
File Description
search/​util.go Defines similarity statistics and the preSearch marker.
search/​query/​term.go Adds optional notes to term queries.
search/​query/​query.go Parses similarity queries and extracts their fields.
search/​query/​more_like_this.go Implements validation, statistics collection, ranking, and rewriting.
search/​query/​more_like_this_test.go Tests parsing, validation, rewriting, and query traversal.
search/​query/​disjunction.go Preserves optional disjunction notes.
search/​query/​boolean.go Preserves optional boolean query notes.
search.go Adds statistics and rewritten queries to search results.
search_no_knn.go Adds rewrite-only and notes support to non-vector requests.
search_knn.go Adds request options and rejects similarity queries in vector filters.
pre_search.go Merges similarity statistics and builds rewritten responses.
more_like_this_test.go Tests single-index and alias similarity searches.
index_impl.go Integrates local statistics collection and query rewriting.
index_alias_impl.go Coordinates distributed rewriting and forwards rewritten queries.

Review effort: Balanced

Comment thread index_alias_impl.go
Comment on lines +1090 to +1092
if m := in.Mapping(); m != nil {
return m
}
Comment thread pre_search.go
key := mltTermKey(st)
cur, ok := n.terms[key]
if !ok {
cur = &search.MLTTermStat{Field: st.Field, Term: st.Term, TF: st.TF}
Comment on lines +387 to +390
dvReader, err := r.DocValueReader(fields)
if err != nil {
return nil, err
}
type fieldTerm struct{ field, term string }
var docTerms []fieldTerm
seen := make(map[fieldTerm]struct{})
err = dvReader.VisitDocValues(internalID, func(field string, term []byte) {
Comment on lines +560 to +561
tq := NewTermQuery(w.stat.Term)
tq.SetField(w.stat.Field)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread index_alias_impl.go
Comment on lines +375 to 379
// an explained result also shows the query that was actually run
if req.Explain && searchReq != req {
sr.RewrittenQuery = &RewrittenQuery{Query: searchReq.Query}
}
}
Comment thread index_alias_impl.go
if knnFiltersContainMoreLikeThis(req) {
return nil, fmt.Errorf("knn filter query cannot contain a more_like_this query")
}
mlt := len(mltNodes) > 0
Comment on lines +300 to +308
case fm.Type == "":
// not explicitly mapped: indexed dynamically, if at all
if !im.DocValuesDynamic {
return fmt.Errorf("more_like_this: field %q has no doc values", field)
}
case fm.Type != "text":
return fmt.Errorf("more_like_this: field %q is not a text field", field)
case !fm.DocValues:
return fmt.Errorf("more_like_this: field %q has no doc values", field)
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.

3 participants