Skip to content

Commit 3fdf345

Browse files
Query: add relation count condition (#150)
1 parent afd04fe commit 3fdf345

File tree

4 files changed

+108
-0
lines changed

4 files changed

+108
-0
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ private native long nativeLink(long handle, long storeHandle, int relationOwnerE
152152

153153
private native void nativeSetParameterAlias(long conditionHandle, String alias);
154154

155+
private native long nativeRelationCount(long handle, long storeHandle, int relationOwnerEntityId, int propertyId,
156+
int relationCount);
157+
155158
// ------------------------------ (Not)Null------------------------------
156159

157160
private native long nativeNull(long handle, int propertyId);
@@ -582,6 +585,13 @@ public QueryBuilder<T> notNull(Property<T> property) {
582585
return this;
583586
}
584587

588+
public QueryBuilder<T> relationCount(RelationInfo<T, ?> relationInfo, int relationCount) {
589+
verifyHandle();
590+
checkCombineCondition(nativeRelationCount(handle, storeHandle, relationInfo.targetInfo.getEntityId(),
591+
relationInfo.targetIdProperty.id, relationCount));
592+
return this;
593+
}
594+
585595
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
586596
// Integers
587597
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.objectbox.query;
2+
3+
import io.objectbox.relation.RelationInfo;
4+
5+
public class RelationCountCondition<T> extends QueryConditionImpl<T> {
6+
7+
private final RelationInfo<T, ?> relationInfo;
8+
private final int relationCount;
9+
10+
11+
public RelationCountCondition(RelationInfo<T, ?> relationInfo, int relationCount) {
12+
this.relationInfo = relationInfo;
13+
this.relationCount = relationCount;
14+
}
15+
16+
@Override
17+
void apply(QueryBuilder<T> builder) {
18+
builder.relationCount(relationInfo, relationCount);
19+
}
20+
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import io.objectbox.annotation.apihint.Internal;
2626
import io.objectbox.internal.ToManyGetter;
2727
import io.objectbox.internal.ToOneGetter;
28+
import io.objectbox.query.QueryCondition;
29+
import io.objectbox.query.RelationCountCondition;
2830

2931
/**
3032
* Meta info describing a relation including source and target entity.
@@ -130,5 +132,31 @@ public boolean isBacklink() {
130132
public String toString() {
131133
return "RelationInfo from " + sourceInfo.getEntityClass() + " to " + targetInfo.getEntityClass();
132134
}
135+
136+
/**
137+
* Creates a condition to match objects that have {@code relationCount} related objects pointing to them.
138+
* <pre>
139+
* try (Query&lt;Customer&gt; query = customerBox
140+
* .query(Customer_.orders.relationCount(2))
141+
* .build()) {
142+
* List&lt;Customer&gt; customersWithTwoOrders = query.find();
143+
* }
144+
* </pre>
145+
* {@code relationCount} may be 0 to match objects that do not have related objects.
146+
* It typically should be a low number.
147+
* <p>
148+
* This condition has some limitations:
149+
* <ul>
150+
* <li>only 1:N (ToMany using @Backlink) relations are supported,</li>
151+
* <li>the complexity is {@code O(n * (relationCount + 1))} and cannot be improved via indexes,</li>
152+
* <li>the relation count cannot be changed with setParameter once the query is built.</li>
153+
* </ul>
154+
*/
155+
public QueryCondition<SOURCE> relationCount(int relationCount) {
156+
if (targetIdProperty == null) {
157+
throw new IllegalStateException("The relation count condition is only supported for 1:N (ToMany using @Backlink) relations.");
158+
}
159+
return new RelationCountCondition<>(this, relationCount);
160+
}
133161
}
134162

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.objectbox.query;
2+
3+
import io.objectbox.relation.AbstractRelationTest;
4+
import io.objectbox.relation.Customer;
5+
import io.objectbox.relation.Customer_;
6+
import org.junit.Test;
7+
8+
import java.util.List;
9+
10+
import static org.junit.Assert.assertEquals;
11+
12+
public class QueryRelationCountTest extends AbstractRelationTest {
13+
14+
@Test
15+
public void queryRelationCount() {
16+
// Customer without orders.
17+
putCustomer();
18+
// Customer with 2 orders.
19+
Customer customerWithOrders = putCustomer();
20+
putOrder(customerWithOrders, "First order");
21+
putOrder(customerWithOrders, "Second order");
22+
23+
// Find customer with no orders.
24+
try (Query<Customer> query = customerBox
25+
.query(Customer_.orders.relationCount(0))
26+
.build()) {
27+
List<Customer> customer = query.find();
28+
assertEquals(1, customer.size());
29+
assertEquals(0, customer.get(0).getOrders().size());
30+
}
31+
32+
// Find customer with two orders.
33+
try (Query<Customer> query = customerBox
34+
.query(Customer_.orders.relationCount(2))
35+
.build()) {
36+
List<Customer> customer = query.find();
37+
assertEquals(1, customer.size());
38+
assertEquals(2, customer.get(0).getOrders().size());
39+
}
40+
41+
// Find no customer with three orders.
42+
try (Query<Customer> query = customerBox
43+
.query(Customer_.orders.relationCount(3))
44+
.build()) {
45+
List<Customer> customer = query.find();
46+
assertEquals(0, customer.size());
47+
}
48+
}
49+
50+
}

0 commit comments

Comments
 (0)