Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# 12.13.0
- [feature] Added support for `minimum` and `maximum` FieldValue operations.
- [feature] Added search stage support for `languageCode`, `offset`, `limit`, and `retrievalDepth`.
- [feature] Added support for Pipeline expressions `arraySlice`, `arrayFilter`, `arrayTransform` and `arrayTransformWithIndex`. (#16001)
- [fixed] Add missing `noexcept` specifiers to move, hash, swap operations [#16117].
Expand Down
123 changes: 123 additions & 0 deletions Firestore/Example/Tests/Integration/API/FIRNumericTransformTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#import <XCTest/XCTest.h>

#import <math.h>

#import "Firestore/Source/API/FIRFieldValue+Internal.h"

#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
Expand Down Expand Up @@ -65,7 +67,7 @@
- (void)writeInitialData:(NSDictionary<NSString *, id> *)data {
[self writeDocumentRef:_docRef data:data];
XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, data);
XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, data);

Check failure on line 70 in Firestore/Example/Tests/Integration/API/FIRNumericTransformTests.mm

View workflow job for this annotation

GitHub Actions / xcodetest_nightly (macOS, FirestoreEnterprise)

testMaximumWithExistingDouble, (([_accumulator awaitRemoteEvent].data) equal to (data)) failed: throwing "NSRangeException: *** -[NSArray subarrayWithRange:]: range {2, 1} extends beyond bounds [0 .. 1]"
}

- (void)expectLocalAndRemoteValue:(int64_t)expectedSum {
Expand Down Expand Up @@ -215,4 +217,125 @@
XCTAssertEqualWithAccuracy(0.111, [snap[@"sum"] doubleValue], DOUBLE_EPSILON);
}

- (void)testCreateDocumentWithMinimum {
[self writeDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMinimum:1337]}];
[self expectLocalAndRemoteValue:1337];
}

- (void)testCreateDocumentWithMaximum {
[self writeDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMaximum:1337]}];
[self expectLocalAndRemoteValue:1337];
}

- (void)testMinimumWithExistingInteger {
[self writeInitialData:@{@"sum" : @10}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMinimum:5]}];
[self expectLocalAndRemoteValue:5];

[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMinimum:20]}];
[self expectLocalAndRemoteValue:5];
}

- (void)testMaximumWithExistingInteger {
[self writeInitialData:@{@"sum" : @10}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMaximum:5]}];
[self expectLocalAndRemoteValue:10];

[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMaximum:20]}];
[self expectLocalAndRemoteValue:20];
}

- (void)testMinimumWithExistingDouble {
[self writeInitialData:@{@"sum" : @10.5}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMinimum:5.5]}];
[self expectApproximateLocalAndRemoteValue:5.5];

[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMinimum:20.5]}];
[self expectApproximateLocalAndRemoteValue:5.5];
}

- (void)testMaximumWithExistingDouble {
[self writeInitialData:@{@"sum" : @10.5}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMaximum:5.5]}];
[self expectApproximateLocalAndRemoteValue:10.5];

[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMaximum:20.5]}];
[self expectApproximateLocalAndRemoteValue:20.5];
}

- (void)testMixedTypesPreserveOperandTypeForMinimum {
// field and input value of mixed types: field takes on type of smaller operand
[self writeInitialData:@{@"sum" : @10}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMinimum:5.5]}];
[self expectApproximateLocalAndRemoteValue:5.5];

[self writeInitialData:@{@"sum" : @10.5}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMinimum:5]}];
[self expectLocalAndRemoteValue:5];
}

- (void)testMixedTypesPreserveOperandTypeForMaximum {
// field and input value of mixed types: field takes on type of larger operand
[self writeInitialData:@{@"sum" : @10}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMaximum:20.5]}];
[self expectApproximateLocalAndRemoteValue:20.5];

[self writeInitialData:@{@"sum" : @10.5}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMaximum:20]}];
[self expectLocalAndRemoteValue:20];
}

- (void)testEquivalentValuesDoNotChangeTypeForMinimum {
// equivalent (e.g. 3 and 3.0), field does not change type
[self writeInitialData:@{@"sum" : @3}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMinimum:3.0]}];
[self expectLocalAndRemoteValue:3];

[self writeInitialData:@{@"sum" : @3.0}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMinimum:3]}];
[self expectApproximateLocalAndRemoteValue:3.0];
}

- (void)testEquivalentValuesDoNotChangeTypeForMaximum {
// equivalent (e.g. 3 and 3.0), field does not change type
[self writeInitialData:@{@"sum" : @3}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMaximum:3.0]}];
[self expectLocalAndRemoteValue:3];

[self writeInitialData:@{@"sum" : @3.0}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMaximum:3]}];
[self expectApproximateLocalAndRemoteValue:3.0];
}

- (void)expectLocalAndRemoteNaN {
FIRDocumentSnapshot *snap = [_accumulator awaitLocalEvent];
XCTAssertTrue([snap[@"sum"] isKindOfClass:[NSNumber class]]);
XCTAssertTrue(isnan([snap[@"sum"] doubleValue]));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small: This is using isnan() from <math.h>. In a C++ context, it is usually preferred to use std::isnan() from .

snap = [_accumulator awaitRemoteEvent];
XCTAssertTrue([snap[@"sum"] isKindOfClass:[NSNumber class]]);
XCTAssertTrue(isnan([snap[@"sum"] doubleValue]));
}

- (void)testMinimumWithNaN {
// If one of the values is NaN, minimum is NaN
[self writeInitialData:@{@"sum" : @(NAN)}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMinimum:5]}];
[self expectLocalAndRemoteNaN];

[self writeInitialData:@{@"sum" : @5}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMinimum:NAN]}];
[self expectLocalAndRemoteNaN];
}

- (void)testMaximumWithNaN {
// If one of the values is NaN, maximum is NaN
[self writeInitialData:@{@"sum" : @(NAN)}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForIntegerMaximum:5]}];
[self expectLocalAndRemoteNaN];

[self writeInitialData:@{@"sum" : @5}];
[self updateDocumentRef:_docRef data:@{@"sum" : [FIRFieldValue fieldValueForDoubleMaximum:NAN]}];
[self expectLocalAndRemoteNaN];
}

@end
12 changes: 12 additions & 0 deletions Firestore/Source/API/FIRFieldValue+Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,16 @@ NS_ASSUME_NONNULL_BEGIN
@property(strong, nonatomic, readonly) NSNumber *operand;
@end

/** FIRFieldValue class for number minimum transforms. */
@interface FSTNumericMinimumFieldValue : FIRFieldValue
- (instancetype)init NS_UNAVAILABLE;
@property(strong, nonatomic, readonly) NSNumber *operand;
@end

/** FIRFieldValue class for number maximum transforms. */
@interface FSTNumericMaximumFieldValue : FIRFieldValue
- (instancetype)init NS_UNAVAILABLE;
@property(strong, nonatomic, readonly) NSNumber *operand;
@end

NS_ASSUME_NONNULL_END
58 changes: 58 additions & 0 deletions Firestore/Source/API/FIRFieldValue.mm
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,48 @@ - (NSString *)methodName {

@end

#pragma mark - FSTNumericMinimumFieldValue

/* FieldValue class for minimum() transforms. */
@interface FSTNumericMinimumFieldValue ()
- (instancetype)initWithOperand:(NSNumber *)operand;
@end

@implementation FSTNumericMinimumFieldValue
- (instancetype)initWithOperand:(NSNumber *)operand {
if (self = [super initPrivate]) {
_operand = operand;
}
return self;
}

- (NSString *)methodName {
return @"FieldValue.minimum()";
}

@end

#pragma mark - FSTNumericMaximumFieldValue

/* FieldValue class for maximum() transforms. */
@interface FSTNumericMaximumFieldValue ()
- (instancetype)initWithOperand:(NSNumber *)operand;
@end

@implementation FSTNumericMaximumFieldValue
- (instancetype)initWithOperand:(NSNumber *)operand {
if (self = [super initPrivate]) {
_operand = operand;
}
return self;
}

- (NSString *)methodName {
return @"FieldValue.maximum()";
}

@end

#pragma mark - FIRFieldValue

@implementation FIRFieldValue
Expand Down Expand Up @@ -177,6 +219,22 @@ + (instancetype)fieldValueForIntegerIncrement:(int64_t)l {
return [[FSTNumericIncrementFieldValue alloc] initWithOperand:@(l)];
}

+ (instancetype)fieldValueForDoubleMinimum:(double)d {
return [[FSTNumericMinimumFieldValue alloc] initWithOperand:@(d)];
}

+ (instancetype)fieldValueForIntegerMinimum:(int64_t)l {
return [[FSTNumericMinimumFieldValue alloc] initWithOperand:@(l)];
}

+ (instancetype)fieldValueForDoubleMaximum:(double)d {
return [[FSTNumericMaximumFieldValue alloc] initWithOperand:@(d)];
}

+ (instancetype)fieldValueForIntegerMaximum:(int64_t)l {
return [[FSTNumericMaximumFieldValue alloc] initWithOperand:@(l)];
}

+ (nonnull FIRVectorValue *)vectorWithArray:(nonnull NSArray<NSNumber *> *)array {
return [[FIRVectorValue alloc] initWithArray:array];
}
Expand Down
16 changes: 16 additions & 0 deletions Firestore/Source/API/FSTUserDataReader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
using firebase::firestore::model::FieldTransform;
using firebase::firestore::model::NullValue;
using firebase::firestore::model::NumericIncrementTransform;
using firebase::firestore::model::NumericMaximumTransform;
using firebase::firestore::model::NumericMinimumTransform;
using firebase::firestore::model::ObjectValue;
using firebase::firestore::model::ResourcePath;
using firebase::firestore::model::ServerTimestampTransform;
Expand Down Expand Up @@ -453,6 +455,20 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex

context.AddToFieldTransforms(*context.path(), std::move(numeric_increment));

} else if ([fieldValue isKindOfClass:[FSTNumericMinimumFieldValue class]]) {
auto *numericMinimumFieldValue = (FSTNumericMinimumFieldValue *)fieldValue;
auto operand = [self parsedQueryValue:numericMinimumFieldValue.operand];
NumericMinimumTransform numeric_minimum(std::move(operand));

context.AddToFieldTransforms(*context.path(), std::move(numeric_minimum));

} else if ([fieldValue isKindOfClass:[FSTNumericMaximumFieldValue class]]) {
auto *numericMaximumFieldValue = (FSTNumericMaximumFieldValue *)fieldValue;
auto operand = [self parsedQueryValue:numericMaximumFieldValue.operand];
NumericMaximumTransform numeric_maximum(std::move(operand));

context.AddToFieldTransforms(*context.path(), std::move(numeric_maximum));

} else {
HARD_FAIL("Unknown FIRFieldValue type: %s", NSStringFromClass([fieldValue class]));
}
Expand Down
48 changes: 48 additions & 0 deletions Firestore/Source/Public/FirebaseFirestore/FIRFieldValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,54 @@ NS_SWIFT_NAME(FieldValue)
*/
+ (instancetype)fieldValueForIntegerIncrement:(int64_t)l NS_SWIFT_NAME(increment(_:));

/**
* Returns a special value that can be used with `setData()` or `updateData()` that tells the server
* to set the field to the minimum of its current value and the given value.
*
* If the current field value is not an integer or double, or if the field does not yet exist,
* the transformation will set the field to the given value.
*
* @param d The double value to compare.
* @return The `FieldValue` sentinel for use in a call to `setData()` or `updateData()`.
*/
+ (instancetype)fieldValueForDoubleMinimum:(double)d NS_SWIFT_NAME(minimum(_:));

/**
* Returns a special value that can be used with `setData()` or `updateData()` that tells the server
* to set the field to the minimum of its current value and the given value.
*
* If the current field value is not an integer or double, or if the field does not yet exist,
* the transformation will set the field to the given value.
*
* @param l The integer value to compare.
* @return The `FieldValue` sentinel for use in a call to `setData()` or `updateData()`.
*/
+ (instancetype)fieldValueForIntegerMinimum:(int64_t)l NS_SWIFT_NAME(minimum(_:));

/**
* Returns a special value that can be used with `setData()` or `updateData()` that tells the server
* to set the field to the maximum of its current value and the given value.
*
* If the current field value is not an integer or double, or if the field does not yet exist,
* the transformation will set the field to the given value.
*
* @param d The double value to compare.
* @return The `FieldValue` sentinel for use in a call to `setData()` or `updateData()`.
*/
+ (instancetype)fieldValueForDoubleMaximum:(double)d NS_SWIFT_NAME(maximum(_:));

/**
* Returns a special value that can be used with `setData()` or `updateData()` that tells the server
* to set the field to the maximum of its current value and the given value.
*
* If the current field value is not an integer or double, or if the field does not yet exist,
* the transformation will set the field to the given value.
*
* @param l The integer value to compare.
* @return The `FieldValue` sentinel for use in a call to `setData()` or `updateData()`.
*/
+ (instancetype)fieldValueForIntegerMaximum:(int64_t)l NS_SWIFT_NAME(maximum(_:));

/**
* Creates a new `VectorValue` constructed with a copy of the given array of NSNumbers.
*
Expand Down
Loading
Loading