google-cloud-firestore 2.6.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/EMULATOR.md CHANGED
@@ -2,19 +2,21 @@
2
2
 
3
3
  To develop and test your application locally, you can use the [Google Cloud
4
4
  Firestore
5
- Emulator](https://cloud.google.com/firestore/docs/security/test-rules-emulator#install_the_emulator),
6
- which provides local emulation of the production Google Cloud Firestore
7
- environment. You can start the Google Cloud Firestore emulator using the
8
- [`firebase` command-line tool](https://firebase.google.com/docs/cli/).
5
+ Emulator](https://cloud.google.com/sdk/gcloud/reference/beta/emulators/firestore/),
6
+ which provides local emulation of the production Google Cloud Firestore
7
+ environment. You can start the Google Cloud Firestore emulator using
8
+ the `gcloud` command-line tool.
9
+
10
+ `gcloud beta emulators firestore start --host-port=0.0.0.0:8080`
9
11
 
10
12
  When you run the Cloud Firestore emulator you will see a message similar to the
11
13
  following printed:
12
14
 
13
15
  ```
14
- $ firebase serve --only firestore
15
- API endpoint: http://[::1]:8080
16
- API endpoint: http://127.0.0.1:8080
17
- Dev App Server is now running.
16
+ If you are using a library that supports the FIRESTORE_EMULATOR_HOST
17
+ environment variable, run:
18
+
19
+ export FIRESTORE_EMULATOR_HOST=localhost:8080
18
20
  ```
19
21
 
20
22
  Now you can connect to the emulator using the `FIRESTORE_EMULATOR_HOST`
data/LOGGING.md CHANGED
@@ -3,7 +3,7 @@
3
3
  To enable logging for this library, set the logger for the underlying
4
4
  [gRPC](https://github.com/grpc/grpc/tree/master/src/ruby) library. The logger
5
5
  that you set may be a Ruby stdlib
6
- [`Logger`](https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html) as
6
+ [`Logger`](https://ruby-doc.org/current/stdlibs/logger/Logger.html) as
7
7
  shown below, or a
8
8
  [`Google::Cloud::Logging::Logger`](https://googleapis.dev/ruby/google-cloud-logging/latest)
9
9
  that will write logs to [Stackdriver
data/OVERVIEW.md CHANGED
@@ -108,7 +108,7 @@ and it can't contain other collections. You do not need to "create" or "delete"
108
108
  collections. After you create the first document in a collection, the collection
109
109
  exists. If you delete all of the documents in a collection, it no longer exists.
110
110
  (For more information, see [Cloud Firestore Data
111
- Model](https://cloud.google.com/firestore/docs/data-model).
111
+ Model](https://cloud.google.com/firestore/docs/data-model).)
112
112
 
113
113
  Use {Google::Cloud::Firestore::Client#cols Client#cols} to list the root-level
114
114
  collections:
@@ -0,0 +1,202 @@
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "google/cloud/firestore/v1"
16
+ require "google/cloud/firestore/aggregate_query_snapshot"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Firestore
21
+ ##
22
+ # # AggregateQuery
23
+ #
24
+ # An aggregate query can be used to fetch aggregate values (ex: count) for a query
25
+ #
26
+ # Instances of this class are immutable. All methods that refine the aggregate query
27
+ # return new instances.
28
+ #
29
+ # @example
30
+ # require "google/cloud/firestore"
31
+ #
32
+ # firestore = Google::Cloud::Firestore.new
33
+ #
34
+ # query = firestore.col "cities"
35
+ #
36
+ # # Create an aggregate query
37
+ # aggregate_query = query.aggregate_query
38
+ # .add_count
39
+ #
40
+ # aggregate_query.get do |aggregate_snapshot|
41
+ # puts aggregate_snapshot.get
42
+ # end
43
+ #
44
+ # @example Alias an aggregate query
45
+ # require "google/cloud/firestore"
46
+ #
47
+ # firestore = Google::Cloud::Firestore.new
48
+ #
49
+ # # Create a query
50
+ # query = firestore.col "cities"
51
+ #
52
+ # # Create an aggregate query
53
+ # aggregate_query = query.aggregate_query
54
+ # .add_count aggregate_alias: 'total_cities'
55
+ #
56
+ # aggregate_query.get do |aggregate_snapshot|
57
+ # puts aggregate_snapshot.get('total_cities')
58
+ # end
59
+ #
60
+ class AggregateQuery
61
+ ##
62
+ # @private The firestore client object.
63
+ attr_accessor :client
64
+
65
+ ##
66
+ # @private The type for limit queries.
67
+ attr_reader :parent_path
68
+
69
+ ##
70
+ # @private The Google::Cloud::Firestore::V1::StructuredQuery object.
71
+ attr_reader :query
72
+
73
+ ##
74
+ # @private Array of Google::Cloud::Firestore::V1::StructuredAggregationQuery::Aggregation objects
75
+ attr_reader :aggregates
76
+
77
+ ##
78
+ # @private Creates a new AggregateQuery
79
+ def initialize query, parent_path, client, aggregates: []
80
+ @query = query
81
+ @parent_path = parent_path
82
+ @aggregates = aggregates
83
+ @client = client
84
+ end
85
+
86
+ ##
87
+ # Adds a count aggregate.
88
+ #
89
+ # @param [aggregate_alias] Alias to refer to the aggregate. Optional
90
+ #
91
+ # @return [AggregateQuery] A new aggregate query with the added count aggregate.
92
+ #
93
+ # @example
94
+ # require "google/cloud/firestore"
95
+ #
96
+ # firestore = Google::Cloud::Firestore.new
97
+ #
98
+ # query = firestore.col "cities"
99
+ #
100
+ # # Create an aggregate query
101
+ # aggregate_query = query.aggregate_query
102
+ # .add_count
103
+ #
104
+ # aggregate_query.get do |aggregate_snapshot|
105
+ # puts aggregate_snapshot.get
106
+ # end
107
+ #
108
+ def add_count aggregate_alias: nil
109
+ aggregate_alias ||= ALIASES[:count]
110
+ new_aggregates = @aggregates.dup
111
+ new_aggregates << StructuredAggregationQuery::Aggregation.new(
112
+ count: StructuredAggregationQuery::Aggregation::Count.new,
113
+ alias: aggregate_alias
114
+ )
115
+ AggregateQuery.start query, new_aggregates, parent_path, client
116
+ end
117
+
118
+ ##
119
+ # Retrieves aggregate snapshot for the query.
120
+ #
121
+ # @yield [snapshot] The block for accessing the aggregate query snapshots.
122
+ # @yieldparam [AggregateQuerySnapshot] An aggregate query snapshot.
123
+ #
124
+ # @return [Enumerator<AggregateQuerySnapshot>] A list of aggregate query snapshots.
125
+ #
126
+ # @example
127
+ # require "google/cloud/firestore"
128
+ #
129
+ # firestore = Google::Cloud::Firestore.new
130
+ #
131
+ # query = firestore.col "cities"
132
+ #
133
+ # # Create an aggregate query
134
+ # aggregate_query = query.aggregate_query
135
+ # .add_count
136
+ #
137
+ # aggregate_query.get do |aggregate_snapshot|
138
+ # puts aggregate_snapshot.get
139
+ # end
140
+ #
141
+ def get
142
+ ensure_service!
143
+
144
+ return enum_for :get unless block_given?
145
+
146
+ responses = service.run_aggregate_query @parent_path, structured_aggregation_query
147
+ responses.each do |response|
148
+ next if response.result.nil?
149
+ yield AggregateQuerySnapshot.from_run_aggregate_query_response response
150
+ end
151
+ end
152
+
153
+ ##
154
+ # @private Creates a Google::Cloud::Firestore::V1::StructuredAggregationQuery object
155
+ def structured_aggregation_query
156
+ StructuredAggregationQuery.new(
157
+ structured_query: @query,
158
+ aggregations: @aggregates
159
+ )
160
+ end
161
+
162
+ ##
163
+ # @private Start a new AggregateQuery.
164
+ def self.start query, aggregates, parent_path, client
165
+ new query, parent_path, client, aggregates: aggregates
166
+ end
167
+
168
+ protected
169
+
170
+ ##
171
+ # @private
172
+ StructuredAggregationQuery = Google::Cloud::Firestore::V1::StructuredAggregationQuery
173
+
174
+ ##
175
+ # @private
176
+ ALIASES = {
177
+ count: "count"
178
+ }.freeze
179
+
180
+ ##
181
+ # @private Raise an error unless a database is available.
182
+ def ensure_client!
183
+ raise "Must have active connection to service" unless client
184
+ end
185
+
186
+ ##
187
+ # @private Raise an error unless an active connection to the service
188
+ # is available.
189
+ def ensure_service!
190
+ raise "Must have active connection to service" unless service
191
+ end
192
+
193
+ ##
194
+ # @private The Service object.
195
+ def service
196
+ ensure_client!
197
+ client.service
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,120 @@
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module Google
17
+ module Cloud
18
+ module Firestore
19
+ ##
20
+ # # AggregateQuerySnapshot
21
+ #
22
+ # An aggregate query snapshot object is an immutable representation for
23
+ # an aggregate query result.
24
+ #
25
+ # @example
26
+ # require "google/cloud/firestore"
27
+ #
28
+ # firestore = Google::Cloud::Firestore.new
29
+ #
30
+ # query = firestore.col "cities"
31
+ #
32
+ # # Create an aggregate query
33
+ # aggregate_query = query.aggregate_query
34
+ # .add_count
35
+ #
36
+ # aggregate_query.get do |aggregate_snapshot|
37
+ # puts aggregate_snapshot.get
38
+ # end
39
+ # @return [Integer] The aggregate value.
40
+ #
41
+ # @example Alias an aggregate query
42
+ # require "google/cloud/firestore"
43
+ #
44
+ # firestore = Google::Cloud::Firestore.new
45
+ #
46
+ # query = firestore.col "cities"
47
+ #
48
+ # # Create an aggregate query
49
+ # aggregate_query = query.aggregate_query
50
+ # .add_count aggregate_alias: 'total'
51
+ #
52
+ # aggregate_query.get do |aggregate_snapshot|
53
+ # puts aggregate_snapshot.get('total')
54
+ # end
55
+ class AggregateQuerySnapshot
56
+ ##
57
+ # Retrieves the aggregate data.
58
+ #
59
+ # @param aggregate_alias [String] The alias used to access
60
+ # the aggregate value. For an AggregateQuery with a
61
+ # single aggregate field, this parameter can be omitted.
62
+ #
63
+ # @return [Integer] The aggregate value.
64
+ #
65
+ # @example
66
+ # require "google/cloud/firestore"
67
+ #
68
+ # firestore = Google::Cloud::Firestore.new
69
+ #
70
+ # query = firestore.col "cities"
71
+ #
72
+ # # Create an aggregate query
73
+ # aggregate_query = query.aggregate_query
74
+ # .add_count
75
+ #
76
+ # aggregate_query.get do |aggregate_snapshot|
77
+ # puts aggregate_snapshot.get
78
+ # end
79
+ # @return [Integer] The aggregate value.
80
+ #
81
+ # @example Alias an aggregate query
82
+ # require "google/cloud/firestore"
83
+ #
84
+ # firestore = Google::Cloud::Firestore.new
85
+ #
86
+ # query = firestore.col "cities"
87
+ #
88
+ # # Create an aggregate query
89
+ # aggregate_query = query.aggregate_query
90
+ # .add_count aggregate_alias: 'total'
91
+ #
92
+ # aggregate_query.get do |aggregate_snapshot|
93
+ # puts aggregate_snapshot.get('total')
94
+ # end
95
+ def get aggregate_alias = nil
96
+ if @aggregate_fields.count > 1 && aggregate_alias.nil?
97
+ raise ArgumentError, "Required param aggregate_alias for AggregateQuery with multiple aggregate fields"
98
+ end
99
+ aggregate_alias ||= @aggregate_fields.keys.first
100
+ @aggregate_fields[aggregate_alias]
101
+ end
102
+
103
+ ##
104
+ # @private New AggregateQuerySnapshot from a
105
+ # Google::Cloud::Firestore::V1::RunAggregationQueryResponse object.
106
+ def self.from_run_aggregate_query_response response
107
+ aggregate_fields = response
108
+ .result
109
+ .aggregate_fields
110
+ .to_h # convert from protobuf to ruby map
111
+ .transform_values { |v| v[:integer_value] }
112
+
113
+ new.tap do |s|
114
+ s.instance_variable_set :@aggregate_fields, aggregate_fields
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -69,7 +69,7 @@ module Google
69
69
  #
70
70
  # @return [String] database identifier.
71
71
  def database_id
72
- "(default)"
72
+ service.database
73
73
  end
74
74
 
75
75
  ##
@@ -85,6 +85,9 @@ module Google
85
85
  ##
86
86
  # Retrieves an enumerator for the root collections.
87
87
  #
88
+ # @param [Time] read_time Reads documents as they were at the given time.
89
+ # This may not be older than 270 seconds. Optional
90
+ #
88
91
  # @yield [collections] The block for accessing the collections.
89
92
  # @yieldparam [CollectionReference] collection A collection reference object.
90
93
  #
@@ -101,10 +104,21 @@ module Google
101
104
  # puts col.collection_id
102
105
  # end
103
106
  #
104
- def cols &block
107
+ # @example
108
+ # require "google/cloud/firestore"
109
+ #
110
+ # firestore = Google::Cloud::Firestore.new
111
+ # read_time = Time.now
112
+ #
113
+ # # Get the root collections
114
+ # firestore.cols(read_time: read_time).each do |col|
115
+ # puts col.collection_id
116
+ # end
117
+ #
118
+ def cols read_time: nil, &block
105
119
  ensure_service!
106
- grpc = service.list_collections "#{path}/documents"
107
- cols_enum = CollectionReferenceList.from_grpc(grpc, self, "#{path}/documents").all
120
+ grpc = service.list_collections "#{path}/documents", read_time: read_time
121
+ cols_enum = CollectionReferenceList.from_grpc(grpc, self, "#{path}/documents", read_time: read_time).all
108
122
  cols_enum.each(&block) if block_given?
109
123
  cols_enum
110
124
  end
@@ -164,8 +178,7 @@ module Google
164
178
  #
165
179
  def col_group collection_id
166
180
  if collection_id.include? "/"
167
- raise ArgumentError, "Invalid collection_id: '#{collection_id}', " \
168
- "must not contain '/'."
181
+ raise ArgumentError, "Invalid collection_id: '#{collection_id}', must not contain '/'."
169
182
  end
170
183
 
171
184
  CollectionGroup.from_collection_id service.documents_path, collection_id, self
@@ -218,6 +231,8 @@ module Google
218
231
  # individual fields joined by ".". Fields containing `~`, `*`, `/`,
219
232
  # `[`, `]`, and `.` cannot be in a dotted string, and should provided
220
233
  # using a {FieldPath} object instead. (See {#field_path}.)
234
+ # @param [Time] read_time Reads documents as they were at the given time.
235
+ # This may not be older than 270 seconds. Optional
221
236
  #
222
237
  # @yield [documents] The block for accessing the document snapshots.
223
238
  # @yieldparam [DocumentSnapshot] document A document snapshot.
@@ -246,11 +261,24 @@ module Google
246
261
  # puts "#{city.document_id} has #{city[:population]} residents."
247
262
  # end
248
263
  #
249
- def get_all *docs, field_mask: nil
264
+ # @example Get docs using a read_time:
265
+ # require "google/cloud/firestore"
266
+ #
267
+ # firestore = Google::Cloud::Firestore.new
268
+ #
269
+ # read_time = Time.now
270
+ #
271
+ # # Get and print city documents
272
+ # cities = ["cities/NYC", "cities/SF", "cities/LA"]
273
+ # firestore.get_all(cities, read_time: read_time).each do |city|
274
+ # puts "#{city.document_id} has #{city[:population]} residents."
275
+ # end
276
+ #
277
+ def get_all *docs, field_mask: nil, read_time: nil
250
278
  ensure_service!
251
279
 
252
280
  unless block_given?
253
- return enum_for :get_all, *docs, field_mask: field_mask
281
+ return enum_for :get_all, *docs, field_mask: field_mask, read_time: read_time
254
282
  end
255
283
 
256
284
  doc_paths = Array(docs).flatten.map do |doc_path|
@@ -265,7 +293,7 @@ module Google
265
293
  end
266
294
  mask = nil if mask.empty?
267
295
 
268
- results = service.get_documents doc_paths, mask: mask
296
+ results = service.get_documents doc_paths, mask: mask, read_time: read_time
269
297
  results.each do |result|
270
298
  next if result.result.nil?
271
299
  yield DocumentSnapshot.from_batch_result result, self
@@ -628,7 +656,24 @@ module Google
628
656
  commit_return = transaction.commit
629
657
  # Conditional return value, depending on truthy commit_response
630
658
  commit_response ? commit_return : transaction_return
631
- rescue Google::Cloud::UnavailableError => e
659
+ rescue Google::Cloud::AbortedError,
660
+ Google::Cloud::CanceledError,
661
+ Google::Cloud::UnknownError,
662
+ Google::Cloud::DeadlineExceededError,
663
+ Google::Cloud::InternalError,
664
+ Google::Cloud::UnauthenticatedError,
665
+ Google::Cloud::ResourceExhaustedError,
666
+ Google::Cloud::UnavailableError,
667
+ Google::Cloud::InvalidArgumentError => e
668
+
669
+ if e.instance_of? Google::Cloud::InvalidArgumentError
670
+ # Return if a previous call was retried but ultimately succeeded
671
+ return nil if backoff[:current].positive?
672
+ # The Firestore backend uses "INVALID_ARGUMENT" for transaction IDs that have expired.
673
+ # While INVALID_ARGUMENT is generally not retryable, we retry this specific case.
674
+ raise e unless e.message =~ /transaction has expired/
675
+ end
676
+
632
677
  # Re-raise if retried more than the max
633
678
  raise e if backoff[:current] > backoff[:max]
634
679
 
@@ -643,12 +688,6 @@ module Google
643
688
  transaction = Transaction.from_client \
644
689
  self, previous_transaction: transaction.transaction_id
645
690
  retry
646
- rescue Google::Cloud::InvalidArgumentError => e
647
- # Return if a previous call was retried but ultimately succeeded
648
- return nil if backoff[:current].positive?
649
-
650
- # Re-raise error.
651
- raise e
652
691
  rescue StandardError => e
653
692
  # Rollback transaction when handling unexpected error
654
693
  transaction.rollback rescue nil
@@ -658,13 +697,54 @@ module Google
658
697
  end
659
698
  end
660
699
 
700
+ ##
701
+ # Create a transaction to perform multiple reads that are
702
+ # executed atomically at a single logical point in time in a database.
703
+ #
704
+ # All changes are accumulated in memory until the block completes.
705
+ # Transactions will be automatically retried when documents change
706
+ # before the transaction is committed. See {Transaction}.
707
+ #
708
+ # @see https://firebase.google.com/docs/firestore/manage-data/transactions
709
+ # Transactions and Batched Writes
710
+ #
711
+ # @param [Time] read_time The maximum number of retries for
712
+ # transactions failed due to errors. Default is 5. Optional.
713
+ #
714
+ # @yield [transaction] The block for reading data.
715
+ # @yieldparam [Transaction] transaction The transaction object for
716
+ # making changes.
717
+ #
718
+ # @return [Object] The return value of the provided
719
+ # yield block
720
+ #
721
+ # @example Read only transaction with read time
722
+ # require "google/cloud/firestore"
723
+ #
724
+ # firestore = Google::Cloud::Firestore.new
725
+ #
726
+ # # Get a document reference
727
+ # nyc_ref = firestore.doc "cities/NYC"
728
+ #
729
+ # read_time = Time.now
730
+ #
731
+ # firestore.read_only_transaction(read_time: read_time) do |tx|
732
+ # # Get a document snapshot
733
+ # nyc_snap = tx.get nyc_ref
734
+ # end
735
+ #
736
+ def read_only_transaction read_time: nil
737
+ transaction = Transaction.from_client self, read_time: read_time, read_only: true
738
+ yield transaction
739
+ end
740
+
661
741
  # @!endgroup
662
742
 
663
743
  # @private
664
- def list_documents parent, collection_id, token: nil, max: nil
744
+ def list_documents parent, collection_id, token: nil, max: nil, read_time: nil
665
745
  ensure_service!
666
- grpc = service.list_documents parent, collection_id, token: token, max: max
667
- DocumentReference::List.from_grpc grpc, self, parent, collection_id
746
+ grpc = service.list_documents parent, collection_id, token: token, max: max, read_time: read_time
747
+ DocumentReference::List.from_grpc grpc, self, parent, collection_id, read_time: read_time
668
748
  end
669
749
 
670
750
  protected
@@ -48,6 +48,8 @@ module Google
48
48
  #
49
49
  # @param [Integer] partition_count The desired maximum number of partition points. The number must be strictly
50
50
  # positive. The actual number of partitions returned may be fewer.
51
+ # @param [Time] read_time Reads documents as they were at the given time.
52
+ # This may not be older than 270 seconds. Optional
51
53
  #
52
54
  # @return [Array<QueryPartition>] An ordered array of query partitions.
53
55
  #
@@ -62,7 +64,20 @@ module Google
62
64
  #
63
65
  # queries = partitions.map(&:to_query)
64
66
  #
65
- def partitions partition_count
67
+ # @example partition with read time
68
+ # require "google/cloud/firestore"
69
+ #
70
+ # firestore = Google::Cloud::Firestore.new
71
+ #
72
+ # col_group = firestore.col_group "cities"
73
+ #
74
+ # read_time = Time.now
75
+ #
76
+ # partitions = col_group.partitions 3, read_time: read_time
77
+ #
78
+ # queries = partitions.map(&:to_query)
79
+ #
80
+ def partitions partition_count, read_time: nil
66
81
  ensure_service!
67
82
 
68
83
  raise ArgumentError, "partition_count must be > 0" unless partition_count.positive?
@@ -75,7 +90,7 @@ module Google
75
90
 
76
91
  grpc_partitions = if partition_count.positive?
77
92
  # Retrieve all pages, since cursor order is not guaranteed and they must be sorted.
78
- list_all partition_count, query_with_default_order
93
+ list_all partition_count, query_with_default_order, read_time
79
94
  else
80
95
  [] # Ensure that a single, empty QueryPartition is returned.
81
96
  end
@@ -118,11 +133,12 @@ module Google
118
133
 
119
134
  protected
120
135
 
121
- def list_all partition_count, query_with_default_order
136
+ def list_all partition_count, query_with_default_order, read_time
122
137
  grpc_partitions = []
123
138
  token = nil
124
139
  loop do
125
- grpc = service.partition_query parent_path, query_with_default_order.query, partition_count, token: token
140
+ grpc = service.partition_query parent_path, query_with_default_order.query, partition_count,
141
+ token: token, read_time: read_time
126
142
  grpc_partitions += Array(grpc.partitions)
127
143
  token = grpc.next_page_token
128
144
  token = nil if token == ""
@@ -147,6 +147,8 @@ module Google
147
147
  # @param [String] token A previously-returned page token representing
148
148
  # part of the larger set of results to view.
149
149
  # @param [Integer] max Maximum number of results to return.
150
+ # @param [Time] read_time Reads documents as they were at the given time.
151
+ # This may not be older than 270 seconds. Optional
150
152
  #
151
153
  # @return [Array<DocumentReference>] An array of document references.
152
154
  #
@@ -161,10 +163,23 @@ module Google
161
163
  # puts doc_ref.document_id
162
164
  # end
163
165
  #
164
- def list_documents token: nil, max: nil
166
+ # @example List documents with read time
167
+ # require "google/cloud/firestore"
168
+ #
169
+ # firestore = Google::Cloud::Firestore.new
170
+ #
171
+ # read_time = Time.now
172
+ #
173
+ # col = firestore.col "cities"
174
+ #
175
+ # col.list_documents(read_time: read_time).each do |doc_ref|
176
+ # puts doc_ref.document_id
177
+ # end
178
+ #
179
+ def list_documents token: nil, max: nil, read_time: nil
165
180
  ensure_client!
166
181
  client.list_documents \
167
- parent_path, collection_id, token: token, max: max
182
+ parent_path, collection_id, token: token, max: max, read_time: read_time
168
183
  end
169
184
 
170
185
  ##
@@ -52,8 +52,8 @@ module Google
52
52
  def next
53
53
  return nil unless next?
54
54
  ensure_service!
55
- grpc = @client.service.list_collections @parent, token: token, max: @max
56
- self.class.from_grpc grpc, @client, @parent, max: @max
55
+ grpc = @client.service.list_collections @parent, token: token, max: @max, read_time: @read_time
56
+ self.class.from_grpc grpc, @client, @parent, max: @max, read_time: @read_time
57
57
  end
58
58
 
59
59
  ##
@@ -110,7 +110,7 @@ module Google
110
110
  ##
111
111
  # @private New CollectionReference::List from a `Google::Cloud::Firestore::V1::ListCollectionIdsResponse`
112
112
  # object.
113
- def self.from_grpc grpc, client, parent, max: nil
113
+ def self.from_grpc grpc, client, parent, max: nil, read_time: nil
114
114
  raise ArgumentError, "parent is required" unless parent
115
115
  cols = CollectionReferenceList.new(Array(grpc.collection_ids).map do |collection_id|
116
116
  CollectionReference.from_path "#{parent}/#{collection_id}", client
@@ -121,6 +121,7 @@ module Google
121
121
  cols.instance_variable_set :@client, client
122
122
  cols.instance_variable_set :@parent, parent
123
123
  cols.instance_variable_set :@max, max
124
+ cols.instance_variable_set :@read_time, read_time
124
125
  cols
125
126
  end
126
127