From 094baab7be1b3ebeff6b260c9ebff4bac77edbbd Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 13 Aug 2026 02:06:33 +0200 Subject: [PATCH] feat(search): driveId as a query field scope: takes an opaque resource id, which is hostile to hand-written queries. Accept driveId:"" as a regular KQL field instead: it resolves to the indexed RootID, and a bare drive id is completed to the root resource id (a space root's opaque id is its space id). Full root ids pass through untouched. Combined with path: this gives a readable location scope without any token stripping: both are plain fields, so they compose with groups, OR and NOT like everything else. --- services/search/pkg/query/normalize.go | 17 +++++++++++++++++ services/search/pkg/query/normalize_test.go | 17 +++++++++++++++++ services/search/pkg/query/resolver.go | 1 + 3 files changed, 35 insertions(+) diff --git a/services/search/pkg/query/normalize.go b/services/search/pkg/query/normalize.go index c2aef1e820..62577ca6fb 100644 --- a/services/search/pkg/query/normalize.go +++ b/services/search/pkg/query/normalize.go @@ -2,6 +2,7 @@ package query import ( "strconv" + "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "reflect" @@ -41,6 +42,9 @@ func normalizeNodes(nodes []ast.Node, resolve func(string) string, defaultKey st if node.Key == "Type" { node.Value = resourceType(node.Value) } + if node.Key == "RootID" { + node.Value = completeRootID(node.Value) + } if exp := mimetype.Expand(node.Key, node.Value); exp != nil { out = append(out, normalizeNodes(exp, resolve, defaultKey)...) continue @@ -71,6 +75,19 @@ func normalizeNodes(nodes []ast.Node, resolve func(string) string, defaultKey st return out } +// completeRootID turns a driveId ("storage$space") into the full root +// resource id ("storage$space!space") stored in the index: a space root's +// opaque id is its space id. Full ids pass through untouched. +func completeRootID(v string) string { + if strings.Contains(v, "!") { + return v + } + if i := strings.LastIndex(v, "$"); i >= 0 && i+1 < len(v) { + return v + "!" + v[i+1:] + } + return v +} + // toPointer returns n as a pointer; the parser emits some nodes by value and the // in-place key rewrites would be lost on those. func toPointer(n ast.Node) ast.Node { diff --git a/services/search/pkg/query/normalize_test.go b/services/search/pkg/query/normalize_test.go index 519e979a83..94eecfb300 100644 --- a/services/search/pkg/query/normalize_test.go +++ b/services/search/pkg/query/normalize_test.go @@ -82,6 +82,23 @@ var _ = Describe("Normalize", func() { })) }) + It("resolves driveId to RootID and completes the root id", func() { + got := norm( + &ast.StringNode{Key: "driveId", Value: "1$2"}, + &ast.OperatorNode{Value: "AND"}, + &ast.StringNode{Key: "driveid", Value: "1$2!4"}, + &ast.OperatorNode{Value: "AND"}, + &ast.StringNode{Key: "RootID", Value: "1$2"}, + ) + Expect(got).To(Equal([]ast.Node{ + &ast.StringNode{Key: "RootID", Value: "1$2!2"}, + &ast.OperatorNode{Value: "AND"}, + &ast.StringNode{Key: "RootID", Value: "1$2!4"}, + &ast.OperatorNode{Value: "AND"}, + &ast.StringNode{Key: "RootID", Value: "1$2!2"}, + })) + }) + // A bare restriction inside a named group inherits the group key; a keyed // child keeps its own key; a bare restriction in an unnamed group falls // back to Name. diff --git a/services/search/pkg/query/resolver.go b/services/search/pkg/query/resolver.go index 6f46d65952..5628dd886f 100644 --- a/services/search/pkg/query/resolver.go +++ b/services/search/pkg/query/resolver.go @@ -13,6 +13,7 @@ import ( var aliases = map[string]string{ "tag": "Tags", "favorite": "Favorites", + "driveid": "RootID", } // fieldIndex maps a lowercased KQL key to its canonical field name ("" is the