Compare commits

...
Author SHA1 Message Date
Dominik Schmidt 054337f4fd fix(search): analyze Content queries on OpenSearch
Content is an analyzed text field, but a single-term KQL query on it was
compiled to a term query, which matches the raw value against the tokenized
content and never hits. "Content:https://opencloud.eu/" (tokenized to
[https, opencloud.eu, ...]) returned nothing; "Content:alan@" likewise.

Route full-text fields to match_phrase so the query is analyzed the same way
as the indexed content. Fixes the OpenSearch content-search acceptance
scenarios (contentSearch.feature search by different content types).
2026-08-19 08:40:58 +02:00
2 changed files with 28 additions and 0 deletions

No files matched your search

@@ -15,6 +15,18 @@ func TranspileKQLToOpenSearch(nodes []ast.Node) (osu.Builder, error) {
return kqlOpensearchTranspiler{}.Transpile(nodes)
}
// fullTextFields are analyzed (text) fields whose query value must be analyzed
// the same way as the indexed content. Querying them with a term query would
// match the raw, unanalyzed value against tokenized content and miss.
var fullTextFields = map[string]struct{}{
"Content": {},
}
func isFullTextField(key string) bool {
_, ok := fullTextFields[key]
return ok
}
type kqlOpensearchTranspiler struct{}
func (t kqlOpensearchTranspiler) Transpile(nodes []ast.Node) (osu.Builder, error) {
@@ -108,6 +120,11 @@ func (t kqlOpensearchTranspiler) toBuilder(node ast.Node) (osu.Builder, error) {
isSingleTerm := len(totalTerms) == 1
isMultiTerm := len(totalTerms) >= 1
switch {
case isFullTextField(node.Key):
// analyzed text fields must analyze the query too; a term query looks
// for the raw value and never matches tokenized content, e.g.
// "Content:https://opencloud.eu/" against the tokenized URL
return osu.NewMatchPhraseQuery(node.Key).Query(node.Value), nil
case isSingleTerm:
return osu.NewTermQuery[string](node.Key).Value(node.Value), nil
case isMultiTerm:
@@ -51,6 +51,17 @@ func TestTranspileKQLToOpenSearch(t *testing.T) {
},
Want: osu.NewMatchPhraseQuery("Name").Query(`open cloud`),
},
{
Name: "match-phrase query - full text Content field, single term",
Got: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "Content", Value: "https://opencloud.eu/"},
},
},
// Content is analyzed, so even a single term must use match_phrase
// rather than a term query, which would not match tokenized content.
Want: osu.NewMatchPhraseQuery("Content").Query("https://opencloud.eu/"),
},
{
Name: "wildcard query - string node",
Got: &ast.Ast{