com.pushtechnology.diffusion.client.features.TimeSeries Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2017, 2023 DiffusionData Ltd., All Rights Reserved.
*
* Use is subject to licence terms.
*
* NOTICE: All information contained herein is, and remains the
* property of DiffusionData. The intellectual and technical
* concepts contained herein are proprietary to DiffusionData and
* may be covered by U.S. and Foreign Patents, patents in process, and
* are protected by trade secret or copyright law.
*******************************************************************************/
package com.pushtechnology.diffusion.client.features;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.stream.Stream;
import com.pushtechnology.diffusion.client.features.control.topics.TopicControl;
import com.pushtechnology.diffusion.client.session.Feature;
import com.pushtechnology.diffusion.client.session.PermissionsException;
import com.pushtechnology.diffusion.client.session.Session;
import com.pushtechnology.diffusion.client.session.SessionClosedException;
import com.pushtechnology.diffusion.client.session.SessionException;
import com.pushtechnology.diffusion.client.topics.details.TopicSpecification;
import com.pushtechnology.diffusion.client.types.PathPermission;
import com.pushtechnology.diffusion.datatype.Bytes;
import com.pushtechnology.diffusion.datatype.DataType;
/**
* This feature allows a session to update and query time series topics.
*
* Time series topics
*
*
* A time series is a sequence of events. Each event contains a value
* and has server-assigned metadata comprised of a sequence number, timestamp,
* and author. Events in a time series are ordered by increasing sequence
* number. Sequence numbers have values between {@code 0} and
* {@link Long#MAX_VALUE} and are contiguous: an event with sequence number
* {@code n} will be followed by one with sequence number {@code n + 1}. Two
* events with the same sequence number will be equal – having the same
* timestamp, author, and value.
*
*
* A time series topic allows sessions to access a time series that is
* maintained by the server. A time series topic has an associated
* {@link DataType event data type}, such as {@code Binary}, {@code String}, or
* {@code JSON}, that determines the type of value associated with each event.
*
*
* This feature provides a historic query API for time series topics, allowing a
* session to query arbitrary sub-sequences of a time series. The
* {@link TopicControl} and {@link Topics} features complete the API, providing
* ways to create and subscribe to a time series topic.
*
*
* The API presents a time series as an append-only data structure of immutable
* events that is only changed by adding new events.
*
*
Edit events
*
*
* Although a time series is append-only, an event can be overridden by
* appending an edit event. An edit event is a special type of event
* that overrides an earlier event in the time series (referred to as the
* original event) with a new value. When an edit event is added to a
* time series, the server retains both the original event and the edit event,
* allowing subscription and query results to reflect the edit.
*
*
* For example, suppose a time series has two events with the values {@code A}
* and {@code B}, and the first event has been overridden by a later edit event
* that provides a new value of {@code X}. The server has the following
* information about the time series.
*
*
*
* Sequence
* Value
* Type
*
*
*
* 0
* A
* original event
*
*
* 1
* B
* original event
*
*
* 2
* X
* edit of sequence 0
*
*
*
*
* The current value of the event with sequence number 0 is {@code X}.
*
*
* If an original event has several edit events, the latest edit event (the one
* with the highest sequence number) determines its current value. Each edit
* event refers to an original event, never to another edit event.
*
*
* Extending the example by appending a further edit event to the time series:
*
*
*
* Sequence
* Value
* Type
*
*
*
* 3
* Y
* second edit of sequence 0
*
*
*
*
* The current value of the event with sequence number 0 is now {@code Y}.
*
*
Retained range
*
*
* A time series topic retains a range of the most recent events. When a new
* event is added to the time series, older events that fall outside of the
* range are discarded. By default, this range includes the ten most recent
* events. A different range can be configured by setting the
* {@link TopicSpecification#TIME_SERIES_RETAINED_RANGE
* TIME_SERIES_RETAINED_RANGE} property.
*
*
*
Subscribing to a time series topic
*
*
* A session can {@link Topics#subscribe(String) subscribe} to a time series
* topic and
* {@link Topics#addTimeSeriesStream(String, Class, com.pushtechnology.diffusion.client.features.Topics.ValueStream)
* add a value stream} to receive updates about events appended to the time
* series. Events are represented by {@link Event} instances. Each event has a
* value and {@link EventMetadata metadata}. An edit event has two sets of
* metadata – its own metadata and that of the original event that it
* replaces.
*
*
Subscription range
*
*
* New subscribers are sent a range of events from the end of the time series.
* This is known as the subscription range. Configuring a subscription
* range is a convenient way to provide new subscribers with an appropriate
* subset of the latest events.
*
*
* The default subscription range depends on whether the topic is configured to
* publish delta streams. If delta streams are enabled, new subscribers are sent
* the latest event if one exists. If delta streams are disabled, new
* subscribers are sent no events. Delta streams are enabled by default and can
* be disabled by setting the {@link TopicSpecification#PUBLISH_VALUES_ONLY
* PUBLISH_VALUES_ONLY} property to "true".
*
*
* A larger subscription range can be configured by setting the
* {@link TopicSpecification#TIME_SERIES_SUBSCRIPTION_RANGE
* TIME_SERIES_SUBSCRIPTION_RANGE} property. Regardless of the
* {@code TIME_SERIES_SUBSCRIPTION_RANGE} property, if delta streams are
* enabled, new subscribers will be sent at least the latest event if one
* exists.
*
*
* If the range of events is insufficient, the subscribing session can use a
* {@link TimeSeries#rangeQuery() range query} to retrieve older events.
*
*
* When configuring a non-default subscription range for a time series topic,
* register value streams before subscribing to the topic. The session only
* maintains a local cache of the latest value received for a topic, not the
* full subscription range. If a value stream is added after a session has
* subscribed to a matching time series topic, the new stream will only be
* notified of the latest value.
*
*
Updating a time series topic
*
*
* A session can use {@link #append append} to submit a value to be added to a
* time series. The server will add an event to the end of the time series based
* on the supplied value, with a new sequence number, timestamp, and the author
* set to the authenticated principal of the session.
*
*
* Using {@link #append(String, Class, Object, Instant) append} allows a session
* to submit a value and supplied {@link Instant}. This provides control over
* the timestamp of the event. The supplied instant must not be before the
* latest event stored by the time series topic. There are no other
* restrictions.
*
*
* A session can use {@link #edit edit} to submit an edit to an original time
* series event, identified by its sequence number. The server will add an edit
* event to the end of the time series based on the supplied value, with a new
* sequence number, timestamp, and the author set to the authenticated principal
* of the session.
*
*
* Time series topics can also be updated using the functionality provided by
* the {@link TopicUpdate} feature. This includes
* {@link TopicUpdate#set(String, Class, Object) set},
* {@link TopicUpdate#addAndSet}, and {@link UpdateStream}s. This usage performs
* an append operation with the added benefits of {@link UpdateConstraint}s,
* topic creation when updating (upsert), and delta streams. When using methods
* from {@link TopicUpdate}, the sequence number, timestamp, and author metadata
* will be generated using the same rules as
* {@link #append(String, Class, Object)} but the associated
* {@link EventMetadata} will not be returned to the caller.
*
*
Querying a time series topic
*
* A {@link Query} is a configured query that can be evaluated for a time series
* topic using {@link Query#selectFrom selectFrom(topicPath)}. Results are
* provided as streams of {@link Event Event} instances.
*
* {@link RangeQuery} is a builder for configuring a Query that selects a range
* of a time series. There are two types of range query that differ in how edits
* are processed – value range queries and edit range queries.
*
*
Value range queries
*
*
* A value range query returns a merged view of part of a time series. This is
* the most common time series query and appropriate for most applications.
*
*
* The result of a value range query reflects the latest available edits and the
* {@link QueryResult#stream() query result stream} is ordered by the original
* event sequence number, presenting edit events instead of the original events
* they replace. Original events that have no edit events are included verbatim.
* Original events that have edit events are replaced by the latest edit event.
*
*
* A value range query of the example time series, with no range constraints so
* the entire time series is selected, returns two events:
*
*
* sequence=3, value=Y; original event sequence=0
* sequence=1, value=B
*
*
*
* The original value of the first event is not provided. It's apparent that the
* first event is an edit event because it provides the metadata of the original
* event it replaces.
*
*
Edit range queries
*
*
* Applications with auditing and other administrative requirements can access
* original event values using an edit range query. An edit range query returns
* an unmerged view of a time series that can include both original events and
* the edit events that replace them. Edit range queries are rarely needed
* – value range queries satisfy most use cases.
*
*
* Edit range queries provide a detailed view of a time series. Because this is
* potentially sensitive information, an edit range query can only be performed
* by a session that has the {@code QUERY_OBSOLETE_TIME_SERIES_EVENTS}
* permission for the target topic.
*
*
* There are two sub-types of edit range query.
*
*
* A full audit trail of edit events can be obtained using an all edits
* edit range query. The result contains all original events selected by the
* query, together with all subsequent edit events that affect the original
* events. The query result stream provides events in time series order. An all
* edits query of the example time series, with no range constraints so the
* entire time series is selected, returns four events:
*
*
* sequence=0; value=A
* sequence=1; value=B
* sequence=2; value=X; original event sequence=0
* sequence=3; value=Y; original event sequence=0
*
*
*
* A latest edits edit range query returns a query result stream in
* time series order that contains all original events selected by the query,
* together with the latest edit events that affect the original events. A
* latest edits query of the example time series, with no range constraints so
* the entire time series is selected, returns three events:
*
*
* sequence=0; value=A
* sequence=1; value=B
* sequence=3; value=Y; original event sequence=0
*
*
*
* The initial range of events delivered for a subscription to a time series
* topic is derived from a latest edits edit range query. See
* Subscription Range.
*
*
* When evaluated for a time series that has no edit events, an edit range query
* will return the same results as a similarly configured value range query.
*
*
Changes to a time series made outside the API
*
*
* The API presents a time series as an append-only data structure of immutable
* events that is only changed by adding new events. The API does not allow
* events to be deleted or edited.
*
*
* There are circumstances in which events can be removed from a time series by
* server operations outside the API. For example, a time series topic can be
* configured to discard or archive older events to save storage space; or the
* time series may be held in memory and lost if the server restarts. Subscribed
* sessions are not notified when events are removed in this way, but a session
* can infer the removal of events that are no longer included in query results.
* Similarly, an event's value can be changed on the server. For example, if an
* administrator changes its value to redact sensitive data. Again, subscribed
* sessions are not notified when events are modified, but a session can infer
* this has happened from query results.
*
*
* Whether such changes can happen for a particular time series topic depends on
* the topic specification, and the administrative actions that are allowed. To
* write a robust application, do not rely on two Event instances with the same
* sequence number but obtained though different API calls, being equal; nor
* that there are no sequence number gaps between events in query results.
*
*
Access control
*
* The session must have the {@link PathPermission#READ_TOPIC READ_TOPIC}
* permission for a topic to query a time series topic. The
* {@link PathPermission#QUERY_OBSOLETE_TIME_SERIES_EVENTS
* QUERY_OBSOLETE_TIME_SERIES_EVENTS} permission is additionally required
* to evaluate an {@link RangeQuery#forEdits edit range} query, or a
* {@link RangeQuery#forValues value range query} with an
* {@link RangeQuery#editRange edit range}.
*
* The session must have the {@link PathPermission#UPDATE_TOPIC UPDATE_TOPIC}
* permission for a topic to {@link #append(String, Class, Object) append}
* a new event to a time series topic. The
* {@link PathPermission#EDIT_TIME_SERIES_EVENTS EDIT_TIME_SERIES_EVENTS}
* permission is additionally required to
* {@link #edit(String, long, Class, Object) submit an edit} to any time series
* topic event. The more restrictive
* {@link PathPermission#EDIT_OWN_TIME_SERIES_EVENTS EDIT_OWN_TIME_SERIES_EVENTS}
* permission allows a session to submit edits to time series topic
* events that are authored by the principal of the calling session.
*
* @author DiffusionData Limited
* @since 6.0
*/
public interface TimeSeries extends Feature {
/**
* Update a time series topic by appending a new value.
*
*
* The server will add an event to the end of the time series based on the
* supplied value, with a new sequence number, timestamp, and the author set
* to the authenticated principal of the session.
*
* @param topicPath the path of the time series topic to update
* @param valueClass the type of the supplied value. This must match the
* value type of the {@link DataType} configured as the time series
* topic's {@link TopicSpecification#TIME_SERIES_EVENT_VALUE_TYPE
* event value type}.
* @param value the event value
* @return a CompletableFuture that completes when a response is received
* from the server.
*
*
* If the update was successful, the CompletableFuture will complete
* successfully and provide the {@link EventMetadata} of the new
* event.
*
*
* Otherwise, the CompletableFuture will complete exceptionally with
* a {@link CompletionException}. Common reasons for failure, listed
* by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link NoSuchTopicException} – if there is no topic
* bound to {@code topicPath};
*
*
- {@link IncompatibleTopicException} – if the topic bound
* to {@code topicPath} is not a time series topic;
*
*
- {@link IncompatibleTopicException} – if the
* {@code valueClass} does not match the event data type of the time
* series topic bound to {@code topicPath};
*
*
- {@link UpdateFailedException} – if the update failed,
* for example if the topic is set to
* {@link TopicSpecification#VALIDATE_VALUES validate values} and
* {@code value} is not valid or the server generated timestamp is
* before the most recent event;
*
*
- {@link PermissionsException} – if the calling
* session does not have {@code UPDATE_TOPIC} permission for
* {@code topicPath};
*
*
- {@link SessionClosedException} – if the session is
* closed.
*
*
* @throws IllegalArgumentException if there is no data type that supports
* values of class {@code valueClass}
*/
CompletableFuture append(String topicPath, Class valueClass, V value)
throws IllegalArgumentException;
/**
* Update a time series topic by appending a new value with a supplied
* timestamp.
*
*
* The server will add an event to the end of the time series based on the
* supplied value and timestamp, with a new sequence number, and the author
* set to the authenticated principal of the session.
*
* @param topicPath the path of the time series topic to update
* @param valueClass the type of the supplied value. This must match the
* value type of the {@link DataType} configured as the time series
* topic's {@link TopicSpecification#TIME_SERIES_EVENT_VALUE_TYPE
* event value type}.
* @param value the event value
* @param timestamp the supplied timestamp, must be greater or equal to that
* of the most recent event appended to the topic
* @return a CompletableFuture that completes when a response is received
* from the server.
*
*
* If the update was successful, the CompletableFuture will complete
* successfully and provide the {@link EventMetadata} of the new
* event.
*
*
* Otherwise, the CompletableFuture will complete exceptionally with
* a {@link CompletionException}. Common reasons for failure, listed
* by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link NoSuchTopicException} – if there is no topic
* bound to {@code topicPath};
*
*
- {@link IncompatibleTopicException} – if the topic bound
* to {@code topicPath} is not a time series topic;
*
*
- {@link IncompatibleTopicException} – if the
* {@code valueClass} does not match the event data type of the time
* series topic bound to {@code topicPath};
*
*
- {@link UpdateFailedException} – if the update failed,
* for example if the topic is set to
* {@link TopicSpecification#VALIDATE_VALUES validate values} and
* {@code value} is not valid or the supplied instant is before the
* most recent event;
*
*
- {@link PermissionsException} – if the calling
* session does not have {@code UPDATE_TOPIC} permission for
* {@code topicPath};
*
*
- {@link SessionClosedException} – if the session is
* closed.
*
*
* @throws IllegalArgumentException if there is no data type that supports
* values of class {@code valueClass}
*
* @since 6.6
*/
CompletableFuture append(String topicPath, Class valueClass, V value, Instant timestamp)
throws IllegalArgumentException;
/**
* Update a time series topic by appending a new value that overrides the
* value of an existing event.
*
*
* The existing event is identified by its sequence number and must be an
* original event.
*
*
* The server will add an edit event to the end of the time series based on
* the supplied value, with a new sequence number, timestamp, and the author
* set to the authenticated principal of the session.
*
* @param topicPath the path of the time series topic to update
* @param originalSequence the sequence number of the original event to edit
* @param valueClass the type of the supplied value. This must match the
* value type of the {@link DataType} configured as the time series
* topic's {@link TopicSpecification#TIME_SERIES_EVENT_VALUE_TYPE
* event value type}.
*
* @param value the event value
* @return a CompletableFuture that completes when a response is received
* from the server.
*
*
* If the update was successful, the CompletableFuture will complete
* successfully and provide the {@link EventMetadata} of the new
* event.
*
*
* Otherwise, the CompletableFuture will complete exceptionally with
* a {@link CompletionException}. Common reasons for failure, listed
* by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link NoSuchTopicException} – if there is no topic
* bound to {@code topicPath};
*
*
- {@link IncompatibleTopicException} – if the topic bound
* to {@code topicPath} is not a time series topic;
*
*
- {@link IncompatibleTopicException} – if the
* {@code valueClass} does not match the event data type of the time
* series topic bound to {@code topicPath};
*
*
- {@link NoSuchEventException} – if the topic does not
* have an original event with the sequence number {@code sequence},
* perhaps because the original event has been discarded;
*
*
- {@link PermissionsException} – if the calling
* session does not have the {@code UPDATE_TOPIC} permission for
* {@code topicPath} or neither of the following is true:
*
* - the calling session has the {@code EDIT_TIME_SERIES_EVENTS}
* permission for {@code topicPath};
*
- the calling session has the
* {@code EDIT_OWN_TIME_SERIES_EVENTS} permissions for
* {@code topicPath} and {@code originalSequence} refers to an
* event authored by the principal of the calling session.
*
*
* - {@link SessionClosedException} – if the session is
* closed.
*
*
* @throws IllegalArgumentException if there is no data type that supports
* values of class {@code valueClass}
*/
CompletableFuture edit(String topicPath, long originalSequence, Class valueClass, V value)
throws IllegalArgumentException;
/**
* A configured query.
*
*
* A default query that performs a value range query of an entire time
* series can be obtained using {@link TimeSeries#rangeQuery} and further
* configured using methods of the {@link RangeQuery} interface.
*
* @param query value type
*/
interface Query {
/**
* Evaluate this query for a time series topic.
*
*
* The session must have the {@code READ_TOPIC} topic permission for
* {@code topicPath} to evaluate a query. The
* {@code QUERY_OBSOLETE_TIME_SERIES_EVENTS} topic permission is also
* required if this is an {@link RangeQuery#forEdits edit range} query,
* or a {@link RangeQuery#forValues value range query} with an
* {@link RangeQuery#editRange edit range}.
*
* @param topicPath the path of the time series topic to query
*
* @return a CompletableFuture that completes when a response is
* received from the server.
*
*
* If the query returned results, the CompletableFuture will
* complete successfully and provide an {@link QueryResult}.
*
*
* Otherwise, the CompletableFuture will complete exceptionally
* with a {@link CompletionException}. Common reasons for
* failure, listed by the exception reported as the
* {@link CompletionException#getCause() cause}, include:
*
*
* - {@link NoSuchTopicException} – if there is no topic
* bound to {@code topicPath};
*
*
- {@link IncompatibleTopicException} – if the topic
* bound to {@code topicPath} is not a time series topic;
*
*
- {@link IncompatibleTopicException} – if the
* {@link RangeQuery#as query value type} is incompatible with
* the event data type of the time series topic bound to
* {@code topicPath};
*
*
- {@link InvalidQueryException} – if the range query
* is not valid for the time series;
*
*
- {@link PermissionsException} – if the calling
* session does not have {@code READ_TOPIC} permission for
* {@code topicPath};
*
*
- {@link PermissionsException} – if the calling
* session does not have
* {@code QUERY_OBSOLETE_TIME_SERIES_EVENTS} permission for
* {@code topicPath} and this is an {@link RangeQuery#forEdits
* edit range} query, or a {@link RangeQuery#forValues value
* range query} with an {@link RangeQuery#editRange edit range};
*
*
- {@link SessionClosedException} – if the session is
* closed.
*
*/
CompletableFuture> selectFrom(String topicPath);
}
/**
* Return a default range query that performs a value range query of an
* entire time series.
*
*
* Further queries with different parameters can be configured using the
* {@link RangeQuery} methods.
*
*
* The result provides {@link Bytes} values, making it compatible with any
* event data type supported by time series topics. A query with a more
* specific value type can be configured using {@link RangeQuery#as(Class)}.
*
*
* A RangeQuery equal to the one returned by this method can be created from
* an arbitrary RangeQuery as follows.
*
*
* RangeQuery defaults = anyRangeQuery
* .forValues()
* .fromStart()
* .untilLast(0)
* .limit(Long.MAX_VALUE)
* .as(Bytes.class);
*
*
* @return a RangeQuery with the default settings
*/
RangeQuery rangeQuery();
/**
* Builder for queries that select a range of events from a time series.
*
*
* See {@link TimeSeries} for an overview of the various types of range
* query:
*
* - value range queries,
*
- latest edits edit range queries, and
*
- all edits edit range queries.
*
*
*
* {@link TimeSeries#rangeQuery()} returns a default RangeQuery. Further
* queries with different parameters can be configured using the methods of
* this interface. {@link RangeQuery} instances are immutable. Each method
* returns a copy of this query with a modified setting. Method calls can be
* chained together in a fluent manner to create a query. For example:
*
*
* RangeQuery<Bytes> defaultQuery =
* session.feature(TimeSeries.class).rangeQuery();
*
* // A value range query that selects up to 100 original events from the
* // start of a time series.
* RangeQuery<Bytes> first100 = defaultQuery.forValues().fromStart().next(100);
*
*
* Creating value range queries
*
*
* A value range query returns a merged view of part of a time series. This
* is the most common time series query and appropriate for most
* applications.
*
*
* The syntax of a value range query is shown in the following diagram.
*
*
*
*
*
* A value range query begins with the {@link #forValues()} operator,
* followed by the view range. The view range determines the range
* of original events the time series that are of interest. See Range
* expressions below for the various ways to specify {@code RANGE}.
*
* The events returned by the query are constrained by an optional edit
* range, introduced by the {@link #editRange()} operator. An event
* will only be included in the result if it is in the edit range. Let's
* consider some examples to see how the view range and the edit range
* interact.
*
*
*
* Query
* Meaning
*
*
* {@code rangeQuery().forValues();}
* For each original event in the time series, either return the latest
* edit event or if it has no edit events, return the original event.
*
*
* {@code rangeQuery().forValues().from(100).to(150);}
* For each original event with a sequence number between 100 and 150
* (inclusive), either return the latest edit event or if it has no edit
* events, return the original event.
*
*
*
* {@code rangeQuery().forValues().from(100).to(150).editRange().from(400);}
*
* For each original event with a sequence number between 100 and 150
* (inclusive), return the latest edit event with a sequence number greater
* than or equal to 400.
*
* The result of this query will not include any original events because
* there is no overlap between the view range and the edit range.
*
*
*
*
* Value range queries can be further refined using the {@link #limit
* limit()} and {@link #as as()} operators.
*
*
Creating edit range queries
*
*
* An edit range query returns an unmerged view of a time series than can
* include both original events and the edit events that replace them. Edit
* range queries are rarely needed – value range queries satisfy most
* use cases.
*
*
* The syntax of an edit range query is shown in the following diagram.
*
*
*
*
*
* An edit range query begins with the {@link #forEdits()} operator,
* followed by the view range. The view range determines the range
* of original events the time series that are of interest. The result will
* only contain original events that are in the view range, and edit events
* for original events in the view range. See Range expressions
* below for the various ways to specify {@code RANGE}.
*
* The events returned by the query are constrained by an optional edit
* range, introduced by the {@link #latestEdits()} or
* {@link #allEdits()} operators. An event will only be included in the
* result if it is in the edit range. Let's consider some example edit range
* queries.
*
*
*
* Query
* Meaning
*
*
* {@code rangeQuery().forEdits();}
* Return all events in a time series.
*
*
* {@code rangeQuery().forEdits().from(100).to(150);}
* Return the original events with a sequence number between 100 and 150
* (inclusive) and all edit events in the time series that refer to the
* original events.
*
*
* {@code rangeQuery().forEdits().from(100).to(150).latestEdits();}
* Return the original events with a sequence number between 100 and 150
* (inclusive) and the latest edit events in the time series that refer to
* the original events.
*
*
*
* {@code rangeQuery().forEdits().from(100).to(150).allEdits().from(400);}
*
* For each original event with a sequence number between 100 and 150,
* (inclusive) return all edit events with a sequence number greater than or
* equal to 400.
*
* The result of this query will not include any original events because
* there is no overlap between the view range and the edit range.
*
*
*
*
* Edit range queries can be further refined using the {@link #limit
* limit()} and {@link #as as()} operators.
*
*
Range expressions
*
* Range expressions are used to specify the view and edit ranges in value
* range and edit range queries. Each range expression has an
* anchor that determines where to start, and a span that
* determines where the range ends. Both anchor and span are
* inclusive – if an anchor or span falls on an event, the
* event is included in the result.
*
*
*
*
* Both anchor and the span are optional. If the anchor is unspecified, the
* range begins at the start of the time series. If the span is unspecified,
* the range continues until the end of the time series.
*
*
Anchors
*
*
* There are five ways to specify an anchor.
*
*
*
*
*
* Anchor
* Meaning
*
*
* {@link #from(long)}
* Sets the anchor at an absolute sequence number.
*
*
* {@link #fromStart()}
* Sets the anchor at the start of the time series.
*
*
* {@link #from(Instant)}
* {@link #from(Date)}
* Sets the anchor at an absolute time.
*
*
* {@link #fromLast(long)}
* Sets the anchor at a relative offset before the end of the time
* series. For value range queries, {@code count} is the number of original
* events. For edit range queries, {@code count} is the number of events of
* any type.
*
*
* {@link #fromLast(Duration)}
* {@link #fromLastMillis(long)}
* Sets the anchor at a relative time before the timestamp of the last
* event of the time series.
*
*
*
* An anchor point can be before the start or after the end of the time
* series.
*
*
Spans
*
*
* There are nine ways to specify a span.
*
*
*
*
*
* Span
* Meaning
*
*
* {@link #to(long)}
* The range ends at an absolute sequence number. The {@code sequence}
* argument may be before or after the anchor.
*
*
* {@link #toStart()}
* The range ends at the start of the time series.
*
*
* {@link #to(Instant)}
* {@link #to(Date)}
* The range ends at an absolute time. The {@code instant} argument may
* be before or after the anchor.
*
*
* {@link #next(long)}
* The range ends at an event that is a relative number of events after
* the anchor. For value range queries, {@code count} is the number of
* original events. For edit range queries, {@code count} is the number of
* events of any type.
*
*
* {@link #next(Duration)}
* {@link #nextMillis(long)}
* The range ends at an event that is a relative time after the
* anchor.
*
*
* {@link #previous(long)}
* The range ends at an event that is a relative number of events before
* the anchor. For value range queries, {@code count} is the number of
* original events. For edit range queries, {@code count} is the number of
* events of any type.
*
*
* {@link #previous(Duration)}
* {@link #previousMillis(long)}
* The range ends at an event that is a relative time before the
* anchor.
*
*
* {@link #untilLast(long)}
* The range ends at an event that is a relative number of events before
* the end of the time series. For value range queries, {@code count} is the
* number of original events. For edit range queries, {@code count} is the
* number of events of any type.
*
*
* {@link #untilLast(Duration)}
* {@link #untilLastMillis(long)}
* The range ends at an event that is a relative time before the
* timestamp of the last event of the time series.
*
*
*
*
* A span can specify an end point that is before the start or after the end
* of the time series.
*
* If the span specifies an end point after the anchor, the range includes
* the first event at or following the anchor and ends at the last event at
* or preceding the end point. If the span specifies an end point before the
* anchor, the range includes the first event at or preceding the anchor and
* ends at the last event at or after the end point.
*
*
Using the builder methods
*
*
* Although the natural order of operators in a query is as shown in the
* syntax diagrams above, RangeQuery builder methods – those that
* return another RangeQuery – can be applied in any order with the
* following exceptions:
*
* - {@link #editRange()} only applies to value range queries, so cannot
* follow {@code forEdits()} without an intervening {@code forValues()};
*
- {@link #latestEdits()} and {@link #allEdits()} only apply to edit
* range queries, so cannot follow {@code forValues()} without an
* intervening {@code forEdits()}.
*
*
*
* Each method overrides some configuration of the RangeQuery to which it is
* applied, as summarized in the following table.
*
*
*
* Builder method
* Operator type
* Overridden configuration
*
*
* {@code forValues()}
*
* Value range
* Overrides the existing query type to create a new value range query.
* Overrides the existing view range with a new view range that selects the
* entire time series. The existing edit range is copied unchanged.
*
*
* {@code forEdits()}
* Value range
* Overrides the existing query type to create a new edit range query
* that includes all edits. Overrides the existing view range with a new
* view range that selects the entire time series. The existing edit range
* is copied unchanged.
*
*
* {@code editRange()}
* Edit range
* Overrides the existing edit range with a new edit range that selects
* the entire time series. The existing view range is copied unchanged.
* Throws {@code IllegalStateException} if this is not a value range
* query.
*
*
* {@code latestEdits()}
* {@code allEdits()}
* Edit range
* Overrides the existing edit range with a new edit range that selects
* the entire time series. The existing view range is copied unchanged.
*
* Throws {@code IllegalStateException} if this is not an edit range
* query.
*
*
*
* {@code from()}
* {@code fromStart()}
* {@code fromLast()}
* Anchor
* Overrides the anchor of the current range.
*
*
*
* {@code to()}
* {@code toStart()}
* {@code next()}
* {@code previous()}
* {@code untilLast()}
* Span
* Overrides the span of the current range.
*
*
* {@code limit()}
* Limit
*
* Overrides the limit.
*
*
* {@code as()}
* Query value type
*
* Overrides the query value type.
*
*
*
* @param query value type
* @see TimeSeries#rangeQuery()
*/
interface RangeQuery extends Query {
/**
* Return a copy of this RangeQuery configured to perform a value range
* query with the view range set to the entire time series.
*
*
* Operator type: value range
*
* @return a copy of this range query configured to perform a view range
* query with a new view range that selects the entire time
* series
*/
RangeQuery forValues();
/**
* Return a copy of this RangeQuery configured to perform an edit range
* query with the view range set to the entire time series.
*
*
* Operator type: value range
*
* @return a copy of this range query configured to perform an edit
* range query with a new view range that selects the entire
* time series
*/
RangeQuery forEdits();
/**
* Return a copy of this RangeQuery configured to perform a value range
* query with the edit range set to the entire time series.
*
*
* This operator can only be applied to value range queries. The default
* query returned by {@link TimeSeries#rangeQuery() rangeQuery()} is a
* value range query. The {@link #forValues()} operator can be used to
* create a value range query from an edit range query.
*
*
* Operator type: edit range
*
* @return a copy of this range query configured to perform a view range
* query with a new edit range that selects the entire time
* series
* @throws IllegalStateException if this is not a value range query
*/
RangeQuery editRange() throws IllegalStateException;
/**
* Return a copy of this RangeQuery configured to perform an edit range
* query with the edit range that selects all edits in the entire time
* series.
*
*
* This operator can only be applied to edit range queries. The default
* query returned by {@link TimeSeries#rangeQuery() rangeQuery()} is a
* value range query. The {@link #forEdits()} operator can be used to
* create an edit range query from a value range query.
*
*
* Operator type: edit range
*
* @return a copy of this range query configured to perform an edit
* range query with a new edit range that selects all edits in
* the entire time series
* @throws IllegalStateException if this is not an edit range query
*/
RangeQuery allEdits() throws IllegalStateException;
/**
* Return a copy of this RangeQuery configured to perform an edit range
* query with the edit range that selects latest edits in the entire
* time series.
*
*
* This operator can only be applied to edit range queries. The default
* query returned by {@link TimeSeries#rangeQuery() rangeQuery()} is a
* value range query. The {@link #forEdits()} operator can be used to
* create an edit range query from a value range query.
*
*
* Operator type: edit range
*
* @return a copy of this range query configured to perform an edit
* range query with a new edit range that selects the latest
* edits in the entire time series
* @throws IllegalStateException if this is not an edit range query
*/
RangeQuery latestEdits() throws IllegalStateException;
/**
* Return a copy of this RangeQuery with the anchor of the current range
* configured to be an absolute sequence number.
*
*
* Operator type: anchor
*
* @param sequence absolute sequence number specifying the anchor of
* the returned range
* @throws IllegalArgumentException if sequence is negative
* @return a copy of this range query with a new anchor
*/
RangeQuery from(long sequence) throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the anchor of the current range
* configured to be the start of the time series.
*
*
* There is a difference between {@code fromStart()} and {@code from(0)}
* if the range also ends before the first event of the time series. For
* example, {@code fromStart()).toStart()} is always empty, but
* {@code from(0).toStart()} includes the event with sequence number
* {@code 0}.
*
*
* Operator type: anchor
*
* @return a copy of this range query with a new anchor
*/
RangeQuery fromStart();
/**
* Return a copy of this RangeQuery with the anchor of the current range
* configured to be an absolute time.
*
*
* Operator type: anchor
*
* @param instant absolute time specifying the anchor of range
* @throws ArithmeticException if instant cannot be represented as a
* long number of milliseconds from the epoch of
* 1970-01-01T00:00:00Z, see {@link Instant#toEpochMilli()}
* @return a copy of this range query with a new anchor
*/
RangeQuery from(Instant instant)
throws ArithmeticException;
/**
* Return a copy of this RangeQuery with the anchor of the current range
* configured to be an absolute time.
*
*
* Operator type: anchor
*
* @param date absolute time specifying the anchor of range
* @return a copy of this range query with a new anchor
* @see #from(Instant)
*/
RangeQuery from(Date date);
/**
* Return a copy of this RangeQuery with the anchor of the current range
* configured to be a relative offset before the end of the time series.
*
*
* Operator type: anchor
*
* @param count specifies the anchor as a number of events before the
* end of the time series. For value range queries, count is the
* number of original events. For edit range queries, count is
* the number of events of any type.
* @throws IllegalArgumentException if count is negative
* @return a copy of this range query with a new anchor
*/
RangeQuery fromLast(long count)
throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the anchor of the current range
* configured to be a relative time from the timestamp of the last event
* in the time series.
*
*
* Operator type: anchor
*
* @param timeSpan specifies anchor relative to the timestamp of the
* latest event in the time series
* @throws IllegalArgumentException if timeSpan is negative
* @throws ArithmeticException if timeSpan cannot be represented as a
* long number of milliseconds, see {@link Duration#toMillis()}
* @return a copy of this range query with a new anchor
*/
RangeQuery fromLast(Duration timeSpan)
throws ArithmeticException, IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the anchor of the current range
* configured to be a relative time from the timestamp of the last event
* in the time series.
*
*
* Operator type: anchor
*
* @param timeSpan specifies anchor as a number of milliseconds relative
* to the timestamp of the latest event in the time series
* @throws IllegalArgumentException if timeSpan is negative
* @return a copy of this range query with a new anchor
* @see #fromLast(Duration)
*/
RangeQuery fromLastMillis(long timeSpan)
throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to end at an absolute sequence number.
*
*
* Operator type: span
*
* @param sequence absolute sequence number specifying the end of the
* returned range
* @throws IllegalArgumentException if sequence is negative
* @return a copy of this range query with a new span
*/
RangeQuery to(long sequence) throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to end at the start of the time series.
*
*
* There is a difference between {@code toStart()} and {@code to(0)} if
* the range also starts before the first event of the time series. For
* example, {@code fromStart().toStart()} is always empty, but
* {@code fromStart().to(0)} includes the event with sequence number
* {@code 0}.
*
*
* Operator type: span
*
* @return a copy of this range query with a new span
*/
RangeQuery toStart();
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to end at an absolute time.
*
*
* Operator type: span
*
* @param instant absolute time specifying the end of the range
* @throws ArithmeticException if instant cannot be represented as a
* long number of milliseconds from the epoch of
* 1970-01-01T00:00:00Z, see {@link Instant#toEpochMilli()}
* @return a copy of this range query with a new span
*/
RangeQuery to(Instant instant) throws ArithmeticException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to end at an absolute time.
*
*
* Operator type: span
*
* @param date absolute time specifying the end of the range
* @return a copy of this range query with a new span
* @see #to(Instant)
*/
RangeQuery to(Date date);
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to select a range of events following the anchor.
*
*
* Operator type: span
*
* @param count specifies the end of the range of events to select
* following the anchor. For value range queries, count is the
* number of original events. For edit range queries, count is
* the number of events of any type.
* @throws IllegalArgumentException if count is negative
* @return a copy of this range query with a new span
*/
RangeQuery next(long count) throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to select a temporal range of events following the anchor.
*
*
* Operator type: span
*
* @param timeSpan the time span of events following the anchor to
* select
* @throws IllegalArgumentException if timeSpan is negative
* @throws ArithmeticException if timeSpan cannot be represented as a
* long number of milliseconds, see {@link Duration#toMillis()}
* @return a copy of this range query with a new span
*/
RangeQuery next(Duration timeSpan)
throws ArithmeticException, IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to select a temporal range of events following the anchor.
*
*
* Operator type: span
*
* @param timeSpan the time span in milliseconds of events following the
* anchor to select
* @throws IllegalArgumentException if timeSpan is negative
* @return a copy of this range query with a new span
* @see #next(Duration)
*/
RangeQuery nextMillis(long timeSpan) throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to select a range of events preceding the anchor.
*
*
* Operator type: span
*
* @param count specifies the end of the range of events to select
* preceding the anchor. For value range queries, count is the
* number of original events. For edit range queries, count is
* the number of events of any type.
* @throws IllegalArgumentException if count is negative
* @return a copy of this range query with a new span
*/
RangeQuery previous(long count) throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to select a temporal range of events preceding the anchor.
*
*
* Operator type: span
*
* @param timeSpan the time span of events preceding the anchor to
* select
* @throws IllegalArgumentException if timeSpan is negative
* @throws ArithmeticException if timeSpan cannot be represented as a
* long number of milliseconds, see {@link Duration#toMillis()}
* @return a copy of this range query with a new span
*/
RangeQuery previous(Duration timeSpan)
throws ArithmeticException, IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to select a temporal range of events preceding the anchor.
*
*
* Operator type: span
*
* @param timeSpan the time span in milliseconds of events preceding the
* anchor to select
* @throws IllegalArgumentException if timeSpan is negative
* @return a copy of this range query with a new span
* @see #previous(Duration)
*/
RangeQuery previousMillis(long timeSpan)
throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to end a number of events before the end of the time
* series.
*
*
* Operator type: span
*
* @param count specifies the end of the range of events to select as a
* number of events before the end of the time series. For value
* range queries, count is the number of original events. For
* edit range queries, count is the number of events of any type.
* @throws IllegalArgumentException if count is negative
* @return a copy of this range query with a new span
*/
RangeQuery untilLast(long count)
throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to end at a relative time from the timestamp of the last
* event in the time series.
*
*
* Operator type: span
*
* @param timeSpan specifies the end of the range of events to select
* relative to the timestamp of the latest event in the time
* series
* @throws IllegalArgumentException if timeSpan is negative
* @throws ArithmeticException if timeSpan cannot be represented as a
* long number of milliseconds, see {@link Duration#toMillis()}
* @return a copy of this range query with a new span
*/
RangeQuery untilLast(Duration timeSpan)
throws ArithmeticException, IllegalArgumentException;
/**
* Return a copy of this RangeQuery with the span of the current range
* configured to end at a relative time from the timestamp of the last
* event in the time series.
*
*
* Operator type: span
*
* @param timeSpan specifies the end of the range of events to select as
* a number of milliseconds relative to the timestamp of the
* latest event in the time series
* @throws IllegalArgumentException if timeSpan is negative
* @return a copy of this range query with a new span
* @see #untilLast(Duration)
*/
RangeQuery untilLastMillis(long timeSpan)
throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery that returns at most count events.
*
*
* If the query would otherwise select more than count events, only the
* latest count values (those with the highest sequence numbers) are
* returned.
*
*
* This is most useful when a temporal span has been configured with
* {@link #next(Duration)} or {@link #previous(Duration)},
* where the potential number of returned events is unknown.
*
*
* {@link QueryResult#isComplete()} can be used to determine whether a
* query has returned an incomplete result.
*
*
* Operator type: limit
*
* @param count the maximum number of events to return
* @throws IllegalArgumentException if count is negative
* @return a copy of this range query with a new limit
*/
RangeQuery limit(long count)
throws IllegalArgumentException;
/**
* Return a copy of this RangeQuery with a different query value type.
*
*
* A query can only be evaluated successfully against time series topics
* with a compatible event data type. If a query method is called for a
* time series topic with an incompatible event data type, the query
* will complete exceptionally.
*
*
* If the event data type of the time series topic is known,
* compatibility of a particular {@code valueClass} can be checked using
* {@link com.pushtechnology.diffusion.datatype.DataType#canReadAs(Class)
* dataType.canReadAsClass(valueClass)}. The
* {@link TimeSeries#rangeQuery() default range query} has a query value
* type of {@link Bytes}, which is compatible with all time series value
* data types.
*
*
* Operator type: query value type
*
* @return a copy of this range query with a new query value type
*/
RangeQuery as(Class valueType);
}
/**
* Time series event metadata.
*/
interface EventMetadata {
/**
* Sequence number identifying this event within its time series.
* Assigned by the server when the event is created.
*
*
* Sequence numbers are unique within a time series. Each event appended
* to a time series is assigned a sequence number that is is equal to
* the sequence number of the preceding event plus one.
*
* @return sequence number; non-negative
*/
long sequence();
/**
* Event timestamp. Assigned by the server when the event is created.
*
*
* Events do not have unique timestamps. Events with different sequence
* numbers may have the same timestamp.
*
*
* Subsequent events in a time series usually have timestamps that are
* greater or equal to the timestamps of earlier events, but this is not
* guaranteed due to changes to the time source used by the server.
*
* @return the difference, measured in milliseconds, between the time
* the server added the event to the time series and midnight,
* January 1, 1970 UTC
*/
long timestamp();
/**
* Server-authenticated identity of the session that created the event.
*
* @return the principal that created the event, or
* {@link Session#ANONYMOUS} if the session that created the
* event was not authenticated
*/
String author();
}
/**
* An event in a time series.
*
*
* Two instances are {@link Object#equals(Object) equal} if and only if they
* have identical attributes. Typically two Event instances that have the
* same sequence number will be equal, but this may not be true if the event
* has changed on the server – see Changes to a time series made
* outside the API in the {@link TimeSeries TimeSeries} documentation.
*
* @param query value type
*/
interface Event extends EventMetadata {
/**
* The value associated with the event.
*
* @return event value
*/
V value();
/**
* If this is an edit event, returns the metadata of the original event
* that this event replaces; otherwise returns this event.
*
*
* The result is always the metadata of an original event, never that of
* an edit event.
*
* @return if equal to {@code this}, this is not an edit event;
* otherwise, the sequence number of the event replaced by this
* edit event
*/
EventMetadata originalEvent();
/**
* Return whether this is an edit event.
*
*
* {@code x.isEditEvent()} is equivalent to
* {@code x.originalEvent() != x}.
*
* @return true if this is an edit event, otherwise this is an original
* event
*/
boolean isEditEvent();
/**
* Clone this event with a different value.
*
*
* This method is useful when further transformation of the received
* value is needed, but the application wishes to preserve other event
* attributes. For example, if a Bytes value is received which the
* session wishes to interpret as JSON, it can do the following.
*
*
* Event<JSON> transformToJSON(Event<Bytes> bytesEvent) {
* JSON json = Diffusion.dataTypes().json().readValue(bytesEvent.value();
* return bytesEvent.withValue(json);
* }
*
*
*
* All attributes other than the value will be copied from this event.
* The result will only equal this event if newValue equals this event's
* value.
*
* @return a copy of this event with a different value
* @param the new value type
*/
Event withValue(T newValue);
}
/**
* Query result providing a {@link #stream() stream of events}.
*
* @param query value type
*/
interface QueryResult {
/**
* Returns the number of events selected by the query.
*
*
* This number may be greater than {@code stream().count()} due to a
* policy of the time series topic to limit the number of returned
* results, or the use of {@link RangeQuery#limit}.
*
* @return the number of events selected by the query
*/
long selectedCount();
/**
* @return the events, as a {@link java.util.stream.Stream
* java.util.stream.Stream} interface. Instances benefit from
* the various combinator and reduction methods provided by
* Stream.
*/
Stream> stream();
/**
* Returns whether this result includes all events selected by the
* query.
*
*
* Equivalent to
*
*
* return this.selectedCount() == this.stream().count();
*
*/
boolean isComplete();
/**
* Returns a description of the structure of the {@link #stream() result
* stream}.
*
* @return a StreamStructure that describes the structure of the result
* stream
*/
StreamStructure streamStructure();
/**
* Merge this result with {@code other}, combining original events and
* edit events, to produce an {@link QueryResult} of type
* {@link StreamStructure#VALUE_EVENT_STREAM VALUE_EVENT_STREAM}
*
*
* The following rules are applied to calculate the result:
*
* - If this result and {@code other} have an event with equal
* sequence numbers, the event from {@code other} is selected.
*
- An edit event is selected in place of its original event.
*
- If there are multiple edit events of an original edit, the one
* with the highest sequence is selected.
*
*
*
* The returned result implements {@link #isComplete()} to return true
* and {@link #selectedCount()} to return the count of events in the
* stream, regardless of whether this result is complete.
*/
QueryResult merge(QueryResult other);
/**
* Describes the structural properties of a stream.
*/
enum StreamStructure {
/**
* The stream is ordered by the original event sequence number,
* presenting edit events instead of the original events they
* replace.
*
*
* The original event sequence number of an event {@code e}
* is {@code e.originalEvent().sequence()}. It is equal to
* {@code e.sequence()}}, if and only if {@code e} is an original
* event.
*
*
* The stream has the following properties:
*
*
* - The sequence of each event in the stream is unique.
*
- The original event sequence of each event in the stream is
* unique.
*
- The stream is ordered by original event sequence. The
* original event sequence of each subsequent event in the stream is
* greater than its predecessor.
*
- If no events have been removed from the time series, the
* original event sequence of each subsequent event is one greater
* than its predecessor.
*
- If an event is an original event, the query found no
* corresponding edit events.
*
- If an event is an edit event, its timestamp attribute may lie
* outside the query range. Consequentially, the sequence and
* timestamp attributes of the events may be non-sequential.
*
*/
VALUE_EVENT_STREAM,
/**
* The stream is presented in time series order.
*
*
* The stream has the following properties:
*
*
* - The sequence of each event in the stream is unique.
*
- The stream is ordered by sequence. The sequence of each
* subsequent event in the stream is greater than its predecessor.
*
- Edit event timestamps may lie outside the query range.
*
- The stream can have multiple edit events for the same
* original event.
*
*/
EDIT_EVENT_STREAM;
}
}
/**
* Exception used to report a query that is invalid for the time series.
*
*
* An example invalid query is one where the anchor is a sequence number
* beyond the end of the time series (for example, it is specified using
* {@link RangeQuery#from(long)} with a sequence number greater than the
* latest sequence number, or {@link RangeQuery#fromLast(long)} with a
* {@code count} greater than the number of events in the time series),
* and the span is a relative time. Since no timestamp is associated
* with the anchor, the range is meaningless.
*/
final class InvalidQueryException extends SessionException {
private static final long serialVersionUID = 3932960724502537357L;
/**
* Constructor.
*
* @param message the exception message
*/
public InvalidQueryException(String message) {
super(message);
}
}
/**
* Exception used to report a time series topic does not have an original
* event with the sequence number provided by an {@link TimeSeries#edit
* edit} operation.
*/
final class NoSuchEventException extends SessionException {
private static final long serialVersionUID = -6796182903526667279L;
/**
* Constructor.
*
* @param message the exception message
*/
public NoSuchEventException(String message) {
super(message);
}
}
}