fix(search): keep OpenSearch path queries as unanalyzed term queries

A path value with spaces went through a match_phrase query, which analyzes
the query with the path_hierarchy analyzer; the resulting "." prefix token
matches every document in the space, breaking descendant matching and the
stale-path check after a move.
This commit is contained in:
Dominik Schmidt committed 2026-08-31 13:40:42 +02:00
1 parent 0284224787
commit 17144d4791
2 files changed
+15 -44

No files matched your search

@@ -115,6 +115,13 @@ func (t kqlOpensearchTranspiler) toBuilder(node ast.Node) (osu.Builder, error) {
return osu.NewMatchPhraseQuery(field).Query(value), nil
}
// a path value is a single term in the path_hierarchy token stream; a
// phrase match would analyze the query into its path prefixes and match
// everything under the root, so paths with spaces must stay term queries.
if query.FieldIsPath(node.Key) {
return osu.NewTermQuery[string](field).Value(value), nil
}
totalTerms := strings.Split(value, " ")
isSingleTerm := len(totalTerms) == 1
isMultiTerm := len(totalTerms) >= 1
@@ -97,60 +97,24 @@ func TestTranspileKQLToOpenSearch(t *testing.T) {
Want: osu.NewWildcardQuery("Content").Value("open*"),
},
{
Name: "wildcard query - a question mark counts as a wildcard",
// a phrase match would analyze the query with path_hierarchy and match
// everything under the root
Name: "path with spaces stays an unanalyzed term query",
Got: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "Name", Value: "fo?o"},
&ast.StringNode{Key: "Path", Value: "./parent d!r/child.pdf"},
},
},
Want: osu.NewBoolQuery().
Params(&osu.BoolQueryParams{MinimumShouldMatch: 1}).
Should(
osu.NewWildcardQuery("Name.wildcard").
Value("fo?o").
Params(&osu.WildcardQueryParams{CaseInsensitive: true}),
osu.NewWildcardQuery("Name.wildcard").
Value("fo?o.*").
Params(&osu.WildcardQueryParams{CaseInsensitive: true}),
),
Want: osu.NewTermQuery[string]("Path").Value("./parent d!r/child.pdf"),
},
{
Name: "term query - an equals restriction matches the whole name",
Name: "case-insensitive path with spaces routes to the lowercased sibling as a term query",
Got: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "Name", Value: "foo bar.txt", Exact: true},
&ast.StringNode{Key: "Path", Value: "./Parent Dir", CaseInsensitive: true},
},
},
Want: osu.NewTermQuery[string]("Name.wildcard").
Value("foo bar.txt").
Params(&osu.TermQueryParams{CaseInsensitive: true}),
},
{
Name: "term query - a path loses its trailing slash",
Got: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "Path", Value: "./Documents/"},
},
},
Want: osu.NewTermQuery[string]("Path").Value("./Documents"),
},
{
Name: "term query - a hidden string turns into a bool",
Got: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "Hidden", Value: "true"},
},
},
Want: osu.NewTermQuery[bool]("Hidden").Value(true),
},
{
Name: "match-none query - a hidden string that is no bool",
Got: &ast.Ast{
Nodes: []ast.Node{
&ast.StringNode{Key: "Hidden", Value: "banana"},
},
},
Want: osu.NewMatchNoneQuery(),
Want: osu.NewTermQuery[string]("Path_lowercase").Value("./parent dir"),
},
{
Name: "bool query",