[id='continuous_queries-{context}']
= Continuous Queries

Continuous Queries allow an application to register a listener which will receive the entries that currently match a
query filter, and will be continuously notified of any changes to the queried data set that result from further cache
operations. This includes incoming matches, for values that have joined the set, updated matches, for matching values
that were modified and continue to match, and outgoing matches, for values that have left the set. By using a Continuous
Query the application receives a steady stream of events instead of having to repeatedly execute the same query to
discover changes, resulting in a more efficient use of resources. For instance, all of the following use cases could
utilize Continuous Queries:

* Return all persons with an age between 18 and 25 (assuming the Person entity has an `age` property and is updated by
the user application).
* Return all transactions higher than $2000.
* Return all times where the lap speed of F1 racers were less than 1:45.00s (assuming the cache contains Lap entries and
that laps are entered live during the race).

== Continuous Query Execution

A continuous query uses a listener that is notified when:

* An entry starts matching the specified query, represented by a `Join` event.
* A matching entry is updated and continues to match the query, represented by an `Update` vent.
* An entry stops matching the query, represented by a `Leave` event.

When a client registers a continuous query listener it immediately begins to receive the results currently matching the
query, received as `Join` events as described above. In addition, it will receive subsequent notifications when other
entries begin matching the query, as `Join` events, or stop matching the query, as `Leave` events, as a consequence of
any cache operations that would normally generate creation, modification, removal, or expiration events. Updated cache
entries will generate `Update` events if the entry matches the query filter before and after the operation. To
summarize, the logic used to determine if the listener receives a `Join`, `Update` or `Leave` event is:

. If the query on both the old and new values evaluate false, then the event is suppressed.
. If the query on the old value evaluates false and on the new value evaluates true, then a `Join` event is sent.
. If the query on both the old and new values evaluate true, then an `Update` event is sent.
. If the query on the old value evaluates true and on the new value evaluates false, then a `Leave` event is sent.
. If the query on the old value evaluates true and the entry is removed or expired, then a `Leave` event is sent.

[NOTE]
====
Continuous Queries can use all query capabilities except: grouping, aggregation, and sorting operations.
====

== Creating Continuous Queries

To create a continuous query, do the following:

. Create a Query object.
. Obtain the ContinuousQuery (`org.infinispan.query.api.continuous.ContinuousQuery` object of your cache by calling
the appropriate method:
 * `org.infinispan.client.hotrod.Search.getContinuousQuery(RemoteCache<K, V> cache)` for remote mode
 * `org.infinispan.query.Search.getContinuousQuery(Cache<K, V> cache)` for embedded mode

. Register the query and a continuous query listener (`org.infinispan.query.api.continuous.ContinuousQueryListener`) as follows:

[source,java]
----
continuousQuery.addContinuousQueryListener(query, listener);
----

The following example demonstrates a simple continuous query use case in embedded mode:
⁠
[source,java,title="Registering a Continuous Query"]
----
import org.infinispan.query.api.continuous.ContinuousQuery;
import org.infinispan.query.api.continuous.ContinuousQueryListener;
import org.infinispan.query.Search;
import org.infinispan.query.dsl.QueryFactory;
import org.infinispan.query.dsl.Query;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

[...]

// We have a cache of Persons
Cache<Integer, Person> cache = ...

// We begin by creating a ContinuousQuery instance on the cache
ContinuousQuery<Integer, Person> continuousQuery = Search.getContinuousQuery(cache);

// Define our query. In this case we will be looking for any Person instances under 21 years of age.
QueryFactory queryFactory = Search.getQueryFactory(cache);
Query query = queryFactory.create("FROM Person p WHERE p.age < 21");

final Map<Integer, Person> matches = new ConcurrentHashMap<Integer, Person>();

// Define the ContinuousQueryListener
ContinuousQueryListener<Integer, Person> listener = new ContinuousQueryListener<Integer, Person>() {
    @Override
    public void resultJoining(Integer key, Person value) {
        matches.put(key, value);
    }

    @Override
    public void resultUpdated(Integer key, Person value) {
        // we do not process this event
    }

    @Override
    public void resultLeaving(Integer key) {
        matches.remove(key);
    }
};

// Add the listener and the query
continuousQuery.addContinuousQueryListener(query, listener);

[...]

// Remove the listener to stop receiving notifications
continuousQuery.removeContinuousQueryListener(listener);
----

As Person instances having an age less than 21 are added to the cache they will be received by the listener and will be
placed into the `matches` map, and when these entries are removed from the cache or their age is modified to be greater
or equal than 21 they will be removed from `matches`.

== Removing Continuous Queries
To stop the query from further execution just remove the listener:

[source,java]
----
continuousQuery.removeContinuousQueryListener(listener);
----

== Continuous Query Performance

Continuous queries are designed to provide a constant stream of updates to the application, potentially resulting in a
very large number of events being generated for particularly broad queries. A new temporary memory allocation is made
for each event. This behavior may result in memory pressure, potentially leading to `OutOfMemoryErrors` (especially in
remote mode) if queries are not carefully designed. To prevent such issues it is strongly recommended to ensure that
each query captures the minimal information needed both in terms of number of matched entries and size of each match
(projections can be used to capture the interesting properties), and that each `ContinuousQueryListener` is designed
to quickly process all received events without blocking and to avoid performing actions that will lead to the generation
of new matching events from the cache it listens to.
