Skip to content

Commit 4262c5d

Browse files
Query clone: add copy method to clone the native query (#34)
1 parent 3fdf345 commit 4262c5d

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public class Query<T> implements Closeable {
5555

5656
native void nativeDestroy(long handle);
5757

58+
/** Clones the native query, incl. conditions and parameters, and returns a handle to the clone. */
59+
native long nativeClone(long handle);
60+
5861
native Object nativeFindFirst(long handle, long cursorHandle);
5962

6063
native Object nativeFindUnique(long handle, long cursorHandle);
@@ -129,6 +132,20 @@ native void nativeSetParameter(long handle, int entityId, int propertyId, @Nulla
129132
this.comparator = comparator;
130133
}
131134

135+
/**
136+
* Creates a copy of the {@code originalQuery}, but pointing to a different native query using {@code handle}.
137+
*/
138+
// Note: not using recommended copy constructor (just passing this) as handle needs to change.
139+
private Query(Query<T> originalQuery, long handle) {
140+
this(
141+
originalQuery.box,
142+
handle,
143+
originalQuery.eagerRelations,
144+
originalQuery.filter,
145+
originalQuery.comparator
146+
);
147+
}
148+
132149
/**
133150
* Explicitly call {@link #close()} instead to avoid expensive finalization.
134151
*/
@@ -156,6 +173,22 @@ public synchronized void close() {
156173
}
157174
}
158175

176+
/**
177+
* Creates a copy of this for use in another thread.
178+
* <p>
179+
* Clones the native query, keeping any previously set parameters.
180+
* <p>
181+
* Closing the original query does not close the copy. {@link #close()} the copy once finished using it.
182+
* <p>
183+
* Note: a set {@link QueryBuilder#filter(QueryFilter) filter} or {@link QueryBuilder#sort(Comparator) sort}
184+
* order <b>must be thread safe</b>.
185+
*/
186+
// Note: not overriding clone() to avoid confusion with Java's cloning mechanism.
187+
public Query<T> copy() {
188+
long cloneHandle = nativeClone(handle);
189+
return new Query<>(this, cloneHandle);
190+
}
191+
159192
/** To be called inside a read TX */
160193
long cursorHandle() {
161194
return InternalAccess.getActiveTxCursorHandle(box);
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.objectbox.query;
2+
3+
import io.objectbox.TestEntity;
4+
import io.objectbox.TestEntity_;
5+
import org.junit.Test;
6+
7+
import java.util.Comparator;
8+
import java.util.List;
9+
10+
import static org.junit.Assert.*;
11+
12+
public class QueryCopyTest extends AbstractQueryTest {
13+
14+
@Test
15+
public void queryCopy_isClone() {
16+
putTestEntity("orange", 1);
17+
TestEntity banana = putTestEntity("banana", 2);
18+
putTestEntity("apple", 3);
19+
TestEntity bananaMilkShake = putTestEntity("banana milk shake", 4);
20+
putTestEntity("pineapple", 5);
21+
putTestEntity("papaya", 6);
22+
23+
// Only even nr: 2, 4, 6.
24+
QueryFilter<TestEntity> filter = entity -> entity.getSimpleInt() % 2 == 0;
25+
// Reverse insert order: 6, 4, 2.
26+
Comparator<TestEntity> comparator = Comparator.comparingLong(testEntity -> -testEntity.getId());
27+
28+
Query<TestEntity> queryOriginal = box.query(TestEntity_.simpleString.contains("").alias("fruit"))
29+
.filter(filter)
30+
.sort(comparator)
31+
.build();
32+
// Only bananas: 4, 2.
33+
queryOriginal.setParameter("fruit", banana.getSimpleString());
34+
35+
Query<TestEntity> queryCopy = queryOriginal.copy();
36+
37+
// Object instances and native query handle should differ.
38+
assertNotEquals(queryOriginal, queryCopy);
39+
assertNotEquals(queryOriginal.handle, queryCopy.handle);
40+
41+
// Verify results are identical.
42+
List<TestEntity> resultsOriginal = queryOriginal.find();
43+
queryOriginal.close();
44+
List<TestEntity> resultsCopy = queryCopy.find();
45+
queryCopy.close();
46+
assertEquals(2, resultsOriginal.size());
47+
assertEquals(2, resultsCopy.size());
48+
assertTestEntityEquals(bananaMilkShake, resultsOriginal.get(0));
49+
assertTestEntityEquals(bananaMilkShake, resultsCopy.get(0));
50+
assertTestEntityEquals(banana, resultsOriginal.get(1));
51+
assertTestEntityEquals(banana, resultsCopy.get(1));
52+
}
53+
54+
@Test
55+
public void queryCopy_setParameter_noEffectOriginal() {
56+
TestEntity orange = putTestEntity("orange", 1);
57+
TestEntity banana = putTestEntity("banana", 2);
58+
59+
Query<TestEntity> queryOriginal = box
60+
.query(TestEntity_.simpleString.equal(orange.getSimpleString()).alias("fruit"))
61+
.build();
62+
63+
// Set parameter on clone that changes result.
64+
Query<TestEntity> queryCopy = queryOriginal.copy()
65+
.setParameter("fruit", banana.getSimpleString());
66+
67+
List<TestEntity> resultsOriginal = queryOriginal.find();
68+
queryOriginal.close();
69+
assertEquals(1, resultsOriginal.size());
70+
assertTestEntityEquals(orange, resultsOriginal.get(0));
71+
72+
List<TestEntity> resultsCopy = queryCopy.find();
73+
queryCopy.close();
74+
assertEquals(1, resultsCopy.size());
75+
assertTestEntityEquals(banana, resultsCopy.get(0));
76+
}
77+
78+
private void assertTestEntityEquals(TestEntity expected, TestEntity actual) {
79+
assertEquals(expected.getId(), actual.getId());
80+
assertEquals(expected.getSimpleString(), actual.getSimpleString());
81+
}
82+
}

0 commit comments

Comments
 (0)