Skip to content

docs: add 10 missing aggregation pipeline stage references - #47

Closed
richardsimmonds wants to merge 3 commits into
documentdb:mainfrom
richardsimmonds:docs/add-missing-aggregation-stages
Closed

docs: add 10 missing aggregation pipeline stage references#47
richardsimmonds wants to merge 3 commits into
documentdb:mainfrom
richardsimmonds:docs/add-missing-aggregation-stages

Conversation

@richardsimmonds

Copy link
Copy Markdown
Contributor

Summary

Adds documentation for 10 aggregation pipeline stages that are implemented in DocumentDB but were missing from the docs site.

New stage docs

Stage Description
$project Field inclusion/exclusion and expression projection
$setWindowFields Window functions with partitioning and frame specs
$graphLookup Recursive graph traversal across collections
$replaceRoot Promote a subdocument to top-level
$unionWith Combine results from multiple collections (UNION ALL)
$vectorSearch Approximate nearest neighbor vector search
$limit Restrict output document count
$count Count documents passing through pipeline
$currentOp Admin metadata for active operations
$search Text search stage

Approach

  • Each doc follows the existing format: YAML frontmatter, syntax block, parameters table, examples, and key takeaways
  • Validated against the C source implementation in bson_aggregation_pipeline.c for accuracy on supported parameters and behavior
  • Brings the documented aggregation stage count from 25 → 35

Validation

  • Confirmed each stage is registered in StageDefinitions[] in the DocumentDB source
  • Cross-referenced parameter handling and validation rules from the C code

Add documentation for aggregation stages that are implemented in
DocumentDB but were missing from the docs:

- $project — field inclusion/exclusion and expression projection
- $setWindowFields — window functions with partitioning
- $graphLookup — recursive graph traversal
- $replaceRoot — promote subdocument to top level
- $unionWith — combine pipeline results (UNION ALL)
- $vectorSearch — approximate nearest neighbor search
- $limit — restrict output document count
- $count — count documents in pipeline
- $currentOp — admin operation metadata
- $search — text search stage

Each doc follows the existing format: YAML frontmatter, syntax block,
parameters table, examples, and key takeaways. Validated against the
C source in bson_aggregation_pipeline.c for accuracy.

Signed-off-by: richardsimmonds <richardsimmonds314@gmail.com>
@guanzhousongmicrosoft

Copy link
Copy Markdown
Contributor

Hi, @richardsimmonds Thanks for the PR, I have reviewed and commented. Please take a look, and let me know.

@guanzhousongmicrosoft guanzhousongmicrosoft 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.

Thanks for this — the gap it targets is real and the scaffolding is right: all 10 stages exist, filenames and frontmatter match the existing 35 pages, and several tables are exact against the source ($graphLookup's parameters, $unionWith's limitations, the 30-operator $setWindowFields list).

Six inline comments below, one per file. Two need a decision before the rest is worth doing:

  1. $search.md — it documents Atlas full-text search; DocumentDB's $search is vector-only. Not fixable by editing lines. Drop it, or rewrite around cosmosSearch.
  2. $count.md — duplicates the pre-existing accumulators/$count.md. Drop the new page, or relocate the old one?

The pattern worth knowing

The PR body says validation was done by confirming each stage is registered in StageDefinitions[]. That's true, and it's also the depth at which this PR is accurate — that table gives a name and a .mutateFunc pointer, and says nothing about what the stage accepts. Everything correct here is readable from a flat table or greppable as an error string; everything wrong needs you to follow the function pointer into the parse function.

The wrong claims are also consistently accurate for MongoDB/Atlas — Atlas Search is real, index really is required in Atlas's $vectorSearch, and the $count$group equivalence is MongoDB's documented behavior.

So these six comments understate the work: I could only flag divergences I could prove. For each page, it's worth re-deriving the parameter table and limitations from the stage's parse function rather than the MongoDB page.

Separately, the wrong output blocks and the 5 vs 41501 count need the queries actually run against the canonical stores dataset — that's the root cause of that whole class of error.

Caveat: my line numbers come from a 0.117-0 checkout while the docs target v0.114-0. The claims hold at the v0.114-0 tag, but bson_aggregation_search.c and bson_aggregation_window_operators.c moved — search by symbol, not line. Where a comment quotes an ERROR: from expected/*.out, that's recorded output; where it says "source read", verify before rewriting.

Comment thread api-reference/operators/aggregation/$search.md Outdated
Comment thread api-reference/operators/aggregation/$vectorsearch.md Outdated
@@ -0,0 +1,102 @@
---
title: $count

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.

Three issues — the first may make the rest moot

This page duplicates accumulators/$count.md (392 lines), which already documents the $count stage, including the same $unwind + $count example. It's the only duplicate title: in the repo, and with no navigation.yml both pages will surface. They also contradict each other on collection size — 5 here vs 41501 there, corroborated by $collstats.md:164 and $bucket.md:176. → Maintainer call: drop this page, or relocate the existing one.

Line 93 — Example 3 unwinds promotionEvents, which is absent from this page's own sample document (lines 31-47). Fix the sample, not the query. Same defect at $project.md:93.

Line 101 — the $group equivalence is false on empty input: $count builds an ungrouped aggregate (always one row), $group a real GROUP BY (zero rows). This contradicts line 100, which is the correct one. Source read.

Comment thread api-reference/operators/aggregation/$setwindowfields.md Outdated
Comment thread api-reference/operators/aggregation/$project.md Outdated
Comment thread api-reference/operators/aggregation/$graphlookup.md Outdated
@documentdb documentdb deleted a comment from GuanzhouSong Aug 3, 2026
@documentdb documentdb deleted a comment from GuanzhouSong Aug 3, 2026
@documentdb documentdb deleted a comment from GuanzhouSong Aug 3, 2026
@documentdb documentdb deleted a comment from GuanzhouSong Aug 3, 2026
@documentdb documentdb deleted a comment from GuanzhouSong Aug 3, 2026
@documentdb documentdb deleted a comment from GuanzhouSong Aug 3, 2026
Rebasing this branch onto main will conflict: $graphLookup, $limit,
$project, and $vectorSearch were all documented in documentdb#59 while this pull
request was open, so those four files are add/add conflicts against
pages that already exist. Removed them here. The remaining six stages
are still missing from the reference and are what this branch adds.

$search needed more than a rebase - it documents the wrong feature. The
page describes full-text search over a text index, with an example
built on a text operator:

  $search: { text: { query: "Beverage", path: "name" } }

DocumentDB's $search is a vector search stage. Its operator registry has
exactly two entries, cosmosSearch and knnBeta (deprecated), both vector
operators, and there is no text operator anywhere in the search sources.
The example above does not return no results - it fails outright with
"Unrecognized $search option: text", and the page's stated requirement
of a text index points at the wrong index type entirely.

Rewritten against the stage's actual parsing: the two operators, the
options carried alongside them (index, count, returnStoredSource), and
the operator spec itself (path, vector, k, filter, exact, oversampling,
score), including which of those knnBeta rejects and which require
server-side pre-filtering to be enabled. count is documented as having
no observable effect, since the engine parses it but does not yet emit
the metadata. The page now points at $vectorSearch as the stage to
prefer for new queries, and notes $text as the answer for the full-text
case the original page was reaching for.

The other five pages were checked against the engine and left alone.
$currentOp in particular is accurate: its five options are real, and its
first-stage and admin-database requirements match the handler.
@guanzhousongmicrosoft

Copy link
Copy Markdown
Contributor

@richardsimmonds I pushed a commit to this branch rather than leaving you a list to work through — hope that is alright, and revert it freely if you would rather take it a different way.

Two things:

Four files removed. $graphLookup, $limit, $project, and $vectorSearch were documented in #59 while this pull request was open, so they are now add/add conflicts against pages that already exist on main. Dropping them clears the conflicts. The six stages that remain — $count, $currentOp, $replaceRoot, $search, $setWindowFields, $unionWith — are still missing from the reference, which is the useful part of this branch.

$search was documenting the wrong feature, and this one is worth explaining because nothing on the page looks wrong from the outside. It described full-text search over a text index:

$search: { text: { query: "Beverage", path: "name" } }

DocumentDB's $search is a vector search stage. Its operator registry has exactly two entries, and both are vector operators:

static const DocumentDBSearchOperatorDef SearchOperatorsList[] = {
    { .operatorName = "cosmosSearch", ... },
    { .operatorName = "knnBeta",  /* deprecated operator, kept for backward compatibility */ ... },
};

There is no text operator anywhere in the search sources, so that example does not return an empty result — it fails with Unrecognized $search option: text. The "requires a text index" note points at the wrong index type for the same reason.

I rewrote it against the stage's actual parsing: the two operators, the options carried alongside them (index, count, returnStoredSource), and the operator spec (path, vector, k, filter, exact, oversampling, score), including which fields knnBeta rejects and which need server-side pre-filtering enabled. count is noted as having no observable effect, since the engine parses it but does not yet emit the metadata. The page now points at $vectorSearch for new queries and at $text for the full-text case the original was reaching for.

The other five pages I checked against the engine and left alone. $currentOp is accurate down to the detail — all five options are real, and the first-stage and admin-database requirements match the handler exactly.

The branch merges cleanly with main now. Thanks for the contribution, and sorry it sat as long as it did.

Two review comments that were fixable from the source.

$setWindowFields showed a syntax template with no valid instantiation:
documents, range, and unit all appear inside one window block, and that
combination is rejected. EnsureValidWindowSpec accepts either documents
alone, or range with an optional unit, and nothing else - documents and
range together throw "Window bounds may only define either 'documents'
or 'unit', but never both", and a window carrying neither throws a
message naming the two valid shapes. The template now shows the choice,
and both errors are quoted so a reader who hits one can map it back.

The sortBy row said "Optional" without qualification, which is true of
the parser and misleading everywhere else. EnsureSortRequirements
requires exactly one ascending sort field for a range window - so the
-1 the row endorsed cannot be used with one - and requires a sort field
for any bounded document window, omissible only when the window is
unbounded at both ends. Several operators require their own, some
non-compound, and $shift requires one unconditionally. Added a table
covering the three cases with the error each raises.

$count claimed equivalence with $group plus $project. The two agree
whenever a document reaches the stage, and diverge on empty input:
HandleCountCore sets hasAggs without a groupClause, making an ungrouped
aggregate that always produces a row, so $count returns zero where
$group returns nothing. That distinction is the reason to reach for
$count, so it is now stated rather than flattened into an equivalence -
and it no longer contradicts the bullet directly above it.
@guanzhousongmicrosoft

Copy link
Copy Markdown
Contributor

Pushed a second commit addressing two more of the review threads. Status of all six, so nothing is left ambiguous:

Resolved

  • $search — rewritten around cosmosSearch / knnBeta, vector index rather than text index, with $text named for the full-text case. One sub-point I closed by deletion rather than correction: the $meta: "searchScore" example is gone rather than rewritten, so the score is no longer described wrongly, but it is not described at all either.
  • $vectorSearch, $project, $graphLookup — files removed, since Document $vectorSearch, $graphLookup, $project, and $limit #59 documented all three while this was open. Worth noting the merged pages already answer what the comments raised: $vectorSearch does not list index as required, marks numCandidates as bounded and ignored on vector-ivf, and states both the per-field-index and sharded-collection limits on filter; $project has the _id rule right in both directions.
  • $setWindowFields — the syntax template now shows documents and range/unit as the alternatives they are, with both parse errors quoted. The sortBy row no longer says "Optional" unqualified: EnsureSortRequirements requires a single ascending sort field for a range window (so the -1 that row endorsed cannot be used with one), requires a sort field for any bounded documents window, and several operators require their own — added as a table with the error each raises.
  • $count, the $group equivalence — corrected rather than deleted. HandleCountCore sets hasAggs without a groupClause, so it is an ungrouped aggregate that always yields a row: $count returns 0 on empty input where $group returns nothing. That is the reason to reach for $count, so the page now says so, and it no longer contradicts the bullet above it.

Still open — two, and neither is mine to close

  • $count duplicating accumulators/$count.md. Both files carry title: $count and the existing page already documents the stage, including the same $unwind example. Maintainer call: drop the new page, or relocate the old one. It may moot the other $count comments, which is why it is worth deciding first.
  • The output blocks. $replaceroot.md:62 ends in { $limit: 2 } and shows one document, the same at :90, and $count.md's Example 3 unwinds promotionEvents, which does not appear in that page's own sample document. These need the queries actually run against the canonical stores dataset, exactly as the review said — they cannot be settled by reading the source, and guessing at plausible-looking output is how they went wrong in the first place. @richardsimmonds, if you still have the environment you drafted these against, that is the fastest path; otherwise the numbers may be better dropped from the examples than left wrong.

guanzhousongmicrosoft pushed a commit that referenced this pull request Aug 3, 2026
Adds reference pages for $currentOp, $replaceRoot, $search, $setWindowFields, and $unionWith, which had none.

Continues the work in #47. The claims on these pages are derived from the
engine source rather than from MongoDB's documentation, which is where the
original divergences came from: $search documented Atlas full-text search
where DocumentDB's stage is vector-only, and $setWindowFields showed a
window template that the parser rejects. $count was dropped as a duplicate
of accumulators/$count.md, and $graphLookup, $limit, $project, and
$vectorSearch were dropped after #59 documented them.

Co-authored-by: richardsimmonds <richardsimmonds314@gmail.com>
@guanzhousongmicrosoft

Copy link
Copy Markdown
Contributor

@richardsimmonds this is now merged as #64, and I am closing this one — with thanks, because the gap it identified was real and six stages that had no reference page now have one.

To be straight about what happened rather than leave you to reconstruct it from a diff: I could not push the last round of changes to this branch from the account that needed to be the final pusher, so #64 carries the work forward from a branch I own. Your commit is in its history and you are credited as co-author on the merge. That is a workflow limitation on our side, not a comment on the contribution.

What changed along the way, all explained in the comments above:

  • $graphLookup, $limit, $project, and $vectorSearch were dropped — Document $vectorSearch, $graphLookup, $project, and $limit #59 documented them while this was open.
  • $search was rewritten. It documented Atlas-style full-text search; DocumentDB's $search is vector-only, carrying cosmosSearch or the deprecated knnBeta.
  • $setWindowFields had its window template and sortBy requirements corrected against the parser.
  • $count was dropped as a duplicate of accumulators/$count.md.
  • The $replaceRoot examples were made self-consistent with their own output.

One thing worth passing on, since it explains nearly every correction: the validation approach in the original description — confirming each stage is registered in StageDefinitions[] — establishes that a stage exists, but that table gives only a name and a function pointer. What a stage accepts lives behind that pointer, in the parse function. Everything that was wrong here was accurate for MongoDB or Atlas, which is exactly the failure mode that reading the parse function catches.

Contributions to this reference are genuinely welcome, and this one made it better. Sorry it took two months to get looked at.

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