Skip to content

feat(firestore): refactor pipeline API for uniform stage options#14322

Merged
bhshkh merged 11 commits into
googleapis:mainfrom
bhshkh:feat/fspq-stage-options
Apr 6, 2026
Merged

feat(firestore): refactor pipeline API for uniform stage options#14322
bhshkh merged 11 commits into
googleapis:mainfrom
bhshkh:feat/fspq-stage-options

Conversation

@bhshkh
Copy link
Copy Markdown
Contributor

@bhshkh bhshkh commented Apr 1, 2026

Design decision here

Overview
This PR standardizes the API surface for the Firestore Pipeline builder in the Go SDK, ensuring idiomatic conformance and aligning closely with cross-platform design paradigms (Java/Node.js). It replaces complex stage-specific configuration structs with a universal functional option pattern and introduces typed slice constants for varied expressions.

Key Features and API Changes:

  • Universal RawOptions Escape Hatch:
    Introduced RawOptions to serve as a universal pass-through for backend options that might not be natively modeled by the Go SDK (e.g., RawOptions{"limit": 5, "distance_field": "dist"}). This deprecates intermediate parameter structs like PipelineFindNearestOptions across pipeline stages.

  • Standardized Variadic Arguments with Slices:
    Methods that formerly relied on raw variadic argument unpacking (e.g. ...Ordering, ...any) have been updated to accept typed slices. To preserve clean query ergonomics, we introduced domain-specific helper constructors (Orders(), Fields(), Accumulators(), Selectables()).

    • Pipeline.Sort(Orders(Ascending(FieldOf("f")), Descending(FieldOf("__name__"))))
    • Pipeline.Aggregate(Accumulators(Count(DocumentID).As("total")))
    • Pipeline.Select(Fields("a", "b"))
  • Functional Stage Options Migration:
    Implemented standard functional options for specialized stage modifiers:

    • WithFindNearestLimit(), WithFindNearestDistanceField()
    • WithAggregateGroups()
    • WithUnnestIndexField()
    • WithExplainMode()

Internal Code Simplification & Testing:

  • Flattened Stage Hierarchy (baseStage removal):
    Removed the baseStage struct embedding across our internal pipeline stages. We replaced it with explicit options map[string]any fields within each stage and introduced a shared stageOptionsToProto(options) helper. This simplifies the internal struct hierarchy, removes opaque state inherited from struct embedding, and makes configuration serialization significantly more explicit and easier to trace inside each stage's toProto() implementation.
  • Pipeline Stage Proto Parsing:
    Refactored internal pipelineStage structures (newAggregateStage, newFindNearestStage, newSortStage, etc.) to reliably map the new RawOptions down to standard *pb.Value payloads.
  • Tests for AlwaysUseImplicitOrderBy:
    Added dedicated test cases (TestQuery_AlwaysUseImplicitOrderBy) to explicitly verify the behavior of the existing WithAlwaysUseImplicitOrderBy client toggle, ensuring it appropriately serializes implicit inequality fields and __name__ into the OrderBy proto requests.
  • Integration Suite Migration:
    Fully migrated the integration suite inside pipeline_integration_test.go and pipeline_test.go to assert against the newly stabilized RawOptions and slice-builder interfaces.

@bhshkh bhshkh requested review from a team as code owners April 1, 2026 16:17
@product-auto-label product-auto-label Bot added the api: firestore Issues related to the Firestore API. label Apr 1, 2026
@bhshkh bhshkh enabled auto-merge (squash) April 1, 2026 16:19
@bhshkh bhshkh disabled auto-merge April 1, 2026 16:19
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Firestore Pipeline API by replacing specific settings structs with a unified options pattern using map[string]any and the StageOption interface. The changes involve updating method signatures for various pipeline stages and adding ergonomic helper functions like Fields and Accumulators. Review feedback highlights concerns regarding the integration tests, specifically the skipping of failing functional tests and the presence of commented-out test cases for features like MapKeys and ArrayFilter. These issues should be addressed to ensure the refactoring does not introduce regressions or leave the codebase with undocumented gaps in feature support.

Comment thread firestore/pipeline_integration_test.go Outdated
Comment thread firestore/pipeline_integration_test.go Outdated
Comment thread firestore/pipeline_integration_test.go Outdated
Comment thread firestore/pipeline_integration_test.go Outdated
Comment thread firestore/pipeline_integration_test.go
Copy link
Copy Markdown
Contributor

@daniel-sanche daniel-sanche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread firestore/pipeline.go
eo.RawOptions[k] = v
}
})
type RawOptions map[string]any
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.

I don't have a good sense of how well defined the keys are for the general options mechanism. My read here is that the map is used to invalidate a previous option with the same key, as opposed to other option patterns where its just a list that allows potential multiple modifications. Is that the driving factor here?

Do we have any problems with key conflicts across these disparate option interfaces? If that's a property we want do we need a test that ensures all defined options are uniquely keyed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Is that the driving factor here ?
    Yes, exactly. The driving factor for using a map[string]any is that it directly mirrors the underlying Firestore Protocol Buffer definition.

    In the protobuf, a pipeline stage is defined as:

        message Stage {
          string name = 1;
          repeated Value args = 2;
          map<string, Value> options = 3; // <-- The backend expects a map
        }
    

    Because the backend enforces unique string keys for options per stage, the Go SDK uses a map to naturally accumulate them. When a user passes multiple options (e.g.,
    pipeline.Collection("users", opt1, opt2)), the SDK iterates through them and applies them to a single map. This gives us standard Go "last-writer-wins" semantics: if opt2 writes to the same
    key as opt1, it intentionally overrides it.

  2. Do we have any problems with key conflicts across disparate option interfaces?
    No, because the map is scoped per stage instance. There is no global options map. When a user creates a stage, a brand-new, empty map is instantiated exclusively for that specific stage:

     func (ps *PipelineSource) Collection(path string, opts ...CollectionOption) *Pipeline {
         options := make(map[string]any) // <-- Brand new map scoped only to this stage
         for _, opt := range opts {
             if opt != nil {
                 opt.applyStage(options)
             }
         }
         // ...
    

    Therefore, if FindNearest uses the key "limit" and a theoretical future Sort option also uses the key "limit", they will never conflict because their options are written to completely isolated maps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a test for unique keys per stage

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 the explanations!

@bhshkh
Copy link
Copy Markdown
Contributor Author

bhshkh commented Apr 3, 2026

update and delete stages were added while the current PR was in review. Refactored those to match the rest of the stages and their options.

Also, added a test for unique keys.

Comment thread firestore/pipeline.go Outdated
Comment thread firestore/pipeline.go Outdated
Comment thread firestore/pipeline_source.go Outdated
Comment thread firestore/transaction_test.go Outdated
bhshkh and others added 4 commits April 3, 2026 16:43
Co-authored-by: Alex Hong <9397363+hongalex@users.noreply.github.com>
Co-authored-by: Alex Hong <9397363+hongalex@users.noreply.github.com>
Co-authored-by: Alex Hong <9397363+hongalex@users.noreply.github.com>
@bhshkh bhshkh enabled auto-merge (squash) April 3, 2026 23:48
@bhshkh bhshkh merged commit ca7c369 into googleapis:main Apr 6, 2026
11 checks passed
@bhshkh bhshkh deleted the feat/fspq-stage-options branch April 6, 2026 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: firestore Issues related to the Firestore API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants