Fix issue with multi-filters

This commit is contained in:
Simon Leier
2022-09-22 23:40:07 +02:00
parent 84b4ef8079
commit a397a339cb
5 changed files with 386 additions and 2960 deletions
+6
View File
@@ -1,3 +1,9 @@
## 3.0.1
### Fixes
- Fixed issue where `.anyOf()`, `.allOf()`, and `.oneOf()` could not be negated
## 3.0.0
This release has been a lot of work! Thanks to everyone who contributed and joined the countless discussions. You are really awesome!
@@ -18,6 +18,8 @@ extension QueryWhere<OBJ, R> on QueryBuilder<OBJ, R, QWhereClause> {
/// Joins the results of the [modifier] for each item in [items] using logical
/// OR. So an object will be included if it matches at least one of the
/// resulting where clauses.
///
/// If [items] is empty, this is a no-op.
QueryBuilder<OBJ, R, QAfterWhereClause> anyOf<E, RS>(
Iterable<E> items,
WhereRepeatModifier<OBJ, R, E> modifier,
@@ -86,34 +88,48 @@ extension QueryFilterNot<OBJ, R> on QueryBuilder<OBJ, R, QFilterCondition> {
/// Joins the results of the [modifier] for each item in [items] using logical
/// OR. So an object will be included if it matches at least one of the
/// resulting filters.
///
/// If [items] is empty, this is a no-op.
QueryBuilder<OBJ, R, QAfterFilterCondition> anyOf<E, RS>(
Iterable<E> items,
FilterRepeatModifier<OBJ, R, E> modifier,
FilterRepeatModifier<OBJ, OBJ, E> modifier,
) {
QueryBuilder<OBJ, R, QAfterFilterCondition>? q;
for (final e in items) {
q = modifier(q?.or() ?? QueryBuilder(_query), e);
}
return q ?? QueryBuilder(_query);
return QueryBuilder.apply(this, (query) {
return query.group((q) {
var q2 = QueryBuilder<OBJ, OBJ, QAfterFilterCondition>(q._query);
for (final e in items) {
q2 = modifier(q2.or(), e);
}
return q2;
});
});
}
/// Joins the results of the [modifier] for each item in [items] using logical
/// AND. So an object will be included if it matches all of the resulting
/// filters.
///
/// If [items] is empty, this is a no-op.
QueryBuilder<OBJ, R, QAfterFilterCondition> allOf<E, RS>(
Iterable<E> items,
FilterRepeatModifier<OBJ, R, E> modifier,
FilterRepeatModifier<OBJ, OBJ, E> modifier,
) {
QueryBuilder<OBJ, R, QAfterFilterCondition>? q;
for (final e in items) {
q = modifier(q?.and() ?? QueryBuilder(_query), e);
}
return q ?? QueryBuilder(_query);
return QueryBuilder.apply(this, (query) {
return query.group((q) {
var q2 = QueryBuilder<OBJ, OBJ, QAfterFilterCondition>(q._query);
for (final e in items) {
q2 = modifier(q2.and(), e);
}
return q2;
});
});
}
/// Joins the results of the [modifier] for each item in [items] using logical
/// XOR. So an object will be included if it matches exactly one of the
/// resulting filters.
///
/// If [items] is empty, this is a no-op.
QueryBuilder<OBJ, R, QAfterFilterCondition> oneOf<E, RS>(
Iterable<E> items,
FilterRepeatModifier<OBJ, R, E> modifier,
@@ -1,170 +0,0 @@
// ignore_for_file: inference_failure_on_function_invocation
import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
part 'any_of_all_of_test.g.dart';
@collection
class Model {
Model(this.id, this.value);
final Id id;
@Index()
final int value;
@override
bool operator ==(Object other) => other is Model && id == other.id;
@override
int get hashCode => id.hashCode;
}
void main() {
group('AnyOf AllOf', () {
late Isar isar;
late Model model0;
late Model model1;
late Model model2;
late Model model3;
setUp(() async {
model0 = Model(0, 0);
model1 = Model(1, 1);
model2 = Model(2, 2);
model3 = Model(3, 3);
isar = await openTempIsar([ModelSchema]);
await isar.writeTxn(() {
return isar.models.putAll([model0, model1, model2, model3]);
});
});
group('where anyOf', () {
isarTest('zero elements', () async {
final one = isar.models.where().anyOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model0, model1, model2, model3]);
});
isarTest('one matching element', () async {
final one = isar.models.where().anyOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model2]);
});
isarTest('one non-matching element', () async {
final one = isar.models.where().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, []);
});
isarTest('one matching and one non-matching elements', () async {
final one = isar.models.where().anyOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model3]);
});
isarTest('one non-matching element', () async {
final one = isar.models.where().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, []);
});
});
group('filter anyOf', () {
isarTest('zero elements', () async {
final one = isar.models.filter().anyOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model0, model1, model2, model3]);
});
isarTest('one matching element', () async {
final one = isar.models.filter().anyOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model2]);
});
isarTest('one non-matching element', () async {
final one = isar.models.filter().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, []);
});
isarTest('one matching and one non-matching elements', () async {
final one = isar.models.filter().anyOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model3]);
});
isarTest('one non-matching element', () async {
final one = isar.models.filter().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, []);
});
});
group('filter allOf', () {
isarTest('zero elements', () async {
final one = isar.models.filter().allOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model0, model1, model2, model3]);
});
isarTest('one matching element', () async {
final one = isar.models.filter().allOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, [model2]);
});
isarTest('one non-matching element', () async {
final one = isar.models.filter().allOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, []);
});
isarTest('one matching and one non-matching elements', () async {
final one = isar.models.filter().allOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, []);
});
isarTest('one non-matching element', () async {
final one = isar.models.filter().allOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(one, []);
});
});
});
}
@@ -0,0 +1,352 @@
// ignore_for_file: inference_failure_on_function_invocation
import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
part 'multi_filter_test.g.dart';
@collection
class Model {
Model(this.id, this.value);
final Id id;
@Index()
final int value;
@override
bool operator ==(Object other) => other is Model && id == other.id;
@override
int get hashCode => id.hashCode;
}
void main() {
group('Multi filters', () {
late Isar isar;
late Model model0;
late Model model1;
late Model model2;
late Model model3;
setUp(() async {
model0 = Model(0, 0);
model1 = Model(1, 1);
model2 = Model(2, 2);
model3 = Model(3, 3);
isar = await openTempIsar([ModelSchema]);
await isar.writeTxn(() {
return isar.models.putAll([model0, model1, model2, model3]);
});
});
group('where anyOf', () {
isarTest('zero elements', () async {
final q = isar.models.where().anyOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model0, model1, model2, model3]);
});
isarTest('one matching element', () async {
final q = isar.models.where().anyOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model2]);
});
isarTest('two matching elements', () async {
final q = isar.models.where().anyOf(
[0, 2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model0, model2]);
});
isarTest('one non-matching element', () async {
final q = isar.models.where().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
});
isarTest('one matching and one non-matching elements', () async {
final q = isar.models.where().anyOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model3]);
});
isarTest('one non-matching element', () async {
final q = isar.models.where().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
});
});
group('filter anyOf', () {
isarTest('zero elements', () async {
final q = isar.models.filter().anyOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model0, model1, model2, model3]);
final notQ = isar.models.filter().not().anyOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
isarTest('one matching element', () async {
final q = isar.models.filter().anyOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model2]);
final notQ = isar.models.filter().not().anyOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model3]);
});
isarTest('two matching elements', () async {
final q = isar.models.filter().anyOf(
[0, 2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model0, model2]);
final notQ = isar.models.filter().not().anyOf(
[0, 2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model1, model3]);
});
isarTest('one non-matching element', () async {
final q = isar.models.filter().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
final notQ = isar.models.filter().not().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
isarTest('one matching and one non-matching elements', () async {
final q = isar.models.filter().anyOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model3]);
final notQ = isar.models.filter().not().anyOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2]);
});
isarTest('one non-matching element', () async {
final q = isar.models.filter().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
final notQ = isar.models.filter().not().anyOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
});
group('filter allOf', () {
isarTest('zero elements', () async {
final q = isar.models.filter().allOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model0, model1, model2, model3]);
final notQ = isar.models.filter().not().allOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
isarTest('one matching element', () async {
final q = isar.models.filter().allOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model2]);
final notQ = isar.models.filter().not().allOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model3]);
});
isarTest('two matching elements', () async {
final q = isar.models.filter().allOf(
[2, 2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model2]);
final notQ = isar.models.filter().not().allOf(
[2, 2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model3]);
});
isarTest('one non-matching element', () async {
final q = isar.models.filter().allOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
final notQ = isar.models.filter().not().allOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
isarTest('one matching and one non-matching elements', () async {
final q = isar.models.filter().allOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
final notQ = isar.models.filter().not().allOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
isarTest('one non-matching element', () async {
final q = isar.models.filter().allOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
final notQ = isar.models.filter().not().allOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
});
group('filter oneOf', () {
isarTest('zero elements', () async {
final q = isar.models.filter().oneOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model0, model1, model2, model3]);
final notQ = isar.models.filter().not().oneOf(
<int>[],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
isarTest('one matching element', () async {
final q = isar.models.filter().oneOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model2]);
final notQ = isar.models.filter().not().oneOf(
[2],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model3]);
});
isarTest('two matching elements', () async {
final q = isar.models.filter().oneOf(
[2, 2, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model3]);
final notQ = isar.models.filter().not().oneOf(
[2, 2, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2]);
});
isarTest('one non-matching element', () async {
final q = isar.models.filter().oneOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
final notQ = isar.models.filter().not().oneOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
isarTest('one matching and one non-matching elements', () async {
final q = isar.models.filter().oneOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, [model3]);
final notQ = isar.models.filter().not().oneOf(
[7, 3],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2]);
});
isarTest('one non-matching element', () async {
final q = isar.models.filter().oneOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(q, []);
final notQ = isar.models.filter().not().oneOf(
[5],
(q, int element) => q.valueEqualTo(element),
);
await qEqual(notQ, [model0, model1, model2, model3]);
});
});
});
}
File diff suppressed because it is too large Load Diff