Commit d927755
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
0 commit comments