Skip to content

Support for DateTime queries #362

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

Closed
wants to merge 2 commits into from
Closed
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
18 changes: 16 additions & 2 deletions generator/lib/src/code_chunks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,10 @@ class CodeChunks {

static String _metaClass(int i, ModelEntity entity) {
final fields = <String>[];

// TODO decide how/when to make the switch - currently using the original behavior.
const useDateQueries = false;

for (var p = 0; p < entity.properties.length; p++) {
final prop = entity.properties[p];
final name = prop.name;
Expand All @@ -570,9 +574,12 @@ class CodeChunks {
case OBXPropertyType.Char:
case OBXPropertyType.Int:
case OBXPropertyType.Long:
fieldType = 'Integer';
break;
case OBXPropertyType.Date:
case OBXPropertyType.DateNano:
fieldType = 'Integer';
// TODO maybe also take into account if this is an integer or DateTime field in the original entity.
fieldType = useDateQueries ? 'Date' : 'Integer';
break;
case OBXPropertyType.Relation:
fieldType = 'Relation';
Expand All @@ -597,7 +604,14 @@ class CodeChunks {
} else {
propCode += 'Query${fieldType}Property<${entity.name}>';
}
propCode += '(_entities[$i].properties[$p]);';
propCode += '(_entities[$i].properties[$p]';
if (useDateQueries && prop.type == OBXPropertyType.Date) {
propCode += ', isDateNano: false);';
} else if (useDateQueries && prop.type == OBXPropertyType.DateNano) {
propCode += ', isDateNano: true);';
} else {
propCode += ');';
}
fields.add(propCode);
}

Expand Down
101 changes: 71 additions & 30 deletions objectbox/lib/src/native/query/query.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,47 +137,84 @@ class QueryByteVectorProperty<EntityT>
_op(val, _ConditionOp.lessOrEq, alias);
}

class QueryIntegerProperty<EntityT> extends QueryProperty<EntityT, int> {
QueryIntegerProperty(ModelProperty model) : super(model);

Condition<EntityT> _op(_ConditionOp cop, int p1, int p2, String? alias) =>
_IntegerCondition<EntityT, int>(cop, this, p1, p2, alias);
mixin _QueryScalarProperty<EntityT, PropertyDartType> {
Condition<EntityT> _op(_ConditionOp cop, PropertyDartType p1,
PropertyDartType? p2, String? alias);

Condition<EntityT> _opList(List<int> list, _ConditionOp cop, String? alias) =>
_IntegerListCondition<EntityT>(cop, this, list, alias);
Condition<EntityT> _opList(
List<PropertyDartType> list, _ConditionOp cop, String? alias);

Condition<EntityT> equals(int p, {String? alias}) =>
_op(_ConditionOp.eq, p, 0, alias);
Condition<EntityT> equals(PropertyDartType p, {String? alias}) =>
_op(_ConditionOp.eq, p, null, alias);

Condition<EntityT> notEquals(int p, {String? alias}) =>
_op(_ConditionOp.notEq, p, 0, alias);
Condition<EntityT> notEquals(PropertyDartType p, {String? alias}) =>
_op(_ConditionOp.notEq, p, null, alias);

Condition<EntityT> greaterThan(int p, {String? alias}) =>
_op(_ConditionOp.gt, p, 0, alias);
Condition<EntityT> greaterThan(PropertyDartType p, {String? alias}) =>
_op(_ConditionOp.gt, p, null, alias);

Condition<EntityT> greaterOrEqual(int p, {String? alias}) =>
_op(_ConditionOp.greaterOrEq, p, 0, alias);
Condition<EntityT> greaterOrEqual(PropertyDartType p, {String? alias}) =>
_op(_ConditionOp.greaterOrEq, p, null, alias);

Condition<EntityT> lessThan(int p, {String? alias}) =>
_op(_ConditionOp.lt, p, 0, alias);
Condition<EntityT> lessThan(PropertyDartType p, {String? alias}) =>
_op(_ConditionOp.lt, p, null, alias);

Condition<EntityT> lessOrEqual(int p, {String? alias}) =>
_op(_ConditionOp.lessOrEq, p, 0, alias);
Condition<EntityT> lessOrEqual(PropertyDartType p, {String? alias}) =>
_op(_ConditionOp.lessOrEq, p, null, alias);

Condition<EntityT> operator <(int p) => lessThan(p);
Condition<EntityT> operator <(PropertyDartType p) => lessThan(p);

Condition<EntityT> operator >(int p) => greaterThan(p);
Condition<EntityT> operator >(PropertyDartType p) => greaterThan(p);

Condition<EntityT> between(int p1, int p2, {String? alias}) =>
Condition<EntityT> between(PropertyDartType p1, PropertyDartType p2,
{String? alias}) =>
_op(_ConditionOp.between, p1, p2, alias);

Condition<EntityT> oneOf(List<int> list, {String? alias}) =>
Condition<EntityT> oneOf(List<PropertyDartType> list, {String? alias}) =>
_opList(list, _ConditionOp.oneOf, alias);

Condition<EntityT> notOneOf(List<int> list, {String? alias}) =>
Condition<EntityT> notOneOf(List<PropertyDartType> list, {String? alias}) =>
_opList(list, _ConditionOp.notOneOf, alias);
}

class QueryIntegerProperty<EntityT> extends QueryProperty<EntityT, int>
with _QueryScalarProperty<EntityT, int> {
QueryIntegerProperty(ModelProperty model) : super(model);

@override
Condition<EntityT> _op(_ConditionOp cop, int p1, int? p2, String? alias) =>
_IntegerCondition<EntityT, int>(cop, this, p1, p2, alias);

@override
Condition<EntityT> _opList(List<int> list, _ConditionOp cop, String? alias) =>
_IntegerListCondition<EntityT, int>(cop, this, list, alias);
}

class QueryDateProperty<EntityT> extends QueryProperty<EntityT, DateTime>
with _QueryScalarProperty<EntityT, DateTime> {
final bool _nano;

QueryDateProperty(ModelProperty model, {required bool isDateNano})
: _nano = isDateNano,
super(model);

@pragma('vm:prefer-inline')
int _convert(DateTime arg) =>
_nano ? arg.microsecondsSinceEpoch * 1000 : arg.millisecondsSinceEpoch;

@override
Condition<EntityT> _op(
_ConditionOp cop, DateTime p1, DateTime? p2, String? alias) =>
_IntegerCondition<EntityT, DateTime>(
cop, this, _convert(p1), p2 == null ? null : _convert(p2), alias);

@override
Condition<EntityT> _opList(
List<DateTime> list, _ConditionOp cop, String? alias) =>
_IntegerListCondition<EntityT, DateTime>(
cop, this, list.map(_convert), alias);
}

class QueryDoubleProperty<EntityT> extends QueryProperty<EntityT, double> {
QueryDoubleProperty(ModelProperty model) : super(model);

Expand Down Expand Up @@ -457,10 +494,13 @@ class _IntegerCondition<EntityT, PropertyDartType>
}
}

class _IntegerListCondition<EntityT>
extends _PropertyCondition<EntityT, int, List<int>> {
_IntegerListCondition(_ConditionOp op, QueryProperty<EntityT, int> prop,
List<int> value, String? alias)
class _IntegerListCondition<EntityT, PropertyDartType>
extends _PropertyCondition<EntityT, PropertyDartType, Iterable<int>> {
_IntegerListCondition(
_ConditionOp op,
QueryProperty<EntityT, PropertyDartType> prop,
Iterable<int> value,
String? alias)
: super(op, prop, value, null, alias);

int _opList<T extends NativeType>(
Expand All @@ -470,10 +510,11 @@ class _IntegerListCondition<EntityT>
void Function(Pointer<T>, int, int) setIndex) {
final length = _value.length;
try {
for (var i = 0; i < length; i++) {
var i = 0;
for (var v in _value) {
// Error: The operator '[]=' isn't defined for the type 'Pointer<T>
// listPtr[i] = _list[i];
setIndex(listPtr, i, _value[i]);
setIndex(listPtr, i++, v);
}
return func(builder._cBuilder, _property._model.id.id, listPtr, length);
} finally {
Expand Down
4 changes: 3 additions & 1 deletion objectbox/test/entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ class TestEntity {
this.tByteList,
this.tInt8List,
this.tUint8List,
this.ignore});
this.ignore,
this.tDate,
this.tDateNano});

TestEntity.filled({
this.id = 1,
Expand Down
27 changes: 27 additions & 0 deletions objectbox/test/query_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -884,4 +884,31 @@ void main() {
expect(query.findFirst, ThrowingInConverters.throwsIn('Setter'));
expect(query.find, ThrowingInConverters.throwsIn('Setter'));
});

test('DateTime', () {
const count = 6;
final dates = [for (var i = 1; i <= count; i++) DateTime.utc(2000, 1, i)];
box.putMany([for (var d in dates) TestEntity(tDate: d, tDateNano: d)]);
expect(box.count(), count); // just a sanity check

final queries = [
// with the old [QueryIntegerProperty]
box.query(TestEntity_.tDate.between(
dates[2].millisecondsSinceEpoch, dates[4].millisecondsSinceEpoch)),
box.query(TestEntity_.tDateNano.between(
dates[2].microsecondsSinceEpoch * 1000,
dates[4].microsecondsSinceEpoch * 1000)),
// with the new [QueryDateProperty]
// box.query(TestEntity_.tDate.between(dates[2], dates[4])),
// box.query(TestEntity_.tDateNano.between(dates[2], dates[4])),
Comment on lines +896 to +903
Copy link
Member

@greenrobot-team greenrobot-team Jan 10, 2022

Choose a reason for hiding this comment

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

Would it be possible to instead let QueryDateProperty expand on QueryIntegerProperty with additional methods that accept DateTime? Then it would be possible to use either int or DateTime?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

those can't be overrides (same name, different param type), thus would need to have a different name, which isn't very good because it would be only specific to this property type (maybe confusing even with its own docs).

Other alternative I've considered is changing the argument to dynamic on QueryDateProperty methods. that way we can make it work with both DateTime and int, though it would be a runtime check whether a proper type was provided...

Copy link
Member

@greenrobot-team greenrobot-team Jan 17, 2022

Choose a reason for hiding this comment

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

Another option (from stand-up): do it the other way round. Still change to DateTime type (breaking change), but add methods that accept int (e.g. betweenMs). Then existing usages can be updated using search + replace and it would still be possible to supply milliseconds directly.

];

for (var query in queries) {
final items = query.build().find();
expect(items.length, 3);
expect(items[0].tDate!.day, 3);
expect(items[1].tDate!.day, 4);
expect(items[2].tDate!.day, 5);
}
});
}