1515 * limitations under the License.
1616 */
1717
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' ;
1824import { FieldPath } from './path' ;
1925
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+
2038/**
2139 * An index definition for field indexes in Firestore.
2240 *
@@ -30,7 +48,7 @@ import { FieldPath } from './path';
3048 */
3149export class FieldIndex {
3250 /** An ID for an index that has not yet been added to persistence. */
33- static UNKNOWN_ID : - 1 ;
51+ static UNKNOWN_ID = - 1 ;
3452
3553 constructor (
3654 /**
@@ -41,12 +59,40 @@ export class FieldIndex {
4159 /** The collection ID this index applies to. */
4260 readonly collectionGroup : string ,
4361 /** 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
4565 ) { }
4666}
4767
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+
4894/** 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 {
5096 /**
5197 * Ordered index. Can be used for <, <=, ==, >=, >, !=, IN and NOT IN queries.
5298 */
@@ -60,11 +106,124 @@ export const enum Kind {
60106}
61107
62108/** An index component consisting of field path and index type. */
63- export class Segment {
109+ export class IndexSegment {
64110 constructor (
65111 /** The field path of the component. */
66112 readonly fieldPath : FieldPath ,
67113 /** The fields sorting order. */
68- readonly kind : Kind
114+ readonly kind : IndexKind
69115 ) { }
70116}
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