Repository: googleapis/google-cloud-ruby (Ruby)

Issue:
Firestore: unexpected results when combining `start_at` or `end_at` with `limit_to_last` in queries
When combining `start_{at,after}` or `end_{at,before}` with `last_to_limit` in a Firestore query, `start_at` and `end_at` are not applied properly, returning unexpected results.

#### Environment details

  - OS: macOS 14.6.1
  - Ruby version: 3.3.4
  - Gem name and version: google-cloud-firestore 2.16.0

#### Steps to reproduce

  1. Create documents in the `data` collection with an `id` field
       ```ruby
        require "google/cloud/firestore"

        firestore = Google::Cloud::Firestore.new

        10.times do |i|
          doc = firestore.collection("data").doc
          doc.set({ id: i })
        end
       ```
  3. Run the sample code using `end_before` and `limit_to_last`
  4. An empty array is returned

#### Code example

```ruby
# example
require 'google/cloud/firestore'

firestore = Google::Cloud::Firestore.new

col = firestore.col 'data'

query = col.order(:id, :asc).end_before(10).limit_to_last(5)

pp query

docs = query.get

pp docs.map(&:data)
```

#### Full backtrace

```sh
❯ GOOGLE_CLOUD_PROJECT=PROJECT_ID ruby sample.rb
#<Google::Cloud::Firestore::Query:0x0000000121db92b0
 @client=#<Google::Cloud::Firestore::Client:0x00000001220530e8 @service=Google::Cloud::Firestore::Service(PROJECT_ID)((default))>,
 @limit_type=:last,
 @parent_path="projects/PROJECT_ID/databases/(default)/documents",
 @query=
  <Google::Cloud::Firestore::V1::StructuredQuery: from: [<Google::Cloud::Firestore::V1::StructuredQuery::CollectionSelector: collection_id: "data", all_descendants: false>], order_by: [<Google::Cloud::Firestore::V1::StructuredQuery::Order: field: <Google::Cloud::Firestore::V1::StructuredQuery::FieldReference: field_path: "id">, direction: :DESCENDING>], start_at: <Google::Cloud::Firestore::V1::Cursor: values: [<Google::Cloud::Firestore::V1::Value: integer_value: 10>], before: false>, end_at: <Google::Cloud::Firestore::V1::Cursor: values: [<Google::Cloud::Firestore::V1::Value: integer_value: 10>], before: true>, offset: 0, limit: <Google::Protobuf::Int32Value: value: 5>>>
[]
```

#### Expected result

```
[{:id=>5}, {:id=>6}, {:id=>7}, {:id=>8}, {:id=>9}]
```

Making sure to follow these steps will guarantee the quickest resolution possible.

Thanks!



Discussion:

Patch:
diff --git a/google-cloud-firestore/lib/google/cloud/firestore/query.rb b/google-cloud-firestore/lib/google/cloud/firestore/query.rb
index f881540fc815..9e0fd6f2958d 100644
--- a/google-cloud-firestore/lib/google/cloud/firestore/query.rb
+++ b/google-cloud-firestore/lib/google/cloud/firestore/query.rb
@@ -493,12 +493,12 @@ def limit_to_last num
             new_start_at = new_query.end_at.dup
             if new_end_at
               new_end_at.before = !new_end_at.before
-              new_query.end_at = new_end_at
             end
             if new_start_at
               new_start_at.before = !new_start_at.before
-              new_query.start_at = new_start_at
             end
+            new_query.end_at = new_end_at
+            new_query.start_at = new_start_at
           end
 
           new_query.limit = Google::Protobuf::Int32Value.new value: num


Test patch:
diff --git a/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb b/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb
index c77989428139..7cd652114740 100644
--- a/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb
+++ b/google-cloud-firestore/test/google/cloud/firestore/collection_reference/query_test.rb
@@ -154,6 +154,46 @@
     assert_results_enum results_enum
   end
 
+  it "runs a query with order, start_at, and limit_to_last" do
+    expected_query = Google::Cloud::Firestore::V1::StructuredQuery.new(
+      end_at: Google::Cloud::Firestore::V1::Cursor.new(values: [Google::Cloud::Firestore::Convert.raw_to_value("foo")], before: false),
+      from: [Google::Cloud::Firestore::V1::StructuredQuery::CollectionSelector.new(collection_id: "messages")],
+      order_by: [
+        Google::Cloud::Firestore::V1::StructuredQuery::Order.new(
+          field: Google::Cloud::Firestore::V1::StructuredQuery::FieldReference.new(field_path: "name"),
+          direction: :DESCENDING),
+        Google::Cloud::Firestore::V1::StructuredQuery::Order.new(
+          field: Google::Cloud::Firestore::V1::StructuredQuery::FieldReference.new(field_path: "__name__"),
+          direction: :ASCENDING)],
+      limit: Google::Protobuf::Int32Value.new(value: 2)
+    )
+    firestore_mock.expect :run_query, query_results_descending_enum, run_query_args(expected_query, parent: collection.parent_path)
+
+    results_enum = collection.order(:name).order(firestore.document_id, :desc).start_at(:foo).limit_to_last(2).get
+
+    assert_results_enum results_enum
+  end
+
+  it "runs a query with order, end_at, and limit_to_last" do
+    expected_query = Google::Cloud::Firestore::V1::StructuredQuery.new(
+      start_at: Google::Cloud::Firestore::V1::Cursor.new(values: [Google::Cloud::Firestore::Convert.raw_to_value("bar")], before: true),
+      from: [Google::Cloud::Firestore::V1::StructuredQuery::CollectionSelector.new(collection_id: "messages")],
+      order_by: [
+        Google::Cloud::Firestore::V1::StructuredQuery::Order.new(
+          field: Google::Cloud::Firestore::V1::StructuredQuery::FieldReference.new(field_path: "name"),
+          direction: :DESCENDING),
+        Google::Cloud::Firestore::V1::StructuredQuery::Order.new(
+          field: Google::Cloud::Firestore::V1::StructuredQuery::FieldReference.new(field_path: "__name__"),
+          direction: :ASCENDING)],
+      limit: Google::Protobuf::Int32Value.new(value: 2)
+    )
+    firestore_mock.expect :run_query, query_results_descending_enum, run_query_args(expected_query, parent: collection.parent_path)
+
+    results_enum = collection.order(:name).order(firestore.document_id, :desc).end_at(:bar).limit_to_last(2).get
+
+    assert_results_enum results_enum
+  end
+
   it "updates limit but does not reflip cursors when calling limit_to_last more than once" do
     expected_query = Google::Cloud::Firestore::V1::StructuredQuery.new(
       start_at: Google::Cloud::Firestore::V1::Cursor.new(values: [Google::Cloud::Firestore::Convert.raw_to_value("bar")], before: true),