mirror of
https://github.com/immich-app/isar.git
synced 2026-09-30 13:23:02 +08:00
Fix issue with multi-filters
This commit is contained in:
@@ -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
Reference in New Issue
Block a user