Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Decimal128 #1192

Merged
merged 30 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
21b15a0
Bind ieee754 compliant bid128_ functions
nielsenko Feb 28, 2023
f83a1eb
WIP
nielsenko Mar 1, 2023
5bf5593
Add Decimal128 + test
nielsenko Mar 1, 2023
aacb9d0
Add Decimal128.abs + more test
nielsenko Mar 1, 2023
8c11209
More tests
nielsenko Mar 1, 2023
153f9a3
Refactor library initialization to be independent of RealmCore
nielsenko Mar 2, 2023
5e19d0e
Ups!
nielsenko Apr 11, 2023
b57e37f
Add decimal128 test to flutter driver tests!
nielsenko Apr 11, 2023
906ebc7
Use alignas(16) to align realm_decimal_128 on 16 bytes boundary
nielsenko Apr 11, 2023
12cbb01
TEST: Downgrade ffigen to 6.0.1
nielsenko Apr 12, 2023
b2a0a51
Remove stale bid_* links
nielsenko Apr 12, 2023
aa71309
Sidestep alingment issues by using to_capi/from_capu and copy shit ar…
nielsenko Apr 14, 2023
8c847a6
Use plain v13.6.0 again
nielsenko Apr 14, 2023
871c503
Update CHANGELOG
nielsenko Apr 14, 2023
c9ef639
Update doc comments
nielsenko Apr 14, 2023
2c121de
Fix comparison behavior to match IEEE754 for NaN values
nielsenko Apr 14, 2023
2c36b62
Add Decimal128.isNaN getter
nielsenko Apr 14, 2023
294cb95
Test a few more IEEE754 corners
nielsenko Apr 14, 2023
d804480
Get ieee754 total ordering right in compareTo
nielsenko Apr 17, 2023
b12c048
Drop dependency on core for Decimal128 and use IntelRDFPMathLib20U2 l…
nielsenko Apr 20, 2023
501b244
Fix prior (and minor) spelling mistake
nielsenko Apr 20, 2023
c16c2d9
Export Decimal128 class from realm_class.dart
nielsenko May 2, 2023
ba9468d
Allow Decimal128 properties on models
nielsenko May 2, 2023
2fa6228
Work-around to Dart FFI issue
nielsenko May 3, 2023
71f0628
More tests
nielsenko May 9, 2023
6c99a27
Support Decimal128 in mixed + tests
nielsenko May 9, 2023
a137cae
Change libraryVersion regex
nielsenko May 12, 2023
943205e
Move decimal128.dart to native as parts file of realm_core.dart (EXPE…
nielsenko May 12, 2023
cfebff6
PR feedback
nielsenko May 15, 2023
cf719b9
PR feedback (more tests)
nielsenko May 16, 2023
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
4 changes: 2 additions & 2 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ jobs:
id: update-libraryVersion
uses: jacobtomlinson/gha-find-replace@b76729678e8d52dadb12e0e16454a93e301a919d #! 2.0.0
with:
find: "static const libraryVersion = '[^']*';"
replace: "static const libraryVersion = '${{ steps.update-changelog.outputs.new-version }}';"
find: "const libraryVersion = '[^']*';"
replace: "const libraryVersion = '${{ steps.update-changelog.outputs.new-version }}';"
include: 'lib/src/native/realm_core.dart'

- name: Make sure we updated libraryVersion in realm_core.dart
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* Added `SyncWebSocketError` and `SyncWebSocketErrorCode` for web socket connection sync errors ([#1182](https://github.com/realm/realm-dart/pull/1182)).
* Added `FlexibleSyncConfiguration.shouldCompactCallback` support ([#1204](https://github.com/realm/realm-dart/pull/1204)).
* Added `RealmSet.asResults()` ([#1214](https://github.com/realm/realm-dart/pull/1214)).
* Support `Decimal128` datatype ([#1192](https://github.com/realm/realm-dart/pull/1192)).

### Fixed
* You may have a crash on Windows if you try to open a file with non-ASCII path (Core upgrade).
Expand Down
6 changes: 3 additions & 3 deletions common/lib/src/realm_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class RealmStateError extends StateError implements RealmError {
}

/// @nodoc
class Decimal128 {} // TODO Support decimal128 datatype https://github.com/realm/realm-dart/issues/725
abstract class Decimal128 {}

/// @nodoc
abstract class RealmObjectBaseMarker {}
Expand Down Expand Up @@ -158,7 +158,7 @@ class RealmValue {
const RealmValue.realmObject(RealmObjectMarker o) : this._(o);
const RealmValue.dateTime(DateTime timestamp) : this._(timestamp);
const RealmValue.objectId(ObjectId id) : this._(id);
// const RealmValue.decimal128(Decimal128 decimal) : this._(decimal); // not supported yet
const RealmValue.decimal128(Decimal128 decimal) : this._(decimal);
const RealmValue.uuid(Uuid uuid) : this._(uuid);

/// Will throw [ArgumentError]
Expand All @@ -172,7 +172,7 @@ class RealmValue {
o is RealmObjectMarker ||
o is DateTime ||
o is ObjectId ||
// o is Decimal128 || // not supported yet
o is Decimal128 ||
o is Uuid) {
return RealmValue._(o);
} else {
Expand Down
12 changes: 7 additions & 5 deletions ffigen/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ headers:
entry-points:
- 'realm.h'
- 'realm_dart.h'
- 'realm_dart_decimal128.h'
- 'realm_dart_scheduler.h'
- 'realm_dart_sync.h'
include-directives: #generate only for these headers
include-directives: # generate only for these headers
- 'realm.h'
- 'realm_dart.h'
- 'realm_dart_decimal128.h'
- 'realm_dart_scheduler.h'
- 'realm_dart_sync.h'
compiler-opts:
Expand All @@ -21,20 +23,20 @@ compiler-opts:
sort: true
functions:
exclude:
- '_.*' #exclude all starting with _
- 'Dart_.*' #exlude all starting with Dart_
- '_.*' # exclude all starting with _
- 'Dart_.*' # exclude all starting with Dart_
symbol-address:
include:
- 'realm_dart_.*'
- 'realm_release'
structs:
exclude:
- '_.*'
- 'Dart_.*' #exlude all starting with Dart_
- 'Dart_.*' # exclude all starting with Dart_
globals:
exclude:
- '_.*'
- 'RLM_.*' #exlude RLM_ globals constants since they are not exported from binary
- 'RLM_.*' # exclude RLM_ globals constants since they are not exported from binary
unions:
exclude:
- '_.*'
Expand Down
1 change: 1 addition & 0 deletions ffigen/realm_dart_decimal128.h
2 changes: 2 additions & 0 deletions flutter/realm_flutter/tests/test_driver/realm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import '../test/backlinks_test.dart' as backlinks_test;
import '../test/client_reset_test.dart' as client_reset_test;
import '../test/configuration_test.dart' as configuration_test;
import '../test/credentials_test.dart' as credentials_test;
import '../test/decimal128_test.dart' as decimal128_test;
import '../test/dynamic_realm_test.dart' as dynamic_realm_test;
import '../test/embedded_test.dart' as embedded_test;
import '../test/indexed_test.dart' as indexed_test;
Expand All @@ -36,6 +37,7 @@ Future<String> main(List<String> args) async {
await client_reset_test.main(args);
await configuration_test.main(args);
await credentials_test.main(args);
await decimal128_test.main(args);
await dynamic_realm_test.main(args);
await embedded_test.main(args);
indexed_test.main(args);
Expand Down
5 changes: 3 additions & 2 deletions generator/lib/src/dart_type_ex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import 'dart:ffi';

import 'dart:typed_data';

import 'package:analyzer/dart/element/nullability_suffix.dart';
Expand All @@ -29,6 +29,7 @@ import 'type_checkers.dart';

extension DartTypeEx on DartType {
bool isExactly<T>() => TypeChecker.fromRuntime(T).isExactlyType(this);
bool isA<T>() => TypeChecker.fromRuntime(T).isAssignableFromType(this);

bool get isRealmValue => const TypeChecker.fromRuntime(RealmValue).isAssignableFromType(this);
bool get isRealmCollection => realmCollectionType != RealmCollectionType.none;
Expand Down Expand Up @@ -112,7 +113,7 @@ extension DartTypeEx on DartType {
if (isRealmValue) return RealmPropertyType.mixed;
if (isExactly<DateTime>()) return RealmPropertyType.timestamp;
if (isDartCoreNum || isDartCoreDouble) return RealmPropertyType.double;
if (isExactly<Decimal128>()) return RealmPropertyType.decimal128;
if (isA<Decimal128>()) return RealmPropertyType.decimal128;
if (isRealmModel) return RealmPropertyType.object;
if (isDartCoreIterable) return RealmPropertyType.linkingObjects;
if (isExactly<ObjectId>()) return RealmPropertyType.objectid;
Expand Down
2 changes: 1 addition & 1 deletion generator/lib/src/field_element_ex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ extension FieldElementEx on FieldElement {
final backlink = backlinkInfo;

// Check for as-of-yet unsupported type
if (type.isDartCoreMap || type.isExactly<Decimal128>()) {
if (type.isDartCoreMap) {
throw RealmInvalidGenerationSourceError(
'Field type not supported yet',
element: this,
Expand Down
4 changes: 3 additions & 1 deletion generator/test/good_test_data/all_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class _Bar {

late RealmValue any;
late List<RealmValue> manyAny;

late Decimal128 decimal;
}

@RealmModel()
Expand All @@ -50,4 +52,4 @@ class _PrimitiveTypes {
late DateTime dateProp;
late double doubleProp;
late ObjectId objectIdProp;
}
}
11 changes: 10 additions & 1 deletion generator/test/good_test_data/all_types.expected
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class Bar extends _Bar with RealmEntity, RealmObjectBase, RealmObject {
bool aBool,
bool another,
ObjectId objectId,
Uuid uuid, {
Uuid uuid,
Decimal128 decimal, {
Uint8List data = Uint8List(16),
DateTime timestamp = DateTime.now(),
double aDouble = 0.0,
Expand Down Expand Up @@ -85,6 +86,7 @@ class Bar extends _Bar with RealmEntity, RealmObjectBase, RealmObject {
RealmObjectBase.set(this, 'uuid', uuid);
RealmObjectBase.set(this, 'anOptionalString', anOptionalString);
RealmObjectBase.set(this, 'any', any);
RealmObjectBase.set(this, 'decimal', decimal);
RealmObjectBase.set<RealmList<int>>(this, 'list', RealmList<int>(list));
RealmObjectBase.set<RealmList<RealmValue>>(
this, 'manyAny', RealmList<RealmValue>(manyAny));
Expand Down Expand Up @@ -167,6 +169,12 @@ class Bar extends _Bar with RealmEntity, RealmObjectBase, RealmObject {
set manyAny(covariant RealmList<RealmValue> value) =>
throw RealmUnsupportedSetError();

@override
Decimal128 get decimal =>
RealmObjectBase.get<Decimal128>(this, 'decimal') as Decimal128;
@override
set decimal(Decimal128 value) => RealmObjectBase.set(this, 'decimal', value);

@override
RealmResults<Foo> get foos =>
RealmObjectBase.get<Foo>(this, 'foos') as RealmResults<Foo>;
Expand Down Expand Up @@ -204,6 +212,7 @@ class Bar extends _Bar with RealmEntity, RealmObjectBase, RealmObject {
SchemaProperty('any', RealmPropertyType.mixed, optional: true),
SchemaProperty('manyAny', RealmPropertyType.mixed,
optional: true, collectionType: RealmCollectionType.list),
SchemaProperty('decimal', RealmPropertyType.decimal128),
SchemaProperty('foos', RealmPropertyType.linkingObjects,
linkOriginProperty: 'bar',
collectionType: RealmCollectionType.list,
Expand Down
149 changes: 149 additions & 0 deletions lib/src/native/decimal128.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2023 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

part of 'realm_core.dart';

/// A 128-bit decimal floating point number.
class Decimal128 extends Comparable<Decimal128> implements common.Decimal128 {
/// The value 0.
static final zero = Decimal128.fromInt(0);

/// The value 1.
static final one = Decimal128.fromInt(1);

/// The value 10.
static final ten = Decimal128.fromInt(10);

/// The value NaN.
static final nan = Decimal128._(_realmLib.realm_dart_decimal128_nan());

/// The value +Inf.
static final infinity = one / zero; // +Inf

/// The value -Inf.
static final negativeInfinity = -infinity;

final realm_decimal128_t _value;

Decimal128._(this._value);

static final _validInput = RegExp(r'^[+-]?((\d+\.?\d*|\d*\.?\d+)([eE][+-]?\d+)?|NaN|Inf(inity)?)$');

/// Parses a string into a [Decimal128]. Returns `null` if the string is not a valid [Decimal128].
static Decimal128? tryParse(String source) {
if (!_validInput.hasMatch(source)) return null;
return using((arena) {
final result = _realmLib.realm_dart_decimal128_from_string(source.toCharPtr(arena));
return Decimal128._(result);
});
}

/// Parses a string into a [Decimal128]. Throws a [FormatException] if the string is not a valid [Decimal128].
factory Decimal128.parse(String source) {
return tryParse(source) ?? (throw FormatException('Invalid Decimal128', source));
}

/// Converts a `int` into a [Decimal128].
factory Decimal128.fromInt(int value) {
return Decimal128._(_realmLib.realm_dart_decimal128_from_int64(value));
}

/// Converts a `double` into a [Decimal128].
factory Decimal128.fromDouble(double value) {
return Decimal128.parse(value.toString()); // TODO(kn): Find a way to optimize this
}

/// Returns `true` if `this` is NaN.
bool get isNaN => _realmLib.realm_dart_decimal128_is_nan(_value);

/// Adds `this` with `other` and returns a new [Decimal128].
Decimal128 operator +(Decimal128 other) {
return Decimal128._(_realmLib.realm_dart_decimal128_add(_value, other._value));
}

/// Subtracts `other` from `this` and returns a new [Decimal128].
Decimal128 operator -(Decimal128 other) {
return Decimal128._(_realmLib.realm_dart_decimal128_subtract(_value, other._value));
}

/// Multiplies `this` with `other` and returns a new [Decimal128].
Decimal128 operator *(Decimal128 other) {
return Decimal128._(_realmLib.realm_dart_decimal128_multiply(_value, other._value));
}

/// Divides `this` by `other` and returns a new [Decimal128].
Decimal128 operator /(Decimal128 other) {
return Decimal128._(_realmLib.realm_dart_decimal128_divide(_value, other._value));
}

/// Negates `this` and returns a new [Decimal128].
Decimal128 operator -() => Decimal128._(_realmLib.realm_dart_decimal128_negate(_value));

/// Returns the absolute value of `this`.
Decimal128 abs() => this < zero ? -this : this;

/// Returns `true` if `this` and `other` are equal.
@override
// ignore: hash_and_equals
operator ==(Object other) {
// WARNING: Don't use identical to ensure nan != nan,
// if (identical(this, other)) return true;
if (other is Decimal128) {
return _realmLib.realm_dart_decimal128_equal(_value, other._value);
}
return false;
}

/// Returns `true` if `this` is less than `other`.
bool operator <(Decimal128 other) {
return _realmLib.realm_dart_decimal128_less_than(_value, other._value);
}

/// Returns `true` if `this` is less than or equal to `other`.
bool operator <=(Decimal128 other) => compareTo(other) <= 0;

/// Returns `true` if `this` is greater than `other`.
bool operator >(Decimal128 other) {
return _realmLib.realm_dart_decimal128_greater_than(_value, other._value);
}

/// Returns `true` if `this` is greater than or equal to `other`.
bool operator >=(Decimal128 other) => compareTo(other) >= 0;

/// Converts `this` to an `int`. Possibly loosing precision.
int toInt() => _realmLib.realm_dart_decimal128_to_int64(_value);

/// String representation of `this`.
@override
String toString() {
return using((arena) {
final realmString = _realmLib.realm_dart_decimal128_to_string(_value);
return ascii.decode(realmString.data.cast<Uint8>().asTypedList(realmString.size));
});
}

/// Compares `this` to `other`.
@override
int compareTo(Decimal128 other) => _realmLib.realm_dart_decimal128_compare_to(_value, other._value);
}

extension Decimal128Internal on Decimal128 {
realm_decimal128_t get value => _value;

static Decimal128 fromNative(realm_decimal128_t value) => Decimal128._(value);
}
Loading