Skip to content

Commit 974f933

Browse files
committed
Query: eager relations
1 parent 75cd594 commit 974f933

8 files changed

Lines changed: 263 additions & 27 deletions

File tree

objectbox-java/src/main/java/io/objectbox/InternalAccess.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ public static <T> Cursor<T> getReader(Box<T> box) {
88
return box.getReader();
99
}
1010

11+
public static long getHandle(Cursor reader) {
12+
return reader.internalHandle();
13+
}
14+
1115
public static <T> void releaseReader(Box<T> box, Cursor<T> reader) {
1216
box.releaseReader(reader);
1317
}
@@ -20,9 +24,14 @@ public static <T> Cursor<T> getActiveTxCursor(Box<T> box) {
2024
return box.getActiveTxCursor();
2125
}
2226

27+
public static <T> long getActiveTxCursorHandle(Box<T> box) {
28+
return box.getActiveTxCursor().internalHandle();
29+
}
30+
2331
public static <T> void releaseWriter(Box<T> box, Cursor<T> writer) {
2432
box.releaseWriter(writer);
2533
}
34+
2635
public static <T> void commitWriter(Box<T> box, Cursor<T> writer) {
2736
box.commitWriter(writer);
2837
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.objectbox.query;
2+
3+
import io.objectbox.relation.RelationInfo;
4+
5+
class EagerRelation {
6+
public final int limit;
7+
public final RelationInfo relationInfo;
8+
9+
EagerRelation(int limit, RelationInfo relationInfo) {
10+
this.limit = limit;
11+
this.relationInfo = relationInfo;
12+
}
13+
}

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

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
import java.util.Date;
44
import java.util.List;
5+
import java.util.concurrent.Callable;
56

67
import javax.annotation.Nonnull;
78
import javax.annotation.Nullable;
89

910
import io.objectbox.Box;
11+
import io.objectbox.BoxStore;
12+
import io.objectbox.InternalAccess;
1013
import io.objectbox.Property;
1114
import io.objectbox.annotation.apihint.Beta;
1215
import io.objectbox.internal.CallWithHandle;
1316
import io.objectbox.reactive.DataObserver;
1417
import io.objectbox.reactive.SubscriptionBuilder;
18+
import io.objectbox.relation.RelationInfo;
19+
import io.objectbox.relation.ToOne;
1520

1621
/**
1722
* A repeatable query returning entities.
@@ -61,18 +66,22 @@ native static void nativeSetParameters(long handle, int propertyId, String param
6166
native static void nativeSetParameter(long handle, int propertyId, String parameterAlias, double value);
6267

6368
native static void nativeSetParameters(long handle, int propertyId, String parameterAlias, double value1,
64-
double value2);
69+
double value2);
6570

6671
private final Box<T> box;
72+
private final BoxStore store;
6773
private final boolean hasOrder;
68-
long handle;
6974
private final QueryPublisher<T> publisher;
75+
private final List<EagerRelation> eagerRelations;
76+
long handle;
7077

71-
Query(Box<T> box, long queryHandle, boolean hasOrder) {
78+
Query(Box<T> box, long queryHandle, boolean hasOrder, List<EagerRelation> eagerRelations) {
7279
this.box = box;
80+
store = box.getStore();
7381
handle = queryHandle;
7482
this.hasOrder = hasOrder;
7583
publisher = new QueryPublisher<>(this, box);
84+
this.eagerRelations = eagerRelations;
7685
}
7786

7887
@Override
@@ -96,10 +105,13 @@ public synchronized void close() {
96105
*/
97106
@Nullable
98107
public T findFirst() {
99-
return box.internalCallWithReaderHandle(new CallWithHandle<T>() {
108+
return store.callInReadTx(new Callable<T>() {
100109
@Override
101-
public T call(long cursorHandle) {
102-
return (T) nativeFindFirst(handle, cursorHandle);
110+
public T call() {
111+
@SuppressWarnings("unchecked")
112+
T entity = (T) nativeFindFirst(handle, InternalAccess.getActiveTxCursorHandle(box));
113+
resolveEagerRelation(entity);
114+
return entity;
103115
}
104116
});
105117
}
@@ -111,10 +123,13 @@ public T call(long cursorHandle) {
111123
*/
112124
@Nullable
113125
public T findUnique() {
114-
return box.internalCallWithReaderHandle(new CallWithHandle<T>() {
126+
return store.callInReadTx(new Callable<T>() {
115127
@Override
116-
public T call(long cursorHandle) {
117-
return (T) nativeFindUnique(handle, cursorHandle);
128+
public T call() {
129+
@SuppressWarnings("unchecked")
130+
T entity = (T) nativeFindUnique(handle, InternalAccess.getActiveTxCursorHandle(box));
131+
resolveEagerRelation(entity);
132+
return entity;
118133
}
119134
});
120135
}
@@ -124,10 +139,13 @@ public T call(long cursorHandle) {
124139
*/
125140
@Nonnull
126141
public List<T> find() {
127-
return box.internalCallWithReaderHandle(new CallWithHandle<List<T>>() {
142+
return store.callInReadTx(new Callable<List<T>>() {
128143
@Override
129-
public List<T> call(long cursorHandle) {
130-
return nativeFind(handle, cursorHandle, 0, 0);
144+
public List<T> call() throws Exception {
145+
long cursorHandle = InternalAccess.getActiveTxCursorHandle(box);
146+
List entities = nativeFind(Query.this.handle, cursorHandle, 0, 0);
147+
resolveEagerRelations(entities);
148+
return entities;
131149
}
132150
});
133151
}
@@ -137,10 +155,13 @@ public List<T> call(long cursorHandle) {
137155
*/
138156
@Nonnull
139157
public List<T> find(final long offset, final long limit) {
140-
return box.internalCallWithReaderHandle(new CallWithHandle<List<T>>() {
158+
return store.callInReadTx(new Callable<List<T>>() {
141159
@Override
142-
public List<T> call(long cursorHandle) {
143-
return nativeFind(handle, cursorHandle, offset, limit);
160+
public List<T> call() {
161+
long cursorHandle = InternalAccess.getActiveTxCursorHandle(box);
162+
List entities = nativeFind(handle, cursorHandle, offset, limit);
163+
resolveEagerRelations(entities);
164+
return entities;
144165
}
145166
});
146167
}
@@ -185,11 +206,14 @@ public void run() {
185206
LazyList<T> lazyList = findLazy();
186207
int size = lazyList.size();
187208
for (int i = 0; i < size; i++) {
188-
T data = lazyList.get(i);
189-
if (data == null) {
209+
T entity = lazyList.get(i);
210+
if (entity == null) {
190211
throw new IllegalStateException("Internal error: data object was null");
191212
}
192-
consumer.accept(data);
213+
if(eagerRelations != null) {
214+
resolveEagerRelationForNonNullEagerRelations(entity, i);
215+
}
216+
consumer.accept(entity);
193217
}
194218
}
195219
});
@@ -203,6 +227,53 @@ public LazyList<T> findLazyCached() {
203227
return new LazyList<>(box, findIds(), true);
204228
}
205229

230+
void resolveEagerRelations(List entities) {
231+
if (eagerRelations != null) {
232+
int entityIndex = 0;
233+
for (Object entity : entities) {
234+
resolveEagerRelationForNonNullEagerRelations(entity, entityIndex);
235+
entityIndex++;
236+
}
237+
}
238+
}
239+
240+
/** Note: no null check on eagerRelations! */
241+
void resolveEagerRelationForNonNullEagerRelations(Object entity, int entityIndex) {
242+
for (EagerRelation eagerRelation : eagerRelations) {
243+
if (eagerRelation.limit == 0 || entityIndex < eagerRelation.limit) {
244+
resolveEagerRelation(entity, eagerRelation);
245+
}
246+
}
247+
}
248+
249+
void resolveEagerRelation(Object entity) {
250+
if (eagerRelations != null) {
251+
for (EagerRelation eagerRelation : eagerRelations) {
252+
resolveEagerRelation(entity, eagerRelation);
253+
}
254+
}
255+
}
256+
257+
void resolveEagerRelation(Object entity, EagerRelation eagerRelation) {
258+
if (eagerRelations != null) {
259+
RelationInfo relationInfo = eagerRelation.relationInfo;
260+
if (relationInfo.toOneGetter != null) {
261+
ToOne toOne = relationInfo.toOneGetter.getToOne(entity);
262+
if (toOne != null) {
263+
toOne.getTarget();
264+
}
265+
} else {
266+
if (relationInfo.toManyGetter == null) {
267+
throw new IllegalStateException("Relation info without relation getter: " + relationInfo);
268+
}
269+
List toMany = relationInfo.toManyGetter.getToMany(entity);
270+
if (toMany != null) {
271+
toMany.size();
272+
}
273+
}
274+
}
275+
}
276+
206277
/** Returns the count of Objects matching the query. */
207278
public long count() {
208279
return box.internalCallWithReaderHandle(new CallWithHandle<Long>() {

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

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

3+
import java.util.ArrayList;
34
import java.util.Date;
5+
import java.util.List;
46

57
import io.objectbox.Box;
68
import io.objectbox.Property;
79
import io.objectbox.annotation.apihint.Experimental;
810
import io.objectbox.annotation.apihint.Internal;
911
import io.objectbox.model.OrderFlags;
12+
import io.objectbox.relation.RelationInfo;
1013

1114
/**
1215
* With QueryBuilder you define custom queries returning matching entities. Using the methods of this class you can
@@ -71,6 +74,8 @@ enum Operator {
7174
private long lastCondition;
7275
private Operator combineNextWith = Operator.NONE;
7376

77+
private List<EagerRelation> eagerRelations;
78+
7479
private static native long nativeCreate(long storeHandle, String entityName);
7580

7681
private static native void nativeDestroy(long handle);
@@ -152,7 +157,7 @@ public Query<T> build() {
152157
throw new IllegalStateException("Incomplete logic condition. Use or()/and() between two conditions only.");
153158
}
154159
long queryHandle = nativeBuild(handle);
155-
Query<T> query = new Query<>(box, queryHandle, hasOrder);
160+
Query<T> query = new Query<>(box, queryHandle, hasOrder, eagerRelations);
156161
close();
157162
return query;
158163
}
@@ -199,14 +204,31 @@ public QueryBuilder<T> orderDesc(Property property) {
199204
* @see #orderDesc(Property)
200205
*/
201206
public QueryBuilder<T> order(Property property, int flags) {
202-
if(combineNextWith != Operator.NONE) {
207+
if (combineNextWith != Operator.NONE) {
203208
throw new IllegalStateException("An operator is pending. Use operators like and() and or() only between two conditions.");
204209
}
205210
nativeOrder(handle, property.getId(), flags);
206211
hasOrder = true;
207212
return this;
208213
}
209214

215+
public QueryBuilder<T> eager(RelationInfo relationInfo, RelationInfo... more) {
216+
return eager(0, relationInfo, more);
217+
}
218+
219+
public QueryBuilder<T> eager(int limit, RelationInfo relationInfo, RelationInfo... more) {
220+
if (eagerRelations == null) {
221+
eagerRelations = new ArrayList<>();
222+
}
223+
eagerRelations.add(new EagerRelation(limit, relationInfo));
224+
if (more != null) {
225+
for (RelationInfo info : more) {
226+
eagerRelations.add(new EagerRelation(limit, info));
227+
}
228+
}
229+
return this;
230+
}
231+
210232
/**
211233
* Combines the previous condition with the following condition with a logical OR.
212234
* <p>
@@ -257,7 +279,7 @@ private void combineOperator(Operator operator) {
257279
if (lastCondition == 0) {
258280
throw new IllegalStateException("No previous condition. Use operators like and() and or() only between two conditions.");
259281
}
260-
if(combineNextWith != Operator.NONE) {
282+
if (combineNextWith != Operator.NONE) {
261283
throw new IllegalStateException("Another operator is pending. Use operators like and() and or() only between two conditions.");
262284
}
263285
combineNextWith = operator;

objectbox-java/src/main/java/io/objectbox/relation/RelationInfo.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.objectbox.EntityInfo;
88
import io.objectbox.Property;
99
import io.objectbox.annotation.apihint.Internal;
10+
import io.objectbox.internal.ToManyGetter;
1011
import io.objectbox.internal.ToOneGetter;
1112

1213
@Internal
@@ -24,11 +25,13 @@ public class RelationInfo<TARGET> implements Serializable {
2425
public final Property targetIdProperty;
2526

2627
/** Only set for ToOne relations */
27-
public final io.objectbox.internal.ToOneGetter toOneGetter;
28+
public final ToOneGetter toOneGetter;
29+
30+
/** Only set for ToMany relations */
31+
public final ToManyGetter toManyGetter;
2832

29-
private final io.objectbox.internal.ToManyGetter toManyGetter;
3033
/** For ToMany relations based on backlinks (null otherwise). */
31-
public final io.objectbox.internal.ToOneGetter backlinkToOneGetter;
34+
public final ToOneGetter backlinkToOneGetter;
3235

3336
/** For stand-alone to-many relations (0 otherwise). */
3437
public final int relationId;
@@ -37,7 +40,7 @@ public class RelationInfo<TARGET> implements Serializable {
3740
* ToOne
3841
*/
3942
public RelationInfo(EntityInfo sourceInfo, EntityInfo<TARGET> targetInfo, Property targetIdProperty,
40-
io.objectbox.internal.ToOneGetter toOneGetter) {
43+
ToOneGetter toOneGetter) {
4144
this.sourceInfo = sourceInfo;
4245
this.targetInfo = targetInfo;
4346
this.targetIdProperty = targetIdProperty;
@@ -51,7 +54,7 @@ public RelationInfo(EntityInfo sourceInfo, EntityInfo<TARGET> targetInfo, Proper
5154
* ToMany as a ToOne backlink
5255
*/
5356
public RelationInfo(EntityInfo sourceInfo, EntityInfo<TARGET> targetInfo, Property targetIdProperty,
54-
io.objectbox.internal.ToManyGetter toManyGetter, ToOneGetter backlinkToOneGetter) {
57+
ToManyGetter toManyGetter, ToOneGetter backlinkToOneGetter) {
5558
this.sourceInfo = sourceInfo;
5659
this.targetInfo = targetInfo;
5760
this.targetIdProperty = targetIdProperty;
@@ -65,7 +68,7 @@ public RelationInfo(EntityInfo sourceInfo, EntityInfo<TARGET> targetInfo, Proper
6568
* Stand-alone ToMany.
6669
*/
6770
public RelationInfo(EntityInfo sourceInfo, EntityInfo<TARGET> targetInfo, int relationId,
68-
io.objectbox.internal.ToManyGetter toManyGetter) {
71+
ToManyGetter toManyGetter) {
6972
this.sourceInfo = sourceInfo;
7073
this.targetInfo = targetInfo;
7174
this.relationId = relationId;
@@ -74,5 +77,10 @@ public RelationInfo(EntityInfo sourceInfo, EntityInfo<TARGET> targetInfo, int re
7477
this.toOneGetter = null;
7578
this.backlinkToOneGetter = null;
7679
}
80+
81+
@Override
82+
public String toString() {
83+
return "RelationInfo from " + sourceInfo.getEntityClass() + " to " + targetInfo.getEntityClass();
84+
}
7785
}
7886

tests/objectbox-java-test/src/main/java/io/objectbox/FunctionalTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.objectbox.query.LazyListTest;
99
import io.objectbox.query.QueryObserverTest;
1010
import io.objectbox.query.QueryTest;
11+
import io.objectbox.relation.RelationEagerTest;
1112
import io.objectbox.relation.RelationTest;
1213
import io.objectbox.relation.ToOneTest;
1314

@@ -26,6 +27,7 @@
2627
QueryObserverTest.class,
2728
QueryTest.class,
2829
RelationTest.class,
30+
RelationEagerTest.class,
2931
ToOneTest.class,
3032
TransactionTest.class,
3133
})

0 commit comments

Comments
 (0)