Skip to content

Commit 3352260

Browse files
committed
Align more tests and behaviors with backend implementation
1 parent b4e7f7f commit 3352260

File tree

71 files changed

+5996
-1502
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+5996
-1502
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.google.firebase.firestore.pipeline.CollectionGroupSource
3030
import com.google.firebase.firestore.pipeline.CollectionSource
3131
import com.google.firebase.firestore.pipeline.CollectionSourceOptions
3232
import com.google.firebase.firestore.pipeline.Field
33+
import com.google.firebase.firestore.pipeline.FunctionExpression
3334
import com.google.firebase.firestore.pipeline.InternalOptions
3435
import com.google.firebase.firestore.pipeline.LimitStage
3536
import com.google.firebase.firestore.pipeline.OffsetStage
@@ -555,9 +556,11 @@ internal constructor(
555556
// Check for Where stage
556557
if (stage is WhereStage) {
557558
// Check if it's the special 'exists(__name__)' case
558-
val funcExpr = stage.condition as? BooleanFunctionExpression
559-
if (funcExpr?.expr?.name == "exists" && funcExpr.expr.params.size == 1) {
560-
val fieldExpr = funcExpr?.expr?.params[0] as? Field
559+
val booleanFunc = stage.condition as? BooleanFunctionExpression
560+
val funcExpr = booleanFunc?.expr as? FunctionExpression
561+
562+
if (funcExpr?.name == "exists" && funcExpr.params.size == 1) {
563+
val fieldExpr = funcExpr?.params[0] as? Field
561564
if (fieldExpr?.fieldPath?.isKeyField == true) {
562565
continue // This specific 'exists(__name__)' filter doesn't count
563566
}

firebase-firestore/src/main/java/com/google/firebase/firestore/UserDataWriter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static com.google.firebase.firestore.model.Values.TYPE_ORDER_MAP;
2424
import static com.google.firebase.firestore.model.Values.TYPE_ORDER_NULL;
2525
import static com.google.firebase.firestore.model.Values.TYPE_ORDER_NUMBER;
26+
import static com.google.firebase.firestore.model.Values.TYPE_ORDER_NUMBER_NAN;
2627
import static com.google.firebase.firestore.model.Values.TYPE_ORDER_REFERENCE;
2728
import static com.google.firebase.firestore.model.Values.TYPE_ORDER_SERVER_TIMESTAMP;
2829
import static com.google.firebase.firestore.model.Values.TYPE_ORDER_STRING;
@@ -77,6 +78,7 @@ public Object convertValue(Value value) {
7778
return null;
7879
case TYPE_ORDER_BOOLEAN:
7980
return value.getBooleanValue();
81+
case TYPE_ORDER_NUMBER_NAN:
8082
case TYPE_ORDER_NUMBER:
8183
return value.hasIntegerValue()
8284
? (Object) value.getIntegerValue() // Cast to Object to prevent type coercion to double

firebase-firestore/src/main/java/com/google/firebase/firestore/model/Values.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ object Values {
135135
}
136136

137137
return when (leftType) {
138+
TYPE_ORDER_NUMBER_NAN,
138139
TYPE_ORDER_NUMBER -> numberEquals(left, right)
139140
TYPE_ORDER_ARRAY -> arrayEquals(left, right)
140141
TYPE_ORDER_VECTOR,
@@ -207,6 +208,8 @@ object Values {
207208
return Values.equals(left, right)
208209
}
209210

211+
internal val compare = Values::compare
212+
210213
internal enum class CompareResult {
211214
LESS_THAN,
212215
EQUAL,

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ import kotlin.math.sqrt
3131

3232
// === Arithmetic Functions ===
3333

34+
internal sealed interface FirestoreNumber
35+
36+
internal data class LongValue(val value: Long) : FirestoreNumber
37+
38+
internal data class DoubleValue(val value: Double) : FirestoreNumber
39+
3440
internal val evaluateAdd: EvaluateFunction = arithmeticPrimitive(LongMath::checkedAdd, Double::plus)
3541

3642
internal val evaluateCeil = arithmeticPrimitive({ it }, Math::ceil)
@@ -111,7 +117,7 @@ internal val evaluateRoundToPrecision =
111117
{ value: Double, places: Long ->
112118
// A double can only represent up to 16 decimal places. Here we return the original value if
113119
// attempting to round to more decimal places than the double can represent.
114-
if (places >= 16 || !value.isInfinite()) {
120+
if (places >= 16 || !value.isFinite()) {
115121
return@arithmetic EvaluateResult.double(value)
116122
}
117123

@@ -126,7 +132,7 @@ internal val evaluateRoundToPrecision =
126132
BigDecimal.valueOf(value).setScale(places.toInt(), RoundingMode.HALF_UP)
127133
val result: Double = rounded.toDouble()
128134

129-
if (result.isInfinite()) EvaluateResult.double(result)
135+
if (result.isFinite()) EvaluateResult.double(result)
130136
else EvaluateResultError // overflow error
131137
}
132138
)
@@ -140,15 +146,23 @@ internal val evaluateAbs =
140146
{ d: Double -> d.absoluteValue }
141147
)
142148

143-
internal val evaluateExp = arithmetic { value: Double -> EvaluateResult.double(exp(value)) }
149+
internal val evaluateExp = arithmetic { value: Double ->
150+
val result = exp(value)
151+
// Returning an error on double overflow (characterized by a non-infinite exponent returning an
152+
// infinite result).
153+
if (result == Double.POSITIVE_INFINITY && value != Double.POSITIVE_INFINITY) {
154+
throw Exception("exp(...) exponent overflow")
155+
}
156+
EvaluateResult.double(exp(value))
157+
}
144158

145159
internal val evaluateLn = arithmetic { value: Double ->
146-
if (value < 0) EvaluateResultError else EvaluateResult.double(ln(value))
160+
if (value <= 0) EvaluateResultError else EvaluateResult.double(ln(value))
147161
}
148162

149163
internal val evaluateLog = arithmetic { value: Double, base: Double ->
150164
return@arithmetic if (value == Double.NEGATIVE_INFINITY) {
151-
EvaluateResultError
165+
EvaluateResult.double(Double.NaN)
152166
} else if (base == Double.POSITIVE_INFINITY) {
153167
EvaluateResult.double(Double.NaN)
154168
} else if (base <= 0 || value <= 0 || base == 1.0) {
@@ -157,7 +171,7 @@ internal val evaluateLog = arithmetic { value: Double, base: Double ->
157171
}
158172

159173
internal val evaluateLog10 = arithmetic { value: Double ->
160-
if (value < 0) EvaluateResultError else EvaluateResult.double(log10(value))
174+
if (value <= 0) EvaluateResultError else EvaluateResult.double(log10(value))
161175
}
162176

163177
internal val evaluateSqrt = arithmetic { value: Double ->

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

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,41 +27,42 @@ import com.google.firestore.v1.Value.ValueTypeCase
2727

2828
internal val evaluateAnd: EvaluateFunction = { params ->
2929
fun(input: MutableDocument): EvaluateResult {
30-
var isError = false
3130
var isNull = false
3231
for (param in params) {
33-
val value = param(input).value
34-
if (value === null) isError = true
35-
else
36-
when (value.valueTypeCase) {
37-
ValueTypeCase.NULL_VALUE -> isNull = true
38-
ValueTypeCase.BOOLEAN_VALUE -> {
39-
if (!value.booleanValue) return FALSE
40-
}
41-
else -> return EvaluateResultError
32+
val result = param(input)
33+
if (result.isError) return EvaluateResultError
34+
35+
val value = result.value
36+
when (value?.valueTypeCase) {
37+
null,
38+
ValueTypeCase.NULL_VALUE -> isNull = true
39+
ValueTypeCase.BOOLEAN_VALUE -> {
40+
if (!value.booleanValue) return FALSE
4241
}
42+
else -> return EvaluateResultError
43+
}
4344
}
44-
return if (isError) EvaluateResultError else if (isNull) NULL else TRUE
45+
return if (isNull) NULL else TRUE
4546
}
4647
}
4748

4849
internal val evaluateOr: EvaluateFunction = { params ->
4950
fun(input: MutableDocument): EvaluateResult {
50-
var isError = false
5151
var isNull = false
5252
for (param in params) {
53-
val value = param(input).value
54-
if (value === null) isError = true
55-
else
56-
when (value.valueTypeCase) {
57-
ValueTypeCase.NULL_VALUE -> isNull = true
58-
ValueTypeCase.BOOLEAN_VALUE -> {
59-
if (value.booleanValue) return TRUE
60-
}
61-
else -> return EvaluateResultError
53+
val result = param(input)
54+
if (result.isError) return EvaluateResultError
55+
val value = result.value
56+
when (value?.valueTypeCase) {
57+
null,
58+
ValueTypeCase.NULL_VALUE -> isNull = true
59+
ValueTypeCase.BOOLEAN_VALUE -> {
60+
if (value.booleanValue) return TRUE
6261
}
62+
else -> return EvaluateResultError
63+
}
6364
}
64-
return if (isError) EvaluateResultError else if (isNull) NULL else FALSE
65+
return if (isNull) NULL else FALSE
6566
}
6667
}
6768

@@ -70,30 +71,66 @@ internal val evaluateXor: EvaluateFunction = variadicFunction { values: BooleanA
7071
}
7172

7273
internal val evaluateCond: EvaluateFunction = ternaryLazyFunction { p1, p2, p3 ->
73-
val v1 = p1().value ?: return@ternaryLazyFunction EvaluateResultError
74-
when (v1.valueTypeCase) {
74+
val r1 = p1()
75+
if (r1.isError) return@ternaryLazyFunction EvaluateResultError
76+
77+
val v1 = r1.value
78+
when (v1?.valueTypeCase) {
7579
ValueTypeCase.BOOLEAN_VALUE -> if (v1.booleanValue) p2() else p3()
80+
null,
7681
ValueTypeCase.NULL_VALUE -> p3()
7782
else -> EvaluateResultError
7883
}
7984
}
8085

8186
internal val evaluateLogicalMaximum: EvaluateFunction =
82-
variadicResultFunction { l: List<EvaluateResult> ->
83-
val value =
84-
l.mapNotNull(EvaluateResult::value)
85-
.filterNot(Value::hasNullValue)
86-
.maxWithOrNull(Values::compare)
87-
if (value === null) NULL else EvaluateResultValue(value)
87+
variadicResultFunction { params: List<EvaluateResult> ->
88+
if (params.size < 2) return@variadicResultFunction EvaluateResultError
89+
90+
val maximum = { a: Value?, b: Value ->
91+
if (a === null) b
92+
else {
93+
val result = Values.Enterprise.compare(a, b)
94+
if (result == 0) a else if (result > 0) a else b
95+
}
96+
}
97+
98+
var maxResult: Value? = null
99+
for (param in params) {
100+
if (param.isError) return@variadicResultFunction EvaluateResultError
101+
val value = param.value
102+
when (value?.valueTypeCase) {
103+
null,
104+
ValueTypeCase.NULL_VALUE -> {}
105+
else -> maxResult = maximum(maxResult, value)
106+
}
107+
}
108+
if (maxResult === null) NULL else EvaluateResult.value(maxResult)
88109
}
89110

90111
internal val evaluateLogicalMinimum: EvaluateFunction =
91-
variadicResultFunction { l: List<EvaluateResult> ->
92-
val value =
93-
l.mapNotNull(EvaluateResult::value)
94-
.filterNot(Value::hasNullValue)
95-
.minWithOrNull(Values::compare)
96-
if (value === null) NULL else EvaluateResultValue(value)
112+
variadicResultFunction { params: List<EvaluateResult> ->
113+
if (params.size < 2) return@variadicResultFunction EvaluateResultError
114+
115+
val minimum = { a: Value?, b: Value ->
116+
if (a === null) b
117+
else {
118+
val result = Values.Enterprise.compare(a, b)
119+
if (result == 0) a else if (result > 0) b else a
120+
}
121+
}
122+
123+
var minResult: Value? = null
124+
for (param in params) {
125+
if (param.isError) return@variadicResultFunction EvaluateResultError
126+
val value = param.value
127+
when (value?.valueTypeCase) {
128+
null,
129+
ValueTypeCase.NULL_VALUE -> {}
130+
else -> minResult = minimum(minResult, value)
131+
}
132+
}
133+
if (minResult === null) NULL else EvaluateResult.value(minResult)
97134
}
98135

99136
// === Type Functions ===

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,28 @@
1515
package com.google.firebase.firestore.pipeline.evaluation
1616

1717
import com.google.firebase.firestore.model.MutableDocument
18+
import com.google.firebase.firestore.model.Values
1819
import com.google.firebase.firestore.model.Values.encodeValue
1920
import com.google.firebase.firestore.util.Assert
2021
import com.google.firestore.v1.Value
2122

2223
// === Map Functions ===
2324

24-
internal val evaluateMapGet = binaryFunction { map: Map<String, Value>, key: String ->
25-
EvaluateResultValue(map[key] ?: return@binaryFunction EvaluateResultUnset)
25+
internal val evaluateMapGet = binaryFunction { mapValue: Value?, keyValue: Value? ->
26+
val map =
27+
when {
28+
mapValue == null -> null
29+
Values.isMapValue(mapValue) && !Values.isVectorValue(mapValue) -> mapValue.mapValue.fieldsMap
30+
else -> null
31+
}
32+
33+
val result =
34+
when (keyValue?.valueTypeCase) {
35+
Value.ValueTypeCase.STRING_VALUE -> map?.get(keyValue.stringValue)
36+
else -> return@binaryFunction EvaluateResultError
37+
}
38+
39+
if (result == null) EvaluateResultUnset else EvaluateResultValue(result)
2640
}
2741

2842
internal val evaluateMap: EvaluateFunction = { params ->

0 commit comments

Comments
 (0)