Skip to content

feat(search): graph search query endpoint with aggregations - #3211

Draft
dschmidt wants to merge 12 commits into
mainfrom
feat/graph-search-query
Draft

feat(search): graph search query endpoint with aggregations#3211
dschmidt wants to merge 12 commits into
mainfrom
feat/graph-search-query

Conversation

@dschmidt

@dschmidt dschmidt commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This contains a huge libre-graph-api-go bump - so please don't be alarmed about the size of the diff

MS-Graph-style POST /graph/v1beta1/search/query with terms, range, metric and nested sub-aggregations, on both the bleve and OpenSearch backends.

Stacked on #3198.

Caveats

  • Vendored libre-graph-api-go is interim, generated from feat: add search query endpoint with request examples libre-graph-api#34 rebased onto main. Proper path: land that spec PR upstream (feat: add search query endpoint with request examples libre-graph-api#34) and pin a released version.
  • Aggregation behavior is pinned in the engine parity suite (AGG-01..09: term buckets, numeric and date ranges, open-ended bounds, metrics, malformed-bound errors), running against bleve and a real OpenSearch. That round also caught and fixed an OpenSearch bug: date range bounds were silently dropped, they now emit a date_range aggregation and malformed bounds are rejected on both engines. Nested sub-aggregations were smoke-tested end-to-end on bleve (artist -> album -> sum(duration)).

Note

Even once this PR and #3210 (honest has-preview + thumbnails relationship) are both merged, search hits still won't carry thumbnails — the $expand=thumbnails wiring for /search/query hits builds on both PRs and currently only exists on the consolidation branch (dschmidt/opencloud@feat/graph-search-full-2, commit 0d94199a: searchEntityThumbnailSet in searchquery.go, preview presence via thumbnail.HasPreviewForMimeType + indexed oc.preview/image-facet dimensions). It needs a follow-up PR once both are in.

@codacy-production

codacy-production Bot commented Jul 30, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 446 complexity

Metric Results
Complexity 446

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@dschmidt

dschmidt commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: narrow Fields when scanning hits for metrics / sub-aggregations

Not blocking, results are correct. It's the cost profile.

needsSubAggScan widens the page to subAggScanSize (math.MaxInt) for any
aggregation with sub-aggregations or a metric, since neither has a native facet
representation in bleve. Together with bleveReq.Fields = []string{"*"}, a
mediatype:image request carrying one Size/sum metric materialises the whole
matching hit set with every stored field, to add up one number. OpenSearch nests
natively and returns no documents.

Easy to reach from a client: a dashboard asking for a few library metrics over
mediatype:image pulls the whole image library into memory per load. Flat term
and range facets are unaffected.

Options by payoff:

  1. Split scan from page. The scan needs only the metric and sub-agg fields; the
    rich Fields set only serves the PageSize window. One request cannot carry
    two field sets.
  2. Otherwise narrow Fields to the union of what hydration and aggregations
    read, instead of "*".
  3. Bound the scan, mark results approximate.
  4. For real library counters (total bytes of all images), maintain a value at
    index time instead.

@dschmidt
dschmidt force-pushed the feat/graph-search-query branch from 1b61ef6 to fb1e41b Compare August 18, 2026 16:51
@dschmidt
dschmidt force-pushed the feat/graph-search-query branch 4 times, most recently from c54ffe7 to cc3e6f4 Compare August 31, 2026 18:37
@dschmidt
dschmidt force-pushed the feat/graph-search-query branch from cc3e6f4 to af951f6 Compare September 1, 2026 05:48
@dschmidt
dschmidt force-pushed the feat/graph-search-query branch from 08eddc9 to ceb7df5 Compare September 1, 2026 07:11
@dschmidt
dschmidt changed the base branch from feat/search-tika-facets to main September 1, 2026 08:34
@dschmidt
dschmidt force-pushed the feat/graph-search-query branch 3 times, most recently from 2513263 to 71ca404 Compare September 3, 2026 08:33
…egations

Rebased dschmidt/libre-graph-api feat/graph-search-full (PR #34) onto
opencloud-eu main and regenerated via the repo's woodpecker build-go recipe
(openapi-generator v7.23.0, --api-name-suffix Api).
Adds the graph /v1beta1/search/query endpoint, the aggregation proto messages,
the service-layer aggregation forwarding/merging, and the recursive bleve
aggregation implementation (terms, range, metric, sub-aggregations).
Implements terms, range, metric and sub-aggregations for the OpenSearch backend
via a dedicated aggs builder, wiring them through the shared search service.
Range aggregations parsed from/to with ParseFloat only, so date bounds on
datetime fields like photo.takenDateTime silently degraded to unbounded
numeric ranges. Detect date-formatted bounds (RFC3339 or YYYY-MM-DD),
switch the facet to bleve date ranges and read DateRanges from the facet
result. Malformed bounds in date mode are rejected.
Metric aggregations (sum/min/max/avg) only worked as sub-aggregations
under a terms bucket. Compute top-level metrics by folding the matched
hits through the existing accumulator and allow them through the graph
layer's numeric field validation.
The cross-space merge only carried buckets, dropping metric results
(value/metricKind) from the per-space responses. Reduce metrics with
their kind's reducer, keyed by field and kind.
Regenerate the vendored libre-graph-api-go from the feat/search spec
(metricDefinition/searchMetric, @libre.graph.subAggregations,
aggregationFilterToken) and add aggregation_filters plus the
permissionsActionsAllowedValues entity field to the search protos.
Migrate the graph search query to metricDefinition/searchMetric and the
@libre.graph prefixes. Add the aggregationFilterToken round-trip: encode
terms/range/or tokens on the response, pass aggregationFilters 1:1 to the
search service, decode them and force exact case-sensitive matches in both
backends. Map the WebDAV-report facets onto search hits: tags, video,
motionPhoto, livePhoto, allowedValues (from the space permission set),
me.following (favorite), webUrl (private link, shared helper) and
thumbnails via opt-in $expand.
Bleve has count facets only: no metric and no nested facet. So far a
metric or a sub-aggregation widened the page to every match and loaded
all stored fields of each, then folded the hits in Go. The stored
document, extracted text included, was decoded for every match: about
1s and 780MB per 100k matches, independent of the nesting depth.

Metrics and nested aggregations now go through an aggCollector hooked
into bleve's collector walk via the document-match-handler context key.
For every match it visits the doc values of the aggregated fields, the
columnar storage bleve's own facets read, and folds them into an
accumulator tree. The page stays the size the caller asked for and no
stored field is loaded for an aggregation: 7x faster and 80x less memory
at 100k matches, within 1.4x of a native bleve facet.

Flat terms and range aggregations stay bleve facets. A range parent now
carries its sub-aggregations too (the hit fold matched range bucket
names against raw values and never attached them), and multi-valued
fields count each value like a bleve facet does.

The parity suite renders nested results and pins them on both engines:
terms in terms, metrics per bucket, terms in numeric and date ranges,
three levels, a page of one still aggregating every match, and a
malformed bound in a nested range.
@dschmidt
dschmidt force-pushed the feat/graph-search-query branch from fef1116 to 7f7ecbf Compare September 11, 2026 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant