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
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class FlutterFirebaseFirestoreMessageCodec extends StandardMessageCodec {
private static final byte DATA_TYPE_FIRESTORE_QUERY = (byte) 197;
private static final byte DATA_TYPE_FIRESTORE_SETTINGS = (byte) 198;
private static final byte DATA_TYPE_VECTOR_VALUE = (byte) 199;
private static final byte DATA_TYPE_MINIMUM_DOUBLE = (byte) 200;
private static final byte DATA_TYPE_MINIMUM_INTEGER = (byte) 201;
private static final byte DATA_TYPE_MAXIMUM_DOUBLE = (byte) 202;
private static final byte DATA_TYPE_MAXIMUM_INTEGER = (byte) 203;

@Override
protected void writeValue(ByteArrayOutputStream stream, Object value) {
Expand Down Expand Up @@ -273,6 +277,18 @@ protected Object readValueOfType(byte type, ByteBuffer buffer) {
case DATA_TYPE_INCREMENT_DOUBLE:
final Number doubleIncrementValue = (Number) readValue(buffer);
return FieldValue.increment(doubleIncrementValue.doubleValue());
case DATA_TYPE_MINIMUM_INTEGER:
final Number integerMinimumValue = (Number) readValue(buffer);
return FieldValue.minimum(integerMinimumValue.intValue());
case DATA_TYPE_MINIMUM_DOUBLE:
final Number doubleMinimumValue = (Number) readValue(buffer);
return FieldValue.minimum(doubleMinimumValue.doubleValue());
case DATA_TYPE_MAXIMUM_INTEGER:
final Number integerMaximumValue = (Number) readValue(buffer);
return FieldValue.maximum(integerMaximumValue.intValue());
case DATA_TYPE_MAXIMUM_DOUBLE:
final Number doubleMaximumValue = (Number) readValue(buffer);
return FieldValue.maximum(doubleMaximumValue.doubleValue());
case DATA_TYPE_DOCUMENT_ID:
return FieldPath.documentId();
case DATA_TYPE_FIRESTORE_INSTANCE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

void runFieldValueTests() {
Expand Down Expand Up @@ -62,6 +63,75 @@ void runFieldValueTests() {
});
});

group('FieldValue.minimum() / FieldValue.maximum()', () {
test(
'minimum sets the smaller of the current value and the operand',
() async {
DocumentReference<Map<String, dynamic>> doc =
await initializeTest('field-value-minimum-existing');
await doc.set({'foo': 80});
await doc.update({'foo': FieldValue.minimum(50)});
DocumentSnapshot<Map<String, dynamic>> snapshot = await doc.get();
expect(snapshot.data()!['foo'], equals(50));

await doc.update({'foo': FieldValue.minimum(100)});
snapshot = await doc.get();
expect(snapshot.data()!['foo'], equals(50));
},
skip: defaultTargetPlatform == TargetPlatform.windows
? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.'
: null,
);

test(
'maximum sets the larger of the current value and the operand',
() async {
DocumentReference<Map<String, dynamic>> doc =
await initializeTest('field-value-maximum-existing');
await doc.set({'foo': 80});
await doc.update({'foo': FieldValue.maximum(100)});
DocumentSnapshot<Map<String, dynamic>> snapshot = await doc.get();
expect(snapshot.data()!['foo'], equals(100));

await doc.update({'foo': FieldValue.maximum(50)});
snapshot = await doc.get();
expect(snapshot.data()!['foo'], equals(100));
},
skip: defaultTargetPlatform == TargetPlatform.windows
? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.'
: null,
);

test(
'minimum sets the operand when the field does not exist',
() async {
DocumentReference<Map<String, dynamic>> doc =
await initializeTest('field-value-minimum-not-exists');
await doc.set({'foo': FieldValue.minimum(50)});
DocumentSnapshot<Map<String, dynamic>> snapshot = await doc.get();
expect(snapshot.data()!['foo'], equals(50));
},
skip: defaultTargetPlatform == TargetPlatform.windows
? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.'
: null,
);

test(
'maximum replaces a non-numeric field with the operand',
() async {
DocumentReference<Map<String, dynamic>> doc =
await initializeTest('field-value-maximum-non-numeric');
await doc.set({'foo': 'bar'});
await doc.update({'foo': FieldValue.maximum(7)});
DocumentSnapshot<Map<String, dynamic>> snapshot = await doc.get();
expect(snapshot.data()!['foo'], equals(7));
},
skip: defaultTargetPlatform == TargetPlatform.windows
? 'The Firebase C++ SDK does not expose FieldValue.minimum/maximum.'
: null,
);
});

group('FieldValue.serverTimestamp()', () {
test('sets a new server time value', () async {
DocumentReference<Map<String, dynamic>> doc =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ - (id)readValueOfType:(UInt8)type {
[FIRFieldValue fieldValueForDoubleIncrement:((NSNumber *)[self readValue]).doubleValue];
case FirestoreDataTypeIncrementInteger:
return [FIRFieldValue fieldValueForIntegerIncrement:((NSNumber *)[self readValue]).intValue];
case FirestoreDataTypeMinimumDouble:
return [FIRFieldValue fieldValueForDoubleMinimum:((NSNumber *)[self readValue]).doubleValue];
case FirestoreDataTypeMinimumInteger:
return [FIRFieldValue fieldValueForIntegerMinimum:((NSNumber *)[self readValue]).intValue];
case FirestoreDataTypeMaximumDouble:
return [FIRFieldValue fieldValueForDoubleMaximum:((NSNumber *)[self readValue]).doubleValue];
case FirestoreDataTypeMaximumInteger:
return [FIRFieldValue fieldValueForIntegerMaximum:((NSNumber *)[self readValue]).intValue];
case FirestoreDataTypeDocumentId:
return [FIRFieldPath documentID];
case FirestoreDataTypeFirestoreInstance:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ typedef NS_ENUM(UInt8, FirestoreDataType) {
FirestoreDataTypeFirestoreQuery = 197,
FirestoreDataTypeFirestoreSettings = 198,
FirestoreDataTypeVectorValue = 199,
FirestoreDataTypeMinimumDouble = 200,
FirestoreDataTypeMinimumInteger = 201,
FirestoreDataTypeMaximumDouble = 202,
FirestoreDataTypeMaximumInteger = 203,
};

@interface FLTFirebaseFirestoreReaderWriter : FlutterStandardReaderWriter
Expand Down
14 changes: 14 additions & 0 deletions packages/cloud_firestore/cloud_firestore/lib/src/field_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ class FieldValue extends FieldValuePlatform {
static FieldValue increment(num value) =>
FieldValue._(_factory.increment(value));

/// Returns a special value for use with set() or update() that tells the
/// server to set the field to the minimum of its current value and [value].
///
/// If the current field value is not an integer or double, or if the field
/// does not yet exist, the transformation sets the field to [value].
static FieldValue minimum(num value) => FieldValue._(_factory.minimum(value));

/// Returns a special value for use with set() or update() that tells the
/// server to set the field to the maximum of its current value and [value].
///
/// If the current field value is not an integer or double, or if the field
/// does not yet exist, the transformation sets the field to [value].
static FieldValue maximum(num value) => FieldValue._(_factory.maximum(value));

dynamic _delegate;

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ void main() {
isTrue,
);
expect(FieldValue.delete() == FieldValue.serverTimestamp(), isFalse);
expect(FieldValue.minimum(1) == FieldValue.minimum(1), isTrue);
expect(FieldValue.maximum(1) == FieldValue.maximum(1), isTrue);
expect(FieldValue.minimum(1) == FieldValue.maximum(1), isFalse);
expect(FieldValue.minimum(1) == FieldValue.increment(1), isFalse);
expect(FieldValue.minimum(1) == FieldValue.minimum(2), isFalse);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <map>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>

Expand Down Expand Up @@ -207,6 +208,17 @@ cloud_firestore_windows::FirestoreCodec::ReadValueOfType(
return CustomEncodableValue(FieldValue::Increment(incrementValue));
}

case DATA_TYPE_MINIMUM_DOUBLE:
case DATA_TYPE_MINIMUM_INTEGER:
case DATA_TYPE_MAXIMUM_DOUBLE:
case DATA_TYPE_MAXIMUM_INTEGER: {
// Consume the encoded operand so the codec stays in sync.
FirestoreCodec::ReadValue(stream);
throw std::runtime_error(
"FieldValue.minimum() and FieldValue.maximum() are not supported on "
"Windows until the Firebase C++ SDK exposes them.");
}

case DATA_TYPE_DOCUMENT_ID: {
return CustomEncodableValue(FieldPath::DocumentId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class FirestoreCodec : public flutter::StandardCodecSerializer {
static const uint8_t DATA_TYPE_FIRESTORE_INSTANCE = 196;
static const uint8_t DATA_TYPE_FIRESTORE_QUERY = 197;
static const uint8_t DATA_TYPE_FIRESTORE_SETTINGS = 198;
static const uint8_t DATA_TYPE_MINIMUM_DOUBLE = 200;
static const uint8_t DATA_TYPE_MINIMUM_INTEGER = 201;
static const uint8_t DATA_TYPE_MAXIMUM_DOUBLE = 202;
static const uint8_t DATA_TYPE_MAXIMUM_INTEGER = 203;

FirestoreCodec();
inline static FirestoreCodec& GetInstance() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ enum FieldValueType {

/// increment or decrement a numeric field value using an integer value
incrementInteger,

/// set a numeric field to the minimum of its current value and a double
minimumDouble,

/// set a numeric field to the minimum of its current value and an integer
minimumInteger,

/// set a numeric field to the maximum of its current value and a double
maximumDouble,

/// set a numeric field to the maximum of its current value and an integer
maximumInteger,
}

/// Default, `MethodChannel`-based delegate for a [FieldValuePlatform].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,43 @@ class MethodChannelFieldValueFactory extends FieldValueFactoryPlatform {
MethodChannelFieldValue(FieldValueType.delete, null);

@override
MethodChannelFieldValue increment(num value) {
MethodChannelFieldValue increment(num value) => _fromNum(
value,
FieldValueType.incrementDouble,
FieldValueType.incrementInteger,
);

@override
MethodChannelFieldValue minimum(num value) => _fromNum(
value,
FieldValueType.minimumDouble,
FieldValueType.minimumInteger,
);

@override
MethodChannelFieldValue maximum(num value) => _fromNum(
value,
FieldValueType.maximumDouble,
FieldValueType.maximumInteger,
);

MethodChannelFieldValue _fromNum(
num value,
FieldValueType doubleType,
FieldValueType integerType,
) {
// It is a compile-time error for any type other than `int` or `double` to
// attempt to extend or implement `num`.
assert(value is int || value is double);
if (value is double) {
return MethodChannelFieldValue(FieldValueType.incrementDouble, value);
return MethodChannelFieldValue(doubleType, value);
// ignore: avoid_double_and_int_checks
} else if (value is int) {
return MethodChannelFieldValue(FieldValueType.incrementInteger, value);
return MethodChannelFieldValue(integerType, value);
}

throw StateError(
'MethodChannelFieldValue().increment() expects a "num" value');
'MethodChannelFieldValue() numeric factories expect a "num" value');
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class FirestoreMessageCodec extends StandardMessageCodec {
static const int _kFirestoreQuery = 197;
static const int _kFirestoreSettings = 198;
static const int _kVectorValue = 199;
static const int _kMinimumDouble = 200;
static const int _kMinimumInteger = 201;
static const int _kMaximumDouble = 202;
static const int _kMaximumInteger = 203;

static const Map<FieldValueType, int> _kFieldValueCodes =
<FieldValueType, int>{
Expand All @@ -53,6 +57,17 @@ class FirestoreMessageCodec extends StandardMessageCodec {
FieldValueType.serverTimestamp: _kServerTimestamp,
FieldValueType.incrementDouble: _kIncrementDouble,
FieldValueType.incrementInteger: _kIncrementInteger,
FieldValueType.minimumDouble: _kMinimumDouble,
FieldValueType.minimumInteger: _kMinimumInteger,
FieldValueType.maximumDouble: _kMaximumDouble,
FieldValueType.maximumInteger: _kMaximumInteger,
};

static const Map<FieldValueType, int> _kIntegerToDoubleCodes =
<FieldValueType, int>{
FieldValueType.incrementInteger: _kIncrementDouble,
FieldValueType.minimumInteger: _kMinimumDouble,
FieldValueType.maximumInteger: _kMaximumDouble,
};

static const Map<FieldPathType, int> _kFieldPathCodes = <FieldPathType, int>{
Expand Down Expand Up @@ -84,14 +99,13 @@ class FirestoreMessageCodec extends StandardMessageCodec {
buffer.putUint8List(value.bytes);
} else if (value is FieldValuePlatform) {
MethodChannelFieldValue delegate = FieldValuePlatform.getDelegate(value);
final int code = _kFieldValueCodes[delegate.type]!;
// We turn int superior to 2^32 into double here to avoid precision loss.
if (delegate.type == FieldValueType.incrementInteger &&
if (_kIntegerToDoubleCodes.containsKey(delegate.type) &&
(delegate.value > 2147483647 || delegate.value < -2147483648)) {
buffer.putUint8(_kIncrementDouble);
buffer.putUint8(_kIntegerToDoubleCodes[delegate.type]!);
writeValue(buffer, (delegate.value as int).toDouble());
} else {
buffer.putUint8(code);
buffer.putUint8(_kFieldValueCodes[delegate.type]!);
if (delegate.value != null) writeValue(buffer, delegate.value);
}
} else if (value is FieldPathType) {
Expand Down Expand Up @@ -181,6 +195,10 @@ class FirestoreMessageCodec extends StandardMessageCodec {
case _kServerTimestamp:
case _kIncrementDouble:
case _kIncrementInteger:
case _kMinimumDouble:
case _kMinimumInteger:
case _kMaximumDouble:
case _kMaximumInteger:
default:
return super.readValueOfType(type, buffer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,22 @@ abstract class FieldValueFactoryPlatform extends PlatformInterface {
dynamic increment(num value) {
throw UnimplementedError('increment() is not implemented');
}

/// Returns a special value for use with set() or update() that tells the
/// server to set the field to the minimum of its current value and [value].
///
/// If the current field value is not an integer or double, or if the field
/// does not yet exist, the transformation sets the field to [value].
dynamic minimum(num value) {
throw UnimplementedError('minimum() is not implemented');
}

/// Returns a special value for use with set() or update() that tells the
/// server to set the field to the maximum of its current value and [value].
///
/// If the current field value is not an integer or double, or if the field
/// does not yet exist, the transformation sets the field to [value].
dynamic maximum(num value) {
throw UnimplementedError('maximum() is not implemented');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class FieldValueFactoryWeb extends FieldValueFactoryPlatform {
FieldValueWeb increment(num value) =>
FieldValueWeb(firestore_interop.FieldValue.increment(value));

@override
FieldValueWeb minimum(num value) =>
FieldValueWeb(firestore_interop.FieldValue.minimum(value));

@override
FieldValueWeb maximum(num value) =>
FieldValueWeb(firestore_interop.FieldValue.maximum(value));

@override
FieldValueWeb serverTimestamp() =>
FieldValueWeb(firestore_interop.FieldValue.serverTimestamp());
Expand Down
Loading
Loading