io.objectbox.query.Query Maven / Gradle / Ivy
Show all versions of objectbox-java Show documentation
/*
* Copyright 2017-2024 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.objectbox.query;
import java.io.Closeable;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import io.objectbox.Box;
import io.objectbox.BoxStore;
import io.objectbox.InternalAccess;
import io.objectbox.Property;
import io.objectbox.annotation.HnswIndex;
import io.objectbox.exception.NonUniqueResultException;
import io.objectbox.reactive.DataObserver;
import io.objectbox.reactive.DataSubscriptionList;
import io.objectbox.reactive.SubscriptionBuilder;
import io.objectbox.relation.RelationInfo;
import io.objectbox.relation.ToOne;
/**
* A repeatable Query returning the latest matching objects.
*
* Use {@link #find()} or related methods to fetch the latest results from the {@link BoxStore}.
*
* Use {@link #property(Property)} to only return values or an aggregate of a single Property.
*
* Make sure to {@link #close()} this query once done with it to reclaim resources immediately.
*
* See the Queries documentation for details.
*
* @param Entity class for which results are returned.
*/
@SuppressWarnings({"SameParameterValue", "UnusedReturnValue", "WeakerAccess"})
public class Query implements Closeable {
native void nativeDestroy(long handle);
/** Clones the native query, incl. conditions and parameters, and returns a handle to the clone. */
native long nativeClone(long handle);
native Object nativeFindFirst(long handle, long cursorHandle);
native Object nativeFindUnique(long handle, long cursorHandle);
native List nativeFind(long handle, long cursorHandle, long offset, long limit) throws Exception;
native long nativeFindFirstId(long handle, long cursorHandle);
native long nativeFindUniqueId(long handle, long cursorHandle);
native long[] nativeFindIds(long handle, long cursorHandle, long offset, long limit);
native List> nativeFindWithScores(long handle, long cursorHandle, long offset, long limit);
native List nativeFindIdsWithScores(long handle, long cursorHandle, long offset, long limit);
native long nativeCount(long handle, long cursorHandle);
native long nativeRemove(long handle, long cursorHandle);
native String nativeToString(long handle);
native String nativeDescribeParameters(long handle);
native void nativeSetParameter(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
String value);
private native void nativeSetParameters(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
String value, String value2);
native void nativeSetParameter(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
long value);
native void nativeSetParameters(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
int[] values);
native void nativeSetParameters(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
long[] values);
native void nativeSetParameters(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
long value1, long value2);
native void nativeSetParameter(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
double value);
native void nativeSetParameters(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
double value1, double value2);
native void nativeSetParameters(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
String[] values);
native void nativeSetParameter(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
byte[] value);
native void nativeSetParameter(long handle, int entityId, int propertyId, @Nullable String parameterAlias,
float[] values);
final Box box;
private final BoxStore store;
private final QueryPublisher publisher;
@Nullable private final List> eagerRelations;
@Nullable private final QueryFilter filter;
@Nullable private final Comparator comparator;
private final int queryAttempts;
private static final int INITIAL_RETRY_BACK_OFF_IN_MS = 10;
// volatile so checkOpen() is more up-to-date (no need for synchronized; it's a race anyway)
volatile long handle;
Query(Box box, long queryHandle, @Nullable List> eagerRelations, @Nullable QueryFilter filter,
@Nullable Comparator comparator) {
this.box = box;
store = box.getStore();
queryAttempts = store.internalQueryAttempts();
handle = queryHandle;
publisher = new QueryPublisher<>(this, box);
this.eagerRelations = eagerRelations;
this.filter = filter;
this.comparator = comparator;
}
/**
* Creates a copy of the {@code originalQuery}, but pointing to a different native query using {@code handle}.
*/
// Note: not using recommended copy constructor (just passing this) as handle needs to change.
private Query(Query originalQuery, long handle) {
this(
originalQuery.box,
handle,
originalQuery.eagerRelations,
originalQuery.filter,
originalQuery.comparator
);
}
/**
* Explicitly call {@link #close()} instead to avoid expensive finalization.
*/
@SuppressWarnings("deprecation") // finalize()
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Closes this query and frees used resources.
*
* If possible, call this always once done with this. Otherwise, will be called once this is finalized (e.g. garbage
* collected).
*
* Calling any other methods of this afterwards will throw an {@link IllegalStateException}.
*/
public synchronized void close() {
if (handle != 0) {
// Closeable recommendation: mark as "closed" before nativeDestroy could throw.
long handleCopy = handle;
handle = 0;
nativeDestroy(handleCopy);
}
}
/**
* Creates a copy of this for use in another thread.
*
* Clones the native query, keeping any previously set parameters.
*
* Closing the original query does not close the copy. {@link #close()} the copy once finished using it.
*
* Note: a set {@link QueryBuilder#filter(QueryFilter) filter} or {@link QueryBuilder#sort(Comparator) sort}
* order must be thread safe.
*/
// Note: not overriding clone() to avoid confusion with Java's cloning mechanism.
public Query copy() {
long cloneHandle = nativeClone(handle);
return new Query<>(this, cloneHandle);
}
/** To be called inside a read TX */
long cursorHandle() {
return InternalAccess.getActiveTxCursorHandle(box);
}
/**
* Finds the first object matching this query.
*
* Note: if no {@link QueryBuilder#order} conditions are present, which object is the first one might be arbitrary
* (sometimes the one with the lowest ID, but never guaranteed to be).
*
* @return The first object if there are matches. {@code null} if no object matches.
*/
@Nullable
public T findFirst() {
ensureNoFilterNoComparator();
return callInReadTx(() -> {
@SuppressWarnings("unchecked")
T entity = (T) nativeFindFirst(handle, cursorHandle());
resolveEagerRelation(entity);
return entity;
});
}
private void ensureNoFilterNoComparator() {
ensureNoFilter();
ensureNoComparator();
}
private void ensureNoFilter() {
if (filter != null) {
throw new UnsupportedOperationException("Does not work with a filter. " +
"Only find() and forEach() support filters.");
}
}
private void ensureNoComparator() {
if (comparator != null) {
throw new UnsupportedOperationException("Does not work with a sorting comparator. " +
"Only find() supports sorting with a comparator.");
}
}
/**
* Finds the only object matching this query.
*
* @return The object if a single object matches. {@code null} if no object matches. Throws
* {@link NonUniqueResultException} if there are multiple objects matching the query.
*/
@Nullable
public T findUnique() {
ensureNoFilter(); // Comparator is fine: does not make any difference for a unique result
return callInReadTx(() -> {
@SuppressWarnings("unchecked")
T entity = (T) nativeFindUnique(handle, cursorHandle());
resolveEagerRelation(entity);
return entity;
});
}
/**
* Finds objects matching the query.
*
* Note: if no {@link QueryBuilder#order} conditions are present, the order is arbitrary (sometimes ordered by ID,
* but never guaranteed to).
*
* @return A list of matching objects. An empty list if no object matches.
*/
@Nonnull
public List find() {
return callInReadTx(() -> {
List entities = nativeFind(Query.this.handle, cursorHandle(), 0, 0);
if (filter != null) {
Iterator iterator = entities.iterator();
while (iterator.hasNext()) {
T entity = iterator.next();
if (!filter.keep(entity)) {
iterator.remove();
}
}
}
resolveEagerRelations(entities);
if (comparator != null) {
Collections.sort(entities, comparator);
}
return entities;
});
}
/**
* Like {@link #find()}, but can skip and limit results.
*
* Use to get a slice of the whole result, e.g. for "result paging".
*
* @param offset If greater than 0, skips this many results.
* @param limit If greater than 0, returns at most this many results.
*/
@Nonnull
public List find(final long offset, final long limit) {
ensureNoFilterNoComparator();
return callInReadTx(() -> {
List entities = nativeFind(handle, cursorHandle(), offset, limit);
resolveEagerRelations(entities);
return entities;
});
}
/**
* Like {@link #findFirst()}, but returns just the ID of the object.
*
* This is more efficient as no object is created.
*
* Ignores any {@link QueryBuilder#filter(QueryFilter) query filter}.
*
* @return The ID of the first matching object. {@code 0} if no object matches.
*/
public long findFirstId() {
checkOpen();
return box.internalCallWithReaderHandle(cursorHandle -> nativeFindFirstId(handle, cursorHandle));
}
/**
* Like {@link #findUnique()}, but returns just the ID of the object.
*
* This is more efficient as no object is created.
*
* Ignores any {@link QueryBuilder#filter(QueryFilter) query filter}.
*
* @return The ID of the object, if a single object matches. {@code 0} if no object matches. Throws
* {@link NonUniqueResultException} if there are multiple objects matching the query.
*/
public long findUniqueId() {
checkOpen();
return box.internalCallWithReaderHandle(cursorHandle -> nativeFindUniqueId(handle, cursorHandle));
}
/**
* Like {@link #find()}, but returns just the IDs of the objects.
*
* IDs can later be used to {@link Box#get} objects.
*
* This is very efficient as no objects are created.
*
* Note: a filter set with {@link QueryBuilder#filter(QueryFilter)} will be silently ignored!
*
* @return An array of IDs of matching objects. An empty array if no objects match.
*/
@Nonnull
public long[] findIds() {
return findIds(0, 0);
}
/**
* Like {@link #findIds()}, but can skip and limit results.
*
* Use to get a slice of the whole result, e.g. for "result paging".
*
* Note: a filter set with {@link QueryBuilder#filter(QueryFilter)} will be silently ignored!
*
* @param offset If greater than 0, skips this many results.
* @param limit If greater than 0, returns at most this many results.
*/
@Nonnull
public long[] findIds(final long offset, final long limit) {
checkOpen();
return box.internalCallWithReaderHandle(cursorHandle -> nativeFindIds(handle, cursorHandle, offset, limit));
}
/**
* Like {@link #findIds()}, but wraps the Object IDs in an unmodifiable {@link LazyList}
* so Objects can be retrieved on demand. The LazyList does not cache retrieved Objects, so only basic
* {@link List} operations like getting or iterating list items are supported. See {@link LazyList} for details.
*/
@Nonnull
public LazyList findLazy() {
ensureNoFilterNoComparator();
return new LazyList<>(box, findIds(), false);
}
/**
* Like {@link #findIds()}, but wraps the Object IDs in an unmodifiable, caching {@link LazyList}
* so Objects can be retrieved on demand. The LazyList caches retrieved Objects supporting almost
* all {@link List} operations, at the expense of used memory. See {@link LazyList} for details.
*/
@Nonnull
public LazyList findLazyCached() {
ensureNoFilterNoComparator();
return new LazyList<>(box, findIds(), true);
}
/**
* Like {@link #findIdsWithScores()}, but can skip and limit results.
*
* Use to get a slice of the whole result, e.g. for "result paging".
*
* @param offset If greater than 0, skips this many results.
* @param limit If greater than 0, returns at most this many results.
*/
@Nonnull
public List findIdsWithScores(final long offset, final long limit) {
checkOpen();
return box.internalCallWithReaderHandle(cursorHandle -> nativeFindIdsWithScores(handle, cursorHandle, offset, limit));
}
/**
* Finds IDs of objects matching the query associated to their query score (e.g. distance in NN search).
*
* This only works on objects with a property with an {@link HnswIndex}.
*
* @return A list of {@link IdWithScore} that wraps IDs of matching objects and their score, sorted by score in
* ascending order.
*/
@Nonnull
public List findIdsWithScores() {
return findIdsWithScores(0, 0);
}
/**
* Like {@link #findWithScores()}, but can skip and limit results.
*
* Use to get a slice of the whole result, e.g. for "result paging".
*
* @param offset If greater than 0, skips this many results.
* @param limit If greater than 0, returns at most this many results.
*/
@Nonnull
public List> findWithScores(final long offset, final long limit) {
ensureNoFilterNoComparator();
return callInReadTx(() -> {
List> results = nativeFindWithScores(handle, cursorHandle(), offset, limit);
if (eagerRelations != null) {
for (int i = 0; i < results.size(); i++) {
resolveEagerRelationForNonNullEagerRelations(results.get(i).get(), i);
}
}
return results;
});
}
/**
* Finds objects matching the query associated to their query score (e.g. distance in NN search).
*
* This only works on objects with a property with an {@link HnswIndex}.
*
* @return A list of {@link ObjectWithScore} that wraps matching objects and their score, sorted by score in
* ascending order.
*/
@Nonnull
public List> findWithScores() {
return findWithScores(0, 0);
}
/**
* Creates a {@link PropertyQuery} for the given property.
*
* A {@link PropertyQuery} uses the same conditions as this Query object,
* but returns only the value(s) of a single property (not an entity objects).
*
* @param property the property for which to return values
*/
public PropertyQuery property(Property property) {
return new PropertyQuery(this, property);
}
R callInReadTx(Callable callable) {
checkOpen();
return store.callInReadTxWithRetry(callable, queryAttempts, INITIAL_RETRY_BACK_OFF_IN_MS, true);
}
/**
* Emits query results one by one to the given consumer (synchronously).
* Once this method returns, the consumer will have received all result object).
* It "streams" each object from the database to the consumer, which is very memory efficient.
* Because this is run in a read transaction, the consumer gets a consistent view on the data.
* Like {@link #findLazy()}, this method can be used for a high amount of data.
*
* Note: because the consumer is called within a read transaction it may not write to the database.
*/
public void forEach(final QueryConsumer consumer) {
ensureNoComparator();
checkOpen(); // findIds also checks, but throw early outside of transaction.
box.getStore().runInReadTx(() -> {
LazyList lazyList = new LazyList<>(box, findIds(), false);
int size = lazyList.size();
for (int i = 0; i < size; i++) {
T entity = lazyList.get(i);
if (entity == null) {
throw new IllegalStateException("Internal error: data object was null");
}
if (filter != null) {
if (!filter.keep(entity)) {
continue;
}
}
if (eagerRelations != null) {
resolveEagerRelationForNonNullEagerRelations(entity, i);
}
try {
consumer.accept(entity);
} catch (BreakForEach breakForEach) {
break;
}
}
});
}
void resolveEagerRelations(List entities) {
if (eagerRelations != null) {
int entityIndex = 0;
for (T entity : entities) {
resolveEagerRelationForNonNullEagerRelations(entity, entityIndex);
entityIndex++;
}
}
}
/** Note: no null check on eagerRelations! */
void resolveEagerRelationForNonNullEagerRelations(@Nonnull T entity, int entityIndex) {
//noinspection ConstantConditions No null check.
for (EagerRelation eagerRelation : eagerRelations) {
if (eagerRelation.limit == 0 || entityIndex < eagerRelation.limit) {
resolveEagerRelation(entity, eagerRelation);
}
}
}
void resolveEagerRelation(@Nullable T entity) {
if (eagerRelations != null && entity != null) {
for (EagerRelation eagerRelation : eagerRelations) {
resolveEagerRelation(entity, eagerRelation);
}
}
}
void resolveEagerRelation(@Nonnull T entity, EagerRelation eagerRelation) {
if (eagerRelations != null) {
RelationInfo relationInfo = eagerRelation.relationInfo;
if (relationInfo.toOneGetter != null) {
ToOne> toOne = relationInfo.toOneGetter.getToOne(entity);
if (toOne != null) {
toOne.getTarget();
}
} else {
if (relationInfo.toManyGetter == null) {
throw new IllegalStateException("Relation info without relation getter: " + relationInfo);
}
List> toMany = relationInfo.toManyGetter.getToMany(entity);
if (toMany != null) {
//noinspection ResultOfMethodCallIgnored Triggers fetching target entities.
toMany.size();
}
}
}
}
/** Returns the count of Objects matching the query. */
public long count() {
checkOpen();
ensureNoFilter();
return box.internalCallWithReaderHandle(cursorHandle -> nativeCount(handle, cursorHandle));
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to a new value.
*/
public Query setParameter(Property> property, String value) {
checkOpen();
nativeSetParameter(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new value to use for the query condition.
*/
public Query setParameter(String alias, String value) {
checkOpen();
nativeSetParameter(handle, 0, 0, alias, value);
return this;
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to a new value.
*/
public Query setParameter(Property> property, long value) {
checkOpen();
nativeSetParameter(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new value to use for the query condition.
*/
public Query setParameter(String alias, long value) {
checkOpen();
nativeSetParameter(handle, 0, 0, alias, value);
return this;
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to a new value.
*/
public Query setParameter(Property> property, double value) {
checkOpen();
nativeSetParameter(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new value to use for the query condition.
*/
public Query setParameter(String alias, double value) {
checkOpen();
nativeSetParameter(handle, 0, 0, alias, value);
return this;
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to a new value.
*
* @throws NullPointerException if given date is null
*/
public Query setParameter(Property> property, Date value) {
return setParameter(property, value.getTime());
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new value to use for the query condition.
* @throws NullPointerException if given date is null
*/
public Query setParameter(String alias, Date value) {
return setParameter(alias, value.getTime());
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to a new value.
*/
public Query setParameter(Property> property, boolean value) {
return setParameter(property, value ? 1 : 0);
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new value to use for the query condition.
*/
public Query setParameter(String alias, boolean value) {
return setParameter(alias, value ? 1 : 0);
}
/**
* Changes the parameter of the query condition for {@code property} to a new {@code value}.
*
* @param property Property reference from generated entity underscore class, like {@code Example_.example}.
* @param value The new {@code int[]} value to use for the query condition.
*/
public Query setParameter(Property> property, int[] value) {
checkOpen();
nativeSetParameters(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new {@code int[]} value to use for the query condition.
*/
public Query setParameter(String alias, int[] value) {
checkOpen();
nativeSetParameters(handle, 0, 0, alias, value);
return this;
}
/**
* Changes the parameter of the query condition for {@code property} to a new {@code value}.
*
* @param property Property reference from generated entity underscore class, like {@code Example_.example}.
* @param value The new {@code long[]} value to use for the query condition.
*/
public Query setParameter(Property> property, long[] value) {
checkOpen();
nativeSetParameters(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new {@code long[]} value to use for the query condition.
*/
public Query setParameter(String alias, long[] value) {
checkOpen();
nativeSetParameters(handle, 0, 0, alias, value);
return this;
}
/**
* Changes the parameter of the query condition for {@code property} to a new {@code value}.
*
* @param property Property reference from generated entity underscore class, like {@code Example_.example}.
* @param value The new {@code float[]} value to use for the query condition.
*/
public Query setParameter(Property> property, float[] value) {
checkOpen();
nativeSetParameter(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new {@code float[]} value to use for the query condition.
*/
public Query setParameter(String alias, float[] value) {
checkOpen();
nativeSetParameter(handle, 0, 0, alias, value);
return this;
}
/**
* Changes the parameter of the query condition for {@code property} to a new {@code value}.
*
* @param property Property reference from generated entity underscore class, like {@code Example_.example}.
* @param value The new {@code String[]} value to use for the query condition.
*/
public Query setParameter(Property> property, String[] value) {
checkOpen();
nativeSetParameters(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new {@code String[]} value to use for the query condition.
*/
public Query setParameter(String alias, String[] value) {
checkOpen();
nativeSetParameters(handle, 0, 0, alias, value);
return this;
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*/
public Query setParameters(Property> property, long value1, long value2) {
checkOpen();
nativeSetParameters(handle, property.getEntityId(), property.getId(), null, value1, value2);
return this;
}
/**
* Changes the parameters of the query condition with the matching {@code alias} to the new values.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value1 The first value to use for the query condition.
* @param value2 The second value to use for the query condition.
*/
public Query setParameters(String alias, long value1, long value2) {
checkOpen();
nativeSetParameters(handle, 0, 0, alias, value1, value2);
return this;
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*
* @deprecated Use {@link #setParameter(Property, int[])} instead.
*/
@Deprecated
public Query setParameters(Property> property, int[] values) {
return setParameter(property, values);
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*
* @param alias as defined using {@link QueryBuilder#parameterAlias(String)}.
* @deprecated Use {@link #setParameter(String, int[])} instead.
*/
@Deprecated
public Query setParameters(String alias, int[] values) {
return setParameter(alias, values);
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*
* @deprecated Use {@link #setParameter(Property, long[])} instead.
*/
@Deprecated
public Query setParameters(Property> property, long[] values) {
return setParameter(property, values);
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*
* @param alias as defined using {@link QueryBuilder#parameterAlias(String)}.
* @deprecated Use {@link #setParameter(String, long[])} instead.
*/
@Deprecated
public Query setParameters(String alias, long[] values) {
return setParameter(alias, values);
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*/
public Query setParameters(Property> property, double value1, double value2) {
checkOpen();
nativeSetParameters(handle, property.getEntityId(), property.getId(), null, value1, value2);
return this;
}
/**
* Changes the parameters of the query condition with the matching {@code alias} to the new values.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value1 The first value to use for the query condition.
* @param value2 The second value to use for the query condition.
*/
public Query setParameters(String alias, double value1, double value2) {
checkOpen();
nativeSetParameters(handle, 0, 0, alias, value1, value2);
return this;
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*
* @deprecated Use {@link #setParameter(Property, String[])} instead.
*/
@Deprecated
public Query setParameters(Property> property, String[] values) {
return setParameter(property, values);
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*
* @param alias as defined using {@link QueryBuilder#parameterAlias(String)}.
* @deprecated Use {@link #setParameter(String, String[])} instead.
*/
@Deprecated
public Query setParameters(String alias, String[] values) {
return setParameter(alias, values);
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*/
public Query setParameters(Property> property, String key, String value) {
checkOpen();
nativeSetParameters(handle, property.getEntityId(), property.getId(), null, key, value);
return this;
}
/**
* Changes the parameters of the query condition with the matching {@code alias} to the new values.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param key The first value to use for the query condition.
* @param value The second value to use for the query condition.
*/
public Query setParameters(String alias, String key, String value) {
checkOpen();
nativeSetParameters(handle, 0, 0, alias, key, value);
return this;
}
/**
* Sets a parameter previously given to the {@link QueryBuilder} to new values.
*/
public Query setParameter(Property> property, byte[] value) {
checkOpen();
nativeSetParameter(handle, property.getEntityId(), property.getId(), null, value);
return this;
}
/**
* Changes the parameter of the query condition with the matching {@code alias} to a new {@code value}.
*
* @param alias as defined using {@link PropertyQueryCondition#alias(String)}.
* @param value The new value to use for the query condition.
*/
public Query setParameter(String alias, byte[] value) {
checkOpen();
nativeSetParameter(handle, 0, 0, alias, value);
return this;
}
/**
* Removes (deletes) all Objects matching the query
*
* @return count of removed Objects
*/
public long remove() {
checkOpen();
ensureNoFilter();
return box.internalCallWithWriterHandle(cursorHandle -> nativeRemove(handle, cursorHandle));
}
/**
* A {@link io.objectbox.reactive.DataObserver} can be subscribed to data changes using the returned builder.
* The observer is supplied via {@link SubscriptionBuilder#observer(DataObserver)} and will be notified once
* the query results have (potentially) changed.
*
* With subscribing, the observer will immediately get current query results.
* The query is run for the subscribing observer.
*
* Threading notes:
* Query observers are notified from a thread pooled. Observers may be notified in parallel.
* The notification order is the same as the subscription order, although this may not always be guaranteed in
* the future.
*
* Stale observers: you must hold on to the Query or {@link io.objectbox.reactive.DataSubscription} objects to keep
* your {@link DataObserver}s active. If this Query is not referenced anymore
* (along with its {@link io.objectbox.reactive.DataSubscription}s, which hold a reference to the Query internally),
* it may be GCed and observers may become stale (won't receive anymore data).
*/
public SubscriptionBuilder> subscribe() {
checkOpen();
return new SubscriptionBuilder<>(publisher, null);
}
/**
* Convenience for {@link #subscribe()} with a subsequent call to
* {@link SubscriptionBuilder#dataSubscriptionList(DataSubscriptionList)}.
*
* @param dataSubscriptionList the resulting {@link io.objectbox.reactive.DataSubscription} will be added to it
*/
public SubscriptionBuilder> subscribe(DataSubscriptionList dataSubscriptionList) {
SubscriptionBuilder> subscriptionBuilder = subscribe();
subscriptionBuilder.dataSubscriptionList(dataSubscriptionList);
return subscriptionBuilder;
}
/**
* Publishes the current data to all subscribed @{@link DataObserver}s.
* This is useful triggering observers when new parameters have been set.
* Note, that setParameter methods will NOT be propagated to observers.
*/
public void publish() {
publisher.publish();
}
/**
* For logging and testing, returns a string describing this query
* like "Query for entity Example with 4 conditions with properties prop1, prop2".
*
* Note: the format of the returned string may change without notice.
*/
public String describe() {
checkOpen();
return nativeToString(handle);
}
/**
* For logging and testing, returns a string describing the conditions of this query
* like "(prop1 == A AND prop2 is null)".
*
* Note: the format of the returned string may change without notice.
*/
public String describeParameters() {
checkOpen();
return nativeDescribeParameters(handle);
}
/**
* Throws if {@link #close()} has been called for this.
*/
private void checkOpen() {
if (handle == 0) {
throw new IllegalStateException("This query is closed. Build and use a new one.");
}
}
}