@@ -18,6 +18,8 @@ import com.google.firestore.v1.Value
18
18
import com.google.firestore.v1.Value.ValueTypeCase
19
19
import com.google.protobuf.ByteString
20
20
import com.google.protobuf.Timestamp
21
+ import com.google.re2j.Pattern
22
+ import com.google.re2j.PatternSyntaxException
21
23
import java.math.BigDecimal
22
24
import java.math.RoundingMode
23
25
import kotlin.math.absoluteValue
@@ -363,6 +365,10 @@ internal val evaluateStrConcat = variadicFunction { strings: List<String> ->
363
365
EvaluateResult .string(buildString { strings.forEach(::append) })
364
366
}
365
367
368
+ internal val evaluateStrContains = binaryFunction { value: String , substring: String ->
369
+ EvaluateResult .boolean(value.contains(substring))
370
+ }
371
+
366
372
internal val evaluateStartsWith = binaryFunction { value: String , prefix: String ->
367
373
EvaluateResult .boolean(value.startsWith(prefix))
368
374
}
@@ -385,24 +391,81 @@ internal val evaluateCharLength = unaryFunction { s: String ->
385
391
EvaluateResult .long(s.codePointCount(0 , s.length))
386
392
}
387
393
388
- internal val evaluateToLowercase = notImplemented
394
+ internal val evaluateToLowercase = unaryFunctionPrimitive( String ::lowercase)
389
395
390
- internal val evaluateToUppercase = notImplemented
396
+ internal val evaluateToUppercase = unaryFunctionPrimitive( String ::uppercase)
391
397
392
- internal val evaluateReverse = notImplemented
398
+ internal val evaluateReverse = unaryFunctionPrimitive( String ::reversed)
393
399
394
400
internal val evaluateSplit = notImplemented // TODO: Does not exist in expressions.kt yet.
395
401
396
402
internal val evaluateSubstring = notImplemented // TODO: Does not exist in expressions.kt yet.
397
403
398
- internal val evaluateTrim = notImplemented
404
+ internal val evaluateTrim = unaryFunctionPrimitive( String ::trim)
399
405
400
406
internal val evaluateLTrim = notImplemented // TODO: Does not exist in expressions.kt yet.
401
407
402
408
internal val evaluateRTrim = notImplemented // TODO: Does not exist in expressions.kt yet.
403
409
404
410
internal val evaluateStrJoin = notImplemented // TODO: Does not exist in expressions.kt yet.
405
411
412
+ internal val evaluateReplaceAll = notImplemented // TODO: Does not exist in backend yet.
413
+
414
+ internal val evaluateReplaceFirst = notImplemented // TODO: Does not exist in backend yet.
415
+
416
+ internal val evaluateRegexContains = binaryPatternFunction { pattern: Pattern , value: String ->
417
+ pattern.matcher(value).find()
418
+ }
419
+
420
+ internal val evaluateRegexMatch = binaryPatternFunction(Pattern ::matches)
421
+
422
+ internal val evaluateLike =
423
+ binaryPatternConstructorFunction(
424
+ { likeString: String ->
425
+ try {
426
+ Pattern .compile(likeToRegex(likeString))
427
+ } catch (e: Exception ) {
428
+ null
429
+ }
430
+ },
431
+ Pattern ::matches
432
+ )
433
+
434
+ private fun likeToRegex (like : String ): String = buildString {
435
+ var escape = false
436
+ for (c in like) {
437
+ if (escape) {
438
+ escape = false
439
+ when (c) {
440
+ ' \\ ' -> append(" \\\\ " )
441
+ else -> append(c)
442
+ }
443
+ } else
444
+ when (c) {
445
+ ' \\ ' -> escape = true
446
+ ' _' -> append(' .' )
447
+ ' %' -> append(" .*" )
448
+ ' .' -> append(" \\ ." )
449
+ ' *' -> append(" \\ *" )
450
+ ' ?' -> append(" \\ ?" )
451
+ ' +' -> append(" \\ +" )
452
+ ' ^' -> append(" \\ ^" )
453
+ ' $' -> append(" \\ $" )
454
+ ' |' -> append(" \\ |" )
455
+ ' (' -> append(" \\ (" )
456
+ ' )' -> append(" \\ )" )
457
+ ' [' -> append(" \\ [" )
458
+ ' ]' -> append(" \\ ]" )
459
+ ' {' -> append(" \\ {" )
460
+ ' }' -> append(" \\ }" )
461
+ else -> append(c)
462
+ }
463
+ }
464
+ if (escape) {
465
+ throw Exception (" LIKE pattern ends in backslash" )
466
+ }
467
+ }
468
+
406
469
// === Date / Timestamp Functions ===
407
470
408
471
private const val L_NANOS_PER_SECOND : Long = 1000_000_000
@@ -574,6 +637,12 @@ private inline fun unaryFunction(crossinline stringOp: (Boolean) -> EvaluateResu
574
637
stringOp,
575
638
)
576
639
640
+ @JvmName(" unaryStringFunctionPrimitive" )
641
+ private inline fun unaryFunctionPrimitive (crossinline stringOp : (String ) -> String ) =
642
+ unaryFunction { s: String ->
643
+ EvaluateResult .string(stringOp(s))
644
+ }
645
+
577
646
@JvmName(" unaryStringFunction" )
578
647
private inline fun unaryFunction (crossinline stringOp : (String ) -> EvaluateResult ) =
579
648
unaryFunctionType(
@@ -696,6 +765,49 @@ private inline fun binaryFunction(crossinline function: (String, String) -> Eval
696
765
function
697
766
)
698
767
768
+ @JvmName(" binaryStringPatternConstructorFunction" )
769
+ private inline fun binaryPatternConstructorFunction (
770
+ crossinline patternConstructor : (String ) -> Pattern ? ,
771
+ crossinline function : (Pattern , String ) -> Boolean
772
+ ) =
773
+ binaryFunctionConstructorType(
774
+ ValueTypeCase .STRING_VALUE ,
775
+ Value ::getStringValue,
776
+ ValueTypeCase .STRING_VALUE ,
777
+ Value ::getStringValue
778
+ ) {
779
+ val cache = cache(patternConstructor)
780
+ ({ value: String , regex: String ->
781
+ val pattern = cache(regex)
782
+ if (pattern == null ) EvaluateResultError else EvaluateResult .boolean(function(pattern, value))
783
+ })
784
+ }
785
+
786
+ @JvmName(" binaryStringPatternFunction" )
787
+ private inline fun binaryPatternFunction (crossinline function : (Pattern , String ) -> Boolean ) =
788
+ binaryPatternConstructorFunction(
789
+ { s: String ->
790
+ try {
791
+ Pattern .compile(s)
792
+ } catch (e: PatternSyntaxException ) {
793
+ null
794
+ }
795
+ },
796
+ function
797
+ )
798
+
799
+ private inline fun <T > cache (crossinline ifAbsent : (String ) -> T ): (String ) -> T ? {
800
+ var cache: Pair <String ?, T ?> = Pair (null , null )
801
+ return block@{ s: String ->
802
+ var (regex, pattern) = cache
803
+ if (regex != s) {
804
+ pattern = ifAbsent(s)
805
+ cache = Pair (s, pattern)
806
+ }
807
+ return @block pattern
808
+ }
809
+ }
810
+
699
811
@JvmName(" binaryArrayArrayFunction" )
700
812
private inline fun binaryFunction (
701
813
crossinline function : (List <Value >, List <Value >) -> EvaluateResult
@@ -756,12 +868,43 @@ private inline fun <T1, T2> binaryFunctionType(
756
868
valueTypeCase2 : ValueTypeCase ,
757
869
crossinline valueExtractor2 : (Value ) -> T2 ,
758
870
crossinline function : (T1 , T2 ) -> EvaluateResult
871
+ ): EvaluateFunction = { params ->
872
+ if (params.size != 2 )
873
+ throw Assert .fail(" Function should have exactly 2 params, but %d were given." , params.size)
874
+ (block@{ input: MutableDocument ->
875
+ val v1 = params[0 ](input).value ? : return @block EvaluateResultError
876
+ val v2 = params[1 ](input).value ? : return @block EvaluateResultError
877
+ when (v1.valueTypeCase) {
878
+ ValueTypeCase .NULL_VALUE ->
879
+ when (v2.valueTypeCase) {
880
+ ValueTypeCase .NULL_VALUE -> EvaluateResult .NULL
881
+ valueTypeCase2 -> EvaluateResult .NULL
882
+ else -> EvaluateResultError
883
+ }
884
+ valueTypeCase1 ->
885
+ when (v2.valueTypeCase) {
886
+ ValueTypeCase .NULL_VALUE -> EvaluateResult .NULL
887
+ valueTypeCase2 -> catch { function(valueExtractor1(v1), valueExtractor2(v2)) }
888
+ else -> EvaluateResultError
889
+ }
890
+ else -> EvaluateResultError
891
+ }
892
+ })
893
+ }
894
+
895
+ private inline fun <T1 , T2 > binaryFunctionConstructorType (
896
+ valueTypeCase1 : ValueTypeCase ,
897
+ crossinline valueExtractor1 : (Value ) -> T1 ,
898
+ valueTypeCase2 : ValueTypeCase ,
899
+ crossinline valueExtractor2 : (Value ) -> T2 ,
900
+ crossinline functionConstructor : () -> (T1 , T2 ) -> EvaluateResult
759
901
): EvaluateFunction = { params ->
760
902
if (params.size != 2 )
761
903
throw Assert .fail(" Function should have exactly 2 params, but %d were given." , params.size)
762
904
val p1 = params[0 ]
763
905
val p2 = params[1 ]
764
- block@{ input: MutableDocument ->
906
+ val f = functionConstructor()
907
+ (block@{ input: MutableDocument ->
765
908
val v1 = p1(input).value ? : return @block EvaluateResultError
766
909
val v2 = p2(input).value ? : return @block EvaluateResultError
767
910
when (v1.valueTypeCase) {
@@ -774,12 +917,12 @@ private inline fun <T1, T2> binaryFunctionType(
774
917
valueTypeCase1 ->
775
918
when (v2.valueTypeCase) {
776
919
ValueTypeCase .NULL_VALUE -> EvaluateResult .NULL
777
- valueTypeCase2 -> catch { function (valueExtractor1(v1), valueExtractor2(v2)) }
920
+ valueTypeCase2 -> catch { f (valueExtractor1(v1), valueExtractor2(v2)) }
778
921
else -> EvaluateResultError
779
922
}
780
923
else -> EvaluateResultError
781
924
}
782
- }
925
+ })
783
926
}
784
927
785
928
private inline fun variadicResultFunction (
0 commit comments