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
+ 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,119 @@ 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
115
+ ) { }
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
+ /** Stores the "high water mark" that indicates how updated the Index is for the current user. */
130
+ export class IndexState {
131
+ constructor (
132
+ /** Indicates when the index was last updated (relative to other indexes). */
133
+ readonly sequenceNumber : number ,
134
+ /** The the latest indexed read time, document and batch id. */
135
+ readonly offset : IndexOffset
69
136
) { }
137
+
138
+ /** The state of an index that has not yet been backfilled. */
139
+ static empty ( ) : IndexState {
140
+ return new IndexState ( INITIAL_SEQUENCE_NUMBER , IndexOffset . min ( ) ) ;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Creates an offset that matches all documents with a read time higher than
146
+ * `readTime`.
147
+ */
148
+ export function newIndexOffsetSuccessorFromReadTime (
149
+ readTime : SnapshotVersion ,
150
+ largestBatchId : number
151
+ ) : IndexOffset {
152
+ // We want to create an offset that matches all documents with a read time
153
+ // greater than the provided read time. To do so, we technically need to
154
+ // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
155
+ // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
156
+ // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
157
+ // all valid document IDs.
158
+ const successorSeconds = readTime . toTimestamp ( ) . seconds ;
159
+ const successorNanos = readTime . toTimestamp ( ) . nanoseconds + 1 ;
160
+ const successor = SnapshotVersion . fromTimestamp (
161
+ successorNanos === 1e9
162
+ ? new Timestamp ( successorSeconds + 1 , 0 )
163
+ : new Timestamp ( successorSeconds , successorNanos )
164
+ ) ;
165
+ return new IndexOffset ( successor , DocumentKey . empty ( ) , largestBatchId ) ;
166
+ }
167
+
168
+ /** Creates a new offset based on the provided document. */
169
+ export function newIndexOffsetFromDocument ( document : Document ) : IndexOffset {
170
+ return new IndexOffset (
171
+ document . readTime ,
172
+ document . key ,
173
+ INITIAL_LARGEST_BATCH_ID
174
+ ) ;
175
+ }
176
+
177
+ /**
178
+ * Stores the latest read time, document and batch ID that were processed for an
179
+ * index.
180
+ */
181
+ export class IndexOffset {
182
+ constructor (
183
+ /**
184
+ * The latest read time version that has been indexed by Firestore for this
185
+ * field index.
186
+ */
187
+ readonly readTime : SnapshotVersion ,
188
+
189
+ /**
190
+ * The key of the last document that was indexed for this query. Use
191
+ * `DocumentKey.empty()` if no document has been indexed.
192
+ */
193
+ readonly documentKey : DocumentKey ,
194
+
195
+ /*
196
+ * The largest mutation batch id that's been processed by Firestore.
197
+ */
198
+ readonly largestBatchId : number
199
+ ) { }
200
+
201
+ /** The state of an index that has not yet been backfilled. */
202
+ static min ( ) : IndexOffset {
203
+ return new IndexOffset (
204
+ SnapshotVersion . min ( ) ,
205
+ DocumentKey . empty ( ) ,
206
+ INITIAL_LARGEST_BATCH_ID
207
+ ) ;
208
+ }
209
+ }
210
+
211
+ export function indexOffsetComparator (
212
+ left : IndexOffset ,
213
+ right : IndexOffset
214
+ ) : number {
215
+ let cmp = left . readTime . compareTo ( right . readTime ) ;
216
+ if ( cmp !== 0 ) {
217
+ return cmp ;
218
+ }
219
+ cmp = DocumentKey . comparator ( left . documentKey , right . documentKey ) ;
220
+ if ( cmp !== 0 ) {
221
+ return cmp ;
222
+ }
223
+ return primitiveComparator ( left . largestBatchId , right . largestBatchId ) ;
70
224
}
0 commit comments