Skip to content

Commit b1f4006

Browse files
committed
Where tests
1 parent 4e98dfc commit b1f4006

File tree

7 files changed

+701
-59
lines changed

7 files changed

+701
-59
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/Pipeline.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.google.firebase.firestore.pipeline.AddFieldsStage
2525
import com.google.firebase.firestore.pipeline.AggregateFunction
2626
import com.google.firebase.firestore.pipeline.AggregateStage
2727
import com.google.firebase.firestore.pipeline.AggregateWithAlias
28+
import com.google.firebase.firestore.pipeline.BaseStage
2829
import com.google.firebase.firestore.pipeline.BooleanExpr
2930
import com.google.firebase.firestore.pipeline.CollectionGroupSource
3031
import com.google.firebase.firestore.pipeline.CollectionSource
@@ -38,19 +39,18 @@ import com.google.firebase.firestore.pipeline.Field
3839
import com.google.firebase.firestore.pipeline.FindNearestStage
3940
import com.google.firebase.firestore.pipeline.FunctionExpr
4041
import com.google.firebase.firestore.pipeline.InternalOptions
41-
import com.google.firebase.firestore.pipeline.Stage
4242
import com.google.firebase.firestore.pipeline.LimitStage
4343
import com.google.firebase.firestore.pipeline.OffsetStage
4444
import com.google.firebase.firestore.pipeline.Ordering
4545
import com.google.firebase.firestore.pipeline.PipelineOptions
46+
import com.google.firebase.firestore.pipeline.RawStage
4647
import com.google.firebase.firestore.pipeline.RealtimePipelineOptions
4748
import com.google.firebase.firestore.pipeline.RemoveFieldsStage
4849
import com.google.firebase.firestore.pipeline.ReplaceStage
4950
import com.google.firebase.firestore.pipeline.SampleStage
5051
import com.google.firebase.firestore.pipeline.SelectStage
5152
import com.google.firebase.firestore.pipeline.Selectable
5253
import com.google.firebase.firestore.pipeline.SortStage
53-
import com.google.firebase.firestore.pipeline.BaseStage
5454
import com.google.firebase.firestore.pipeline.UnionStage
5555
import com.google.firebase.firestore.pipeline.UnnestStage
5656
import com.google.firebase.firestore.pipeline.WhereStage
@@ -159,10 +159,10 @@ private constructor(
159159
* This method provides a way to call stages that are supported by the Firestore backend but that
160160
* are not implemented in the SDK version being used.
161161
*
162-
* @param stage An [Stage] object that specifies stage name and parameters.
162+
* @param rawStage An [RawStage] object that specifies stage name and parameters.
163163
* @return A new [Pipeline] object with this stage appended to the stage list.
164164
*/
165-
fun addStage(stage: Stage): Pipeline = append(stage)
165+
fun addStage(rawStage: RawStage): Pipeline = append(rawStage)
166166

167167
/**
168168
* Adds new fields to outputs from previous stages.

firebase-firestore/src/main/java/com/google/firebase/firestore/core/pipeline.kt

Lines changed: 0 additions & 16 deletions
This file was deleted.

firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/expressions.kt

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ abstract class Expr internal constructor() {
9292
}
9393
.toTypedArray()
9494
)
95-
is List<*> -> ListOfExprs(value.map(toExpr).toTypedArray())
95+
is List<*> -> array(value)
9696
else -> null
9797
}
9898
}
@@ -1018,7 +1018,7 @@ abstract class Expr internal constructor() {
10181018
*/
10191019
@JvmStatic
10201020
fun eqAny(expression: Expr, values: List<Any>): BooleanExpr =
1021-
eqAny(expression, ListOfExprs(toArrayOfExprOrConstant(values)))
1021+
eqAny(expression, array(values))
10221022

10231023
/**
10241024
* Creates an expression that checks if an [expression], when evaluated, is equal to any of the
@@ -1043,7 +1043,7 @@ abstract class Expr internal constructor() {
10431043
*/
10441044
@JvmStatic
10451045
fun eqAny(fieldName: String, values: List<Any>): BooleanExpr =
1046-
eqAny(fieldName, ListOfExprs(toArrayOfExprOrConstant(values)))
1046+
eqAny(fieldName, array(values))
10471047

10481048
/**
10491049
* Creates an expression that checks if a field's value is equal to any of the elements of
@@ -1068,7 +1068,7 @@ abstract class Expr internal constructor() {
10681068
*/
10691069
@JvmStatic
10701070
fun notEqAny(expression: Expr, values: List<Any>): BooleanExpr =
1071-
notEqAny(expression, ListOfExprs(toArrayOfExprOrConstant(values)))
1071+
notEqAny(expression, array(values))
10721072

10731073
/**
10741074
* Creates an expression that checks if an [expression], when evaluated, is not equal to all the
@@ -1093,7 +1093,7 @@ abstract class Expr internal constructor() {
10931093
*/
10941094
@JvmStatic
10951095
fun notEqAny(fieldName: String, values: List<Any>): BooleanExpr =
1096-
notEqAny(fieldName, ListOfExprs(toArrayOfExprOrConstant(values)))
1096+
notEqAny(fieldName, array(values))
10971097

10981098
/**
10991099
* Creates an expression that checks if a field's value is not equal to all of the elements of
@@ -2776,7 +2776,7 @@ abstract class Expr internal constructor() {
27762776
*/
27772777
@JvmStatic
27782778
fun arrayContainsAll(array: Expr, values: List<Any>) =
2779-
arrayContainsAll(array, ListOfExprs(toArrayOfExprOrConstant(values)))
2779+
arrayContainsAll(array, array(values))
27802780

27812781
/**
27822782
* Creates an expression that checks if [array] contains all elements of [arrayExpression].
@@ -2802,7 +2802,7 @@ abstract class Expr internal constructor() {
28022802
"array_contains_all",
28032803
evaluateArrayContainsAll,
28042804
arrayFieldName,
2805-
ListOfExprs(toArrayOfExprOrConstant(values))
2805+
array(values)
28062806
)
28072807

28082808
/**
@@ -2829,7 +2829,7 @@ abstract class Expr internal constructor() {
28292829
"array_contains_any",
28302830
evaluateArrayContainsAny,
28312831
array,
2832-
ListOfExprs(toArrayOfExprOrConstant(values))
2832+
array(values)
28332833
)
28342834

28352835
/**
@@ -2856,7 +2856,7 @@ abstract class Expr internal constructor() {
28562856
"array_contains_any",
28572857
evaluateArrayContainsAny,
28582858
arrayFieldName,
2859-
ListOfExprs(toArrayOfExprOrConstant(values))
2859+
array(values)
28602860
)
28612861

28622862
/**
@@ -4197,17 +4197,6 @@ class Field internal constructor(private val fieldPath: ModelFieldPath) : Select
41974197
}
41984198
}
41994199

4200-
internal class ListOfExprs(private val expressions: Array<out Expr>) : Expr() {
4201-
override fun toProto(userDataReader: UserDataReader): Value =
4202-
encodeValue(expressions.map { it.toProto(userDataReader) })
4203-
4204-
override fun evaluateContext(
4205-
context: EvaluationContext
4206-
): (input: MutableDocument) -> EvaluateResult {
4207-
TODO("Not yet implemented")
4208-
}
4209-
}
4210-
42114200
/**
42124201
* This class defines the base class for Firestore [Pipeline] functions, which can be evaluated
42134202
* within pipeline execution.
@@ -4378,7 +4367,7 @@ internal constructor(name: String, function: EvaluateFunction, params: Array<out
43784367
*
43794368
* You create [Ordering] instances using the [ascending] and [descending] helper methods.
43804369
*/
4381-
class Ordering private constructor(val expr: Expr, private val dir: Direction) {
4370+
class Ordering private constructor(val expr: Expr, internal val dir: Direction) {
43824371
companion object {
43834372

43844373
/**
@@ -4416,12 +4405,9 @@ class Ordering private constructor(val expr: Expr, private val dir: Direction) {
44164405
fun descending(fieldName: String): Ordering = Ordering(field(fieldName), Direction.DESCENDING)
44174406
}
44184407

4419-
private class Direction private constructor(val proto: Value) {
4420-
private constructor(protoString: String) : this(encodeValue(protoString))
4421-
companion object {
4422-
val ASCENDING = Direction("ascending")
4423-
val DESCENDING = Direction("descending")
4424-
}
4408+
internal enum class Direction(val proto: Value) {
4409+
ASCENDING(encodeValue("ascending")),
4410+
DESCENDING(encodeValue("descending"))
44254411
}
44264412

44274413
internal fun toProto(userDataReader: UserDataReader): Value =

firebase-firestore/src/main/java/com/google/firebase/firestore/pipeline/stage.kt

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ import com.google.firestore.v1.Pipeline
2929
import com.google.firestore.v1.Value
3030
import kotlinx.coroutines.flow.Flow
3131
import kotlinx.coroutines.flow.filter
32-
33-
abstract class BaseStage<T : BaseStage<T>>
34-
internal constructor(protected val name: String, internal val options: InternalOptions) {
32+
import kotlinx.coroutines.flow.flow
33+
import kotlinx.coroutines.flow.map
34+
import kotlinx.coroutines.flow.toList
35+
36+
sealed class BaseStage<T : BaseStage<T>>(
37+
protected val name: String,
38+
internal val options: InternalOptions
39+
) {
3540
internal fun toProtoStage(userDataReader: UserDataReader): Pipeline.Stage {
3641
val builder = Pipeline.Stage.newBuilder()
3742
builder.setName(name)
@@ -106,32 +111,32 @@ internal constructor(protected val name: String, internal val options: InternalO
106111
* This class provides a way to call stages that are supported by the Firestore backend but that are
107112
* not implemented in the SDK version being used.
108113
*/
109-
class Stage
114+
class RawStage
110115
private constructor(
111116
name: String,
112117
private val arguments: List<GenericArg>,
113118
options: InternalOptions = InternalOptions.EMPTY
114-
) : BaseStage<Stage>(name, options) {
119+
) : BaseStage<RawStage>(name, options) {
115120
companion object {
116121
/**
117122
* Specify name of stage
118123
*
119124
* @param name The unique name of the stage to add.
120-
* @return [Stage] with specified parameters.
125+
* @return [RawStage] with specified parameters.
121126
*/
122-
@JvmStatic fun ofName(name: String) = Stage(name, emptyList(), InternalOptions.EMPTY)
127+
@JvmStatic fun ofName(name: String) = RawStage(name, emptyList(), InternalOptions.EMPTY)
123128
}
124129

125-
override fun self(options: InternalOptions) = Stage(name, arguments, options)
130+
override fun self(options: InternalOptions) = RawStage(name, arguments, options)
126131

127132
/**
128133
* Specify arguments to stage.
129134
*
130135
* @param arguments A list of ordered parameters to configure the stage's behavior.
131-
* @return [Stage] with specified parameters.
136+
* @return [RawStage] with specified parameters.
132137
*/
133-
fun withArguments(vararg arguments: Any): Stage =
134-
Stage(name, arguments.map(GenericArg::from), options)
138+
fun withArguments(vararg arguments: Any): RawStage =
139+
RawStage(name, arguments.map(GenericArg::from), options)
135140

136141
override fun args(userDataReader: UserDataReader): Sequence<Value> =
137142
arguments.asSequence().map { it.toProto(userDataReader) }
@@ -546,6 +551,43 @@ internal constructor(
546551
override fun self(options: InternalOptions) = SortStage(orders, options)
547552
override fun args(userDataReader: UserDataReader): Sequence<Value> =
548553
orders.asSequence().map { it.toProto(userDataReader) }
554+
555+
override fun evaluate(
556+
context: EvaluationContext,
557+
inputs: Flow<MutableDocument>
558+
): Flow<MutableDocument> {
559+
val evaluates: Array<EvaluateDocument> =
560+
orders.map { it.expr.evaluateContext(context) }.toTypedArray()
561+
val directions: Array<Ordering.Direction> = orders.map { it.dir }.toTypedArray()
562+
return flow {
563+
inputs
564+
// For each document, lazily evaluate order expression values.
565+
.map { doc ->
566+
val orderValues =
567+
evaluates
568+
.map { lazy(LazyThreadSafetyMode.PUBLICATION) { it(doc).value ?: Values.MIN_VALUE } }
569+
.toTypedArray<Lazy<Value>>()
570+
Pair(doc, orderValues)
571+
}
572+
.toList()
573+
.sortedWith(
574+
Comparator { px, py ->
575+
val x = px.second
576+
val y = py.second
577+
directions.forEachIndexed<Ordering.Direction> { i, dir ->
578+
val r =
579+
when (dir) {
580+
Ordering.Direction.ASCENDING -> Values.compare(x[i].value, y[i].value)
581+
Ordering.Direction.DESCENDING -> Values.compare(y[i].value, x[i].value)
582+
}
583+
if (r != 0) return@Comparator r
584+
}
585+
0
586+
}
587+
)
588+
.forEach { p -> emit(p.first) }
589+
}
590+
}
549591
}
550592

551593
internal class DistinctStage

firebase-firestore/src/test/java/com/google/firebase/firestore/core/PipelineTests.kt renamed to firebase-firestore/src/test/java/com/google/firebase/firestore/pipeline/PipelineTests.kt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
package com.google.firebase.firestore.core
1+
// Copyright 2025 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+
// http://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+
package com.google.firebase.firestore.pipeline
216

317
import com.google.common.truth.Truth.assertThat
418
import com.google.firebase.firestore.RealtimePipelineSource
@@ -10,7 +24,10 @@ import kotlinx.coroutines.flow.flowOf
1024
import kotlinx.coroutines.flow.toList
1125
import kotlinx.coroutines.runBlocking
1226
import org.junit.Test
27+
import org.junit.runner.RunWith
28+
import org.robolectric.RobolectricTestRunner
1329

30+
@RunWith(RobolectricTestRunner::class)
1431
internal class PipelineTests {
1532

1633
@Test

0 commit comments

Comments
 (0)