15
15
* limitations under the License.
16
16
*/
17
17
18
+ import { SnapshotVersion } from '../core/snapshot_version' ;
19
+ import { Timestamp } from '../lite-api/timestamp' ;
20
+ import { primitiveComparator } from '../util/misc' ;
21
+
22
+ import { Document } from './document' ;
23
+ import { DocumentKey } from './document_key' ;
18
24
import { FieldPath } from './path' ;
19
25
26
+ /**
27
+ * The initial mutation batch id for each index. Gets updated during index
28
+ * backfill.
29
+ */
30
+ const INITIAL_LARGEST_BATCH_ID = - 1 ;
31
+
32
+ /**
33
+ * The initial sequence number for each index. Gets updated during index
34
+ * backfill.
35
+ */
36
+ export const INITIAL_SEQUENCE_NUMBER = 0 ;
37
+
20
38
/**
21
39
* An index definition for field indexes in Firestore.
22
40
*
@@ -30,7 +48,7 @@ import { FieldPath } from './path';
30
48
*/
31
49
export class FieldIndex {
32
50
/** An ID for an index that has not yet been added to persistence. */
33
- static UNKNOWN_ID : - 1 ;
51
+ static UNKNOWN_ID = - 1 ;
34
52
35
53
constructor (
36
54
/**
@@ -41,12 +59,40 @@ export class FieldIndex {
41
59
/** The collection ID this index applies to. */
42
60
readonly collectionGroup : string ,
43
61
/** The field segments for this index. */
44
- readonly segments : Segment [ ]
62
+ readonly segments : IndexSegment [ ] ,
63
+ /** Shows how up-to-date the index is for the current user. */
64
+ readonly indexState : IndexState
45
65
) { }
46
66
}
47
67
68
+ /**
69
+ * Compares indexes by collection group and segments. Ignores update time and
70
+ * index ID.
71
+ */
72
+ export function fieldIndexSemanticComparator (
73
+ left : FieldIndex ,
74
+ right : FieldIndex
75
+ ) : number {
76
+ let cmp = primitiveComparator ( left . collectionGroup , right . collectionGroup ) ;
77
+ if ( cmp !== 0 ) {
78
+ return cmp ;
79
+ }
80
+
81
+ for (
82
+ let i = 0 ;
83
+ i < Math . min ( left . segments . length , right . segments . length ) ;
84
+ ++ i
85
+ ) {
86
+ cmp = indexSegmentComparator ( left . segments [ i ] , right . segments [ i ] ) ;
87
+ if ( cmp !== 0 ) {
88
+ return cmp ;
89
+ }
90
+ }
91
+ return primitiveComparator ( left . segments . length , right . segments . length ) ;
92
+ }
93
+
48
94
/** The type of the index, e.g. for which type of query it can be used. */
49
- export const enum Kind {
95
+ export const enum IndexKind {
50
96
/**
51
97
* Ordered index. Can be used for <, <=, ==, >=, >, !=, IN and NOT IN queries.
52
98
*/
@@ -60,11 +106,124 @@ export const enum Kind {
60
106
}
61
107
62
108
/** An index component consisting of field path and index type. */
63
- export class Segment {
109
+ export class IndexSegment {
64
110
constructor (
65
111
/** The field path of the component. */
66
112
readonly fieldPath : FieldPath ,
67
113
/** The fields sorting order. */
68
- readonly kind : Kind
114
+ readonly kind : IndexKind
69
115
) { }
70
116
}
117
+
118
+ function indexSegmentComparator (
119
+ left : IndexSegment ,
120
+ right : IndexSegment
121
+ ) : number {
122
+ const cmp = FieldPath . comparator ( left . fieldPath , right . fieldPath ) ;
123
+ if ( cmp !== 0 ) {
124
+ return cmp ;
125
+ }
126
+ return primitiveComparator ( left . kind , right . kind ) ;
127
+ }
128
+
129
+ /**
130
+ * Stores the "high water mark" that indicates how updated the Index is for the
131
+ * current user.
132
+ */
133
+ export class IndexState {
134
+ constructor (
135
+ /**
136
+ * Indicates when the index was last updated (relative to other indexes).
137
+ */
138
+ readonly sequenceNumber : number ,
139
+ /** The the latest indexed read time, document and batch id. */
140
+ readonly offset : IndexOffset
141
+ ) { }
142
+
143
+ /** The state of an index that has not yet been backfilled. */
144
+ static empty ( ) : IndexState {
145
+ return new IndexState ( INITIAL_SEQUENCE_NUMBER , IndexOffset . min ( ) ) ;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Creates an offset that matches all documents with a read time higher than
151
+ * `readTime`.
152
+ */
153
+ export function newIndexOffsetSuccessorFromReadTime (
154
+ readTime : SnapshotVersion ,
155
+ largestBatchId : number
156
+ ) : IndexOffset {
157
+ // We want to create an offset that matches all documents with a read time
158
+ // greater than the provided read time. To do so, we technically need to
159
+ // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
160
+ // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
161
+ // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
162
+ // all valid document IDs.
163
+ const successorSeconds = readTime . toTimestamp ( ) . seconds ;
164
+ const successorNanos = readTime . toTimestamp ( ) . nanoseconds + 1 ;
165
+ const successor = SnapshotVersion . fromTimestamp (
166
+ successorNanos === 1e9
167
+ ? new Timestamp ( successorSeconds + 1 , 0 )
168
+ : new Timestamp ( successorSeconds , successorNanos )
169
+ ) ;
170
+ return new IndexOffset ( successor , DocumentKey . empty ( ) , largestBatchId ) ;
171
+ }
172
+
173
+ /** Creates a new offset based on the provided document. */
174
+ export function newIndexOffsetFromDocument ( document : Document ) : IndexOffset {
175
+ return new IndexOffset (
176
+ document . readTime ,
177
+ document . key ,
178
+ INITIAL_LARGEST_BATCH_ID
179
+ ) ;
180
+ }
181
+
182
+ /**
183
+ * Stores the latest read time, document and batch ID that were processed for an
184
+ * index.
185
+ */
186
+ export class IndexOffset {
187
+ constructor (
188
+ /**
189
+ * The latest read time version that has been indexed by Firestore for this
190
+ * field index.
191
+ */
192
+ readonly readTime : SnapshotVersion ,
193
+
194
+ /**
195
+ * The key of the last document that was indexed for this query. Use
196
+ * `DocumentKey.empty()` if no document has been indexed.
197
+ */
198
+ readonly documentKey : DocumentKey ,
199
+
200
+ /*
201
+ * The largest mutation batch id that's been processed by Firestore.
202
+ */
203
+ readonly largestBatchId : number
204
+ ) { }
205
+
206
+ /** The state of an index that has not yet been backfilled. */
207
+ static min ( ) : IndexOffset {
208
+ return new IndexOffset (
209
+ SnapshotVersion . min ( ) ,
210
+ DocumentKey . empty ( ) ,
211
+ INITIAL_LARGEST_BATCH_ID
212
+ ) ;
213
+ }
214
+ }
215
+
216
+ export function indexOffsetComparator (
217
+ left : IndexOffset ,
218
+ right : IndexOffset
219
+ ) : number {
220
+ let cmp = left . readTime . compareTo ( right . readTime ) ;
221
+ if ( cmp !== 0 ) {
222
+ return cmp ;
223
+ }
224
+ cmp = DocumentKey . comparator ( left . documentKey , right . documentKey ) ;
225
+ if ( cmp !== 0 ) {
226
+ return cmp ;
227
+ }
228
+ return primitiveComparator ( left . largestBatchId , right . largestBatchId ) ;
229
+ }
0 commit comments