Skip to content

Commit c14674e

Browse files
committed
added query filtering
1 parent 75bc45a commit c14674e

4 files changed

Lines changed: 103 additions & 5 deletions

File tree

objectbox-java/src/main/java/io/objectbox/query/Query.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.objectbox.query;
22

33
import java.util.Date;
4+
import java.util.Iterator;
45
import java.util.List;
56
import java.util.concurrent.Callable;
67

@@ -73,15 +74,17 @@ native static void nativeSetParameters(long handle, int propertyId, String param
7374
private final boolean hasOrder;
7475
private final QueryPublisher<T> publisher;
7576
private final List<EagerRelation> eagerRelations;
77+
private final QueryFilter<T> filter;
7678
long handle;
7779

78-
Query(Box<T> box, long queryHandle, boolean hasOrder, List<EagerRelation> eagerRelations) {
80+
Query(Box<T> box, long queryHandle, boolean hasOrder, List<EagerRelation> eagerRelations, QueryFilter<T> filter) {
7981
this.box = box;
8082
store = box.getStore();
8183
handle = queryHandle;
8284
this.hasOrder = hasOrder;
8385
publisher = new QueryPublisher<>(this, box);
8486
this.eagerRelations = eagerRelations;
87+
this.filter = filter;
8588
}
8689

8790
@Override
@@ -105,6 +108,7 @@ public synchronized void close() {
105108
*/
106109
@Nullable
107110
public T findFirst() {
111+
ensureNoFilter();
108112
return store.callInReadTx(new Callable<T>() {
109113
@Override
110114
public T call() {
@@ -116,13 +120,21 @@ public T call() {
116120
});
117121
}
118122

123+
private void ensureNoFilter() {
124+
if (filter != null) {
125+
throw new UnsupportedOperationException("Does not yet work with a filter yet. " +
126+
"At this point, only find() and forEach() are supported with filters.");
127+
}
128+
}
129+
119130
/**
120131
* Find the unique Object matching the query.
121132
*
122133
* @throws io.objectbox.exception.DbException if result was not unique
123134
*/
124135
@Nullable
125136
public T findUnique() {
137+
ensureNoFilter();
126138
return store.callInReadTx(new Callable<T>() {
127139
@Override
128140
public T call() {
@@ -143,7 +155,16 @@ public List<T> find() {
143155
@Override
144156
public List<T> call() throws Exception {
145157
long cursorHandle = InternalAccess.getActiveTxCursorHandle(box);
146-
List entities = nativeFind(Query.this.handle, cursorHandle, 0, 0);
158+
List<T> entities = nativeFind(Query.this.handle, cursorHandle, 0, 0);
159+
if (filter != null) {
160+
Iterator<T> iterator = entities.iterator();
161+
while (iterator.hasNext()) {
162+
T entity = iterator.next();
163+
if (!filter.keep(entity)) {
164+
iterator.remove();
165+
}
166+
}
167+
}
147168
resolveEagerRelations(entities);
148169
return entities;
149170
}
@@ -155,6 +176,7 @@ public List<T> call() throws Exception {
155176
*/
156177
@Nonnull
157178
public List<T> find(final long offset, final long limit) {
179+
ensureNoFilter();
158180
return store.callInReadTx(new Callable<List<T>>() {
159181
@Override
160182
public List<T> call() {
@@ -169,6 +191,8 @@ public List<T> call() {
169191
/**
170192
* Very efficient way to get just the IDs without creating any objects. IDs can later be used to lookup objects
171193
* (lookups by ID are also very efficient in ObjectBox).
194+
*
195+
* Note: a filter set with {@link QueryBuilder#filter} will be silently ignored!
172196
*/
173197
@Nonnull
174198
public long[] findIds() {
@@ -187,6 +211,7 @@ public long[] call(long cursorHandle) {
187211
* Find all Objects matching the query without actually loading the Objects. See @{@link LazyList} for details.
188212
*/
189213
public LazyList<T> findLazy() {
214+
ensureNoFilter();
190215
return new LazyList<>(box, findIds(), false);
191216
}
192217

@@ -203,13 +228,18 @@ public void forEach(final QueryConsumer<T> consumer) {
203228
box.getStore().runInReadTx(new Runnable() {
204229
@Override
205230
public void run() {
206-
LazyList<T> lazyList = findLazy();
231+
LazyList<T> lazyList = new LazyList<>(box, findIds(), false);
207232
int size = lazyList.size();
208233
for (int i = 0; i < size; i++) {
209234
T entity = lazyList.get(i);
210235
if (entity == null) {
211236
throw new IllegalStateException("Internal error: data object was null");
212237
}
238+
if (filter != null) {
239+
if (!filter.keep(entity)) {
240+
continue;
241+
}
242+
}
213243
if (eagerRelations != null) {
214244
resolveEagerRelationForNonNullEagerRelations(entity, i);
215245
}
@@ -228,6 +258,7 @@ public void run() {
228258
*/
229259
@Nonnull
230260
public LazyList<T> findLazyCached() {
261+
ensureNoFilter();
231262
return new LazyList<>(box, findIds(), true);
232263
}
233264

objectbox-java/src/main/java/io/objectbox/query/QueryBuilder.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ enum Operator {
7676

7777
private List<EagerRelation> eagerRelations;
7878

79+
private QueryFilter<T> filter;
80+
7981
private static native long nativeCreate(long storeHandle, String entityName);
8082

8183
private static native void nativeDestroy(long handle);
@@ -157,7 +159,7 @@ public Query<T> build() {
157159
throw new IllegalStateException("Incomplete logic condition. Use or()/and() between two conditions only.");
158160
}
159161
long queryHandle = nativeBuild(handle);
160-
Query<T> query = new Query<>(box, queryHandle, hasOrder, eagerRelations);
162+
Query<T> query = new Query<>(box, queryHandle, hasOrder, eagerRelations, filter);
161163
close();
162164
return query;
163165
}
@@ -205,7 +207,8 @@ public QueryBuilder<T> orderDesc(Property property) {
205207
*/
206208
public QueryBuilder<T> order(Property property, int flags) {
207209
if (combineNextWith != Operator.NONE) {
208-
throw new IllegalStateException("An operator is pending. Use operators like and() and or() only between two conditions.");
210+
throw new IllegalStateException(
211+
"An operator is pending. Use operators like and() and or() only between two conditions.");
209212
}
210213
nativeOrder(handle, property.getId(), flags);
211214
hasOrder = true;
@@ -244,6 +247,28 @@ public QueryBuilder<T> eager(int limit, RelationInfo relationInfo, RelationInfo.
244247
return this;
245248
}
246249

250+
/**
251+
* Sets a filter that executes on primary query results (returned from the db core) on a Java level.
252+
* For efficiency reasons, you should always prefer primary criteria like {@link #equal(Property, String)} if
253+
* possible.
254+
* A filter requires to instantiate full Java objects beforehand, which is less efficient.
255+
* <p>
256+
* The upside of filters is that they allow any complex operation including traversing object graphs,
257+
* and that filtering is executed along with the query (preferably in a background thread).
258+
* Use filtering wisely ;-).
259+
* <p>
260+
* Also note, that a filter may only be used along with {@link Query#find()} and
261+
* {@link Query#forEach(QueryConsumer)} at this point.
262+
* Other find methods will throw a exception and aggregate functions will silently ignore the filter.
263+
*/
264+
public QueryBuilder<T> filter(QueryFilter<T> filter) {
265+
if (this.filter != null) {
266+
throw new IllegalStateException("A filter was already defined, you can only assign one filter");
267+
}
268+
this.filter = filter;
269+
return this;
270+
}
271+
247272
/**
248273
* Combines the previous condition with the following condition with a logical OR.
249274
* <p>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package io.objectbox.query;
2+
3+
/**
4+
* Decides which entities to keep as a query result.
5+
*
6+
* @param <T> The entity
7+
*/
8+
public interface QueryFilter<T> {
9+
boolean keep(T entity);
10+
}

tests/objectbox-java-test/src/main/java/io/objectbox/query/QueryTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,38 @@ public void accept(TestEntity data) {
383383
assertEquals("banana", stringBuilder.toString());
384384
}
385385

386+
@Test
387+
public void testForEachWithFilter() {
388+
putTestEntitiesStrings();
389+
final StringBuilder stringBuilder = new StringBuilder();
390+
box.query().filter(createTestFilter()).build()
391+
.forEach(new QueryConsumer<TestEntity>() {
392+
@Override
393+
public void accept(TestEntity data) {
394+
stringBuilder.append(data.getSimpleString()).append('#');
395+
}
396+
});
397+
assertEquals("apple#banana milk shake#", stringBuilder.toString());
398+
}
399+
400+
@Test
401+
public void testFindWithFilter() {
402+
putTestEntitiesStrings();
403+
List<TestEntity> entities = box.query().filter(createTestFilter()).build().find();
404+
assertEquals(2, entities.size());
405+
assertEquals("apple", entities.get(0).getSimpleString());
406+
assertEquals("banana milk shake", entities.get(1).getSimpleString());
407+
}
408+
409+
private QueryFilter<TestEntity> createTestFilter() {
410+
return new QueryFilter<TestEntity>() {
411+
@Override
412+
public boolean keep(TestEntity entity) {
413+
return entity.getSimpleString().contains("e");
414+
}
415+
};
416+
}
417+
386418
private List<TestEntity> putTestEntitiesScalars() {
387419
return putTestEntities(10, null, 2000);
388420
}

0 commit comments

Comments
 (0)