@@ -17,7 +17,10 @@ package com.google.firebase.firestore
17
17
import com.google.android.gms.tasks.Task
18
18
import com.google.android.gms.tasks.TaskCompletionSource
19
19
import com.google.firebase.Timestamp
20
+ import com.google.firebase.firestore.core.Canonicalizable
21
+ import com.google.firebase.firestore.model.Document
20
22
import com.google.firebase.firestore.model.DocumentKey
23
+ import com.google.firebase.firestore.model.MutableDocument
21
24
import com.google.firebase.firestore.model.Values
22
25
import com.google.firebase.firestore.pipeline.AddFieldsStage
23
26
import com.google.firebase.firestore.pipeline.AggregateFunction
@@ -29,6 +32,7 @@ import com.google.firebase.firestore.pipeline.CollectionSource
29
32
import com.google.firebase.firestore.pipeline.DatabaseSource
30
33
import com.google.firebase.firestore.pipeline.DistinctStage
31
34
import com.google.firebase.firestore.pipeline.DocumentsSource
35
+ import com.google.firebase.firestore.pipeline.EvaluationContext
32
36
import com.google.firebase.firestore.pipeline.Expr
33
37
import com.google.firebase.firestore.pipeline.Expr.Companion.field
34
38
import com.google.firebase.firestore.pipeline.ExprWithAlias
@@ -41,7 +45,6 @@ import com.google.firebase.firestore.pipeline.OffsetStage
41
45
import com.google.firebase.firestore.pipeline.Ordering
42
46
import com.google.firebase.firestore.pipeline.PipelineOptions
43
47
import com.google.firebase.firestore.pipeline.RawStage
44
- import com.google.firebase.firestore.pipeline.RealtimePipelineOptions
45
48
import com.google.firebase.firestore.pipeline.RemoveFieldsStage
46
49
import com.google.firebase.firestore.pipeline.ReplaceStage
47
50
import com.google.firebase.firestore.pipeline.SampleStage
@@ -52,6 +55,7 @@ import com.google.firebase.firestore.pipeline.Stage
52
55
import com.google.firebase.firestore.pipeline.UnionStage
53
56
import com.google.firebase.firestore.pipeline.UnnestStage
54
57
import com.google.firebase.firestore.pipeline.WhereStage
58
+ import com.google.firebase.firestore.util.Assert.fail
55
59
import com.google.firestore.v1.ExecutePipelineRequest
56
60
import com.google.firestore.v1.StructuredPipeline
57
61
import com.google.firestore.v1.Value
@@ -760,7 +764,7 @@ internal constructor(
760
764
firestore: FirebaseFirestore ,
761
765
userDataReader: UserDataReader ,
762
766
stages: List <Stage <* >>
763
- ) : AbstractPipeline (firestore, userDataReader, stages) {
767
+ ) : AbstractPipeline (firestore, userDataReader, stages), Canonicalizable {
764
768
internal constructor (
765
769
firestore: FirebaseFirestore ,
766
770
userDataReader: UserDataReader ,
@@ -787,31 +791,107 @@ internal constructor(
787
791
788
792
fun where (condition : BooleanExpr ): RealtimePipeline = append(WhereStage (condition))
789
793
790
- internal fun rewriteStages (): RealtimePipeline {
794
+ internal val rewrittenStages : List < Stage < * >> by lazy {
791
795
var hasOrder = false
792
- return with (
793
- buildList {
794
- for (stage in stages) when (stage) {
795
- // Stages whose semantics depend on ordering
796
- is LimitStage ,
797
- is OffsetStage -> {
798
- if (! hasOrder) {
799
- hasOrder = true
800
- add(SortStage .BY_DOCUMENT_ID )
801
- }
802
- add(stage)
803
- }
804
- is SortStage -> {
796
+ buildList {
797
+ for (stage in stages) when (stage) {
798
+ // Stages whose semantics depend on ordering
799
+ is LimitStage ,
800
+ is OffsetStage -> {
801
+ if (! hasOrder) {
805
802
hasOrder = true
806
- add(stage.withStableOrdering() )
803
+ add(SortStage . BY_DOCUMENT_ID )
807
804
}
808
- else -> add(stage)
805
+ add(stage)
809
806
}
810
- if (! hasOrder) {
811
- add(SortStage .BY_DOCUMENT_ID )
807
+ is SortStage -> {
808
+ hasOrder = true
809
+ add(stage.withStableOrdering())
812
810
}
811
+ else -> add(stage)
813
812
}
814
- )
813
+ if (! hasOrder) {
814
+ add(SortStage .BY_DOCUMENT_ID )
815
+ }
816
+ }
817
+ }
818
+
819
+ override fun canonicalId (): String {
820
+ return rewrittenStages.joinToString(" |" ) { stage -> (stage as Canonicalizable ).canonicalId() }
821
+ }
822
+
823
+ override fun equals (other : Any? ): Boolean {
824
+ if (this == = other) return true
825
+ if (other !is RealtimePipeline ) return false
826
+ return stages == other.stages
827
+ }
828
+
829
+ override fun hashCode (): Int {
830
+ return stages.hashCode()
831
+ }
832
+
833
+ internal fun evaluate (inputs : List <MutableDocument >): List <MutableDocument > {
834
+ val context = EvaluationContext (this )
835
+ return rewrittenStages.fold(inputs) { documents, stage -> stage.evaluate(context, documents) }
836
+ }
837
+
838
+ internal fun matchesAllDocuments (): Boolean {
839
+ for (stage in rewrittenStages) {
840
+ // Check for LimitStage
841
+ if (stage.name == " limit" ) {
842
+ return false
843
+ }
844
+
845
+ // Check for Where stage
846
+ if (stage is WhereStage ) {
847
+ // Check if it's the special 'exists(__name__)' case
848
+ val funcExpr = stage.condition as ? FunctionExpr
849
+ if (funcExpr?.name == " exists" && funcExpr.params.size == 1 ) {
850
+ val fieldExpr = funcExpr.params[0 ] as ? Field
851
+ if (fieldExpr?.fieldPath?.isKeyField == true ) {
852
+ continue // This specific 'exists(__name__)' filter doesn't count
853
+ }
854
+ }
855
+ return false
856
+ }
857
+ // TODO(pipeline) : Add checks for other filtering stages like Aggregate,
858
+ // Distinct, FindNearest once they are implemented.
859
+ }
860
+ return true
861
+ }
862
+
863
+ internal fun hasLimit (): Boolean {
864
+ for (stage in rewrittenStages) {
865
+ if (stage.name == " limit" ) {
866
+ return true
867
+ }
868
+ // TODO(pipeline): need to check for other stages that could have a limit,
869
+ // like findNearest
870
+ }
871
+ return false
872
+ }
873
+
874
+ internal fun matches (doc : Document ): Boolean {
875
+ val result = evaluate(listOf (doc as MutableDocument ))
876
+ return result.isNotEmpty()
877
+ }
878
+
879
+ private fun evaluateContext (): EvaluationContext {
880
+ return EvaluationContext (this )
881
+ }
882
+
883
+ internal fun comparator (): Comparator <Document > =
884
+ getLastEffectiveSortStage().comparator(evaluateContext())
885
+
886
+ private fun getLastEffectiveSortStage (): SortStage {
887
+ for (stage in rewrittenStages.asReversed()) {
888
+ if (stage is SortStage ) {
889
+ return stage
890
+ }
891
+ // TODO(pipeline): Consider stages that might invalidate ordering later,
892
+ // like fineNearest
893
+ }
894
+ throw fail(" RealtimePipeline must contain at least one Sort stage (ensured by RewriteStages)." )
815
895
}
816
896
}
817
897
0 commit comments