Skip to content

Commit d927755

Browse files
authored
fix(firestore): fixes for Query to Pipeline Conversion (#14422)
This PR updates the Firestore `toPipeline` logic to ensure that queries converted into pipelines follow standard Firestore query semantics. Previously, the pipeline translation was missing several critical behaviors regarding filtering, sorting, and missing-field handling. All the below gaps were identified by running the Java tests in [ITQueryToPipelineTest.java](https://github.com/googleapis/java-firestore/blob/9065134fd70da22250f038d7e964f26aebfeb3cf/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryToPipelineTest.java) and comparing the execute request payload sent by Java with the ones sent by Go equivalent pipelines. Here are the key improvements: ### 1. Correct Stage Ordering (Filters Before Sorts) We now apply the `Where` filters before the `Sort` stage. Applying sorting before filtering is inefficient and diverges from the standard Firestore execution plan, which filters the dataset as early as possible. **Example:** ```go // Before: collection -> sort("name") -> where("age" == 25) // After: collection -> where("age" == 25) -> sort("name") query := client.Collection("users").Where("age", "==", 25).OrderBy("name", firestore.Asc) pipeline := query.toPipeline() ``` ### 2. Enforcing "Missing Field" Semantics In Firestore, a query for `foo == 1` or an ordering on `foo` should implicitly exclude any document where the field `foo` is missing. We now automatically inject `FieldExists` checks into the pipeline to mirror this behavior. **Example:** * **Filter:** `Where("foo", "==", 1)` $\rightarrow$ `And(FieldExists("foo"), Equal("foo", 1))` * **Order:** `OrderBy("bar", Asc)` $\rightarrow$ `Where(FieldExists("bar")) -> Sort("bar", Asc)` ### 3. Support for Composite Filters (OR / Nested AND) The previous implementation could only handle simple top-level field filters. We've added a recursive filter translator that allows the `toPipeline` method to handle complex nested filters, including `OR` queries. **Example:** ```go // This now correctly translates to a nested 'or' function in the pipeline 'where' stage filter := firestore.OrFilter{ firestore.PropertyFilter{Path: "status", Op: "==", Value: "urgent"}, firestore.PropertyFilter{Path: "priority", Op: ">", Value: 10}, } query := client.Collection("tasks").WhereEntity(filter) ``` ### 4. Native `LimitToLast` Implementation Since the pipeline backend doesn't have a specific "limit to last" stage, we now implement this by: 1. Reversing the sorting order. 2. Applying a standard `Limit`. 3. Re-sorting back to the original requested order. **Example:** ```go // Fetch the 5 most recent items // Pipeline: Sort(timestamp DESC) -> Limit(5) -> Sort(timestamp ASC) query := client.Collection("logs").OrderBy("timestamp", firestore.Asc).LimitToLast(5) ``` ### 5. Correct Path Resolution for Subcollections We fixed an issue where subcollection queries were passing the wrong path to the `collection` stage. It now correctly resolves the relative path from the database root. **Example:** ```go // Previously, this might only pass "comments" to the pipeline. // Now it correctly passes "posts/post_123/comments". query := client.Collection("posts").Doc("post_123").Collection("comments") ``` ### 6. Accurate Cursor Boundaries (StartAt / EndAt) We've refined how multi-field cursors are translated into pipeline inequalities. - **Reference Types:** When using a document as a cursor, we now correctly pass it as a `Reference` type instead of a string ID, ensuring the backend can perform accurate comparisons. - **Strict Inequalities:** For multi-field sorts (e.g., `OrderBy("A").OrderBy("B")`), we now use strict inequalities for intermediate fields to ensure the cursor correctly "steps" through the results without including duplicates or skipping valid entries.
1 parent 06584bf commit d927755

2 files changed

Lines changed: 770 additions & 119 deletions

File tree

0 commit comments

Comments
 (0)