Skip to content

Temporal query operators

The experimental Java API queries one immutable committed view at a time. InMemoryEngine.ReadView and LocalDatabase.ReadView accept the same typed queries. A view selects a database knowledge cutoff; each query supplies a valid instant or interval. Both time dimensions remain explicit.

Operators and results

Query Result Ordering and bounds
Get(entity, validAtMicros) Point(OptionalLong) One present value or explicit absence
Scan(entity, window) Segments(List<TemporalSegment>) Present intervals clipped to the window, ascending valid start, adjacent equal values coalesced
Select(entities, validAtMicros, predicate) Rows(List<EntityValue>) Matching present entities, ascending exact entity string order; at most one row per candidate
FilterScan(entity, window, predicate) Segments(List<TemporalSegment>) Matching resolved intervals with the same clipping and ordering as Scan; omitted values and absence remain gaps
Sum(entities, validAtMicros) Aggregate(count, BigInteger sum) Present count and exact mathematical sum; empty input returns zero/zero

Candidate lists for Select and Sum contain distinct nonnull entity IDs. Empty lists are valid and inspect no entities. Constructors copy the lists, reject repeated identities and retain exact strings without normalization. Selection returns only the supplied candidates; it does not enumerate the database.

Selection sorts by EntityId.value().compareTo: Java's lexicographic UTF-16 code unit order, independent of candidate order, hash iteration, locale or numeric suffix. Case and normalization-distinct identities remain distinct. This order can differ from Unicode code-point order for supplementary characters. Journal identities retain those same UTF-16 code units, including embedded zero and lone surrogates.

Numeric predicates

new ValuePredicate(operator, operand) compares a present signed 64-bit value against one signed 64-bit operand:

Operator Match
EQ Equal
NE Not equal
LT Less than
LE Less than or equal
GT Greater than
GE Greater than or equal

Comparisons use signed relations without subtraction, so minimum/maximum values cannot overflow the comparison. Zero and negative values are ordinary values. Absence matches none of the operators, including NE; it is not a zero value. The predicate and query arguments are immutable data, with no application callback invoked during a query. Predicate composition, arbitrary functions and SQL syntax are outside the current API.

Resolve temporal state before filtering

For a fixed entity, valid instant and committed cutoff, the latest applicable operation determines the value or absence. Only then is its value compared. An older matching put cannot reappear because a newer correction fails a predicate. A retraction cannot reveal an earlier matching value either.

For example, Alice is 100 everywhere at commit one. Commit two replaces [10,20) with 120; commit three retracts [12,14). At cutoff three, filtering for EQ 100 returns only the parts outside [10,20). Filtering for GE 110 returns [10,12) and [14,20). The retracted gap stays absent. Earlier cutoffs still return their original results.

A filtered scan clips matching intervals to the requested half-open window. It retains the original scan's canonical unbounded endpoint handling at finite coordinate limits. Equal matches separated by absence or an excluded value stay separate. A completely absent or rejected window returns an empty list.

Java example

With an open view, explicit candidate IDs and valid instant:

var query = new TemporalQuery.Select(List.of(bob, alice), 15,
        new ValuePredicate(ValuePredicate.Operator.GT, 75));
var rows = (TemporalResult.Rows) view.query(query);

var matchingIntervals = view.query(new TemporalQuery.FilterScan(alice,
        new ValidTimeInterval(0L, 30L),
        new ValuePredicate(ValuePredicate.Operator.GE, 110)));

Types are in org.meridiandb.temporal; List is java.util.List. Use openView(ReadCutoff.Head.INSTANCE) to capture head once, or new ReadCutoff.At(position) for an existing historical commit in that lineage. A future or foreign cutoff fails rather than waiting or clamping. There is no hidden wall-clock valid instant.

Run the complete example from the repository root after ./mvnw -B verify:

java --class-path target/classes examples/QueryExample.java

It checks a pinned pre-correction view, a new view, selection and filtered interval answers, and the exact sum before/after a correction and retraction. The same query values work on local database views, including verified reopen.

Snapshot lifetimes and execution costs

Every query captures one immutable snapshot before reading any entity. Selection and aggregation therefore cannot mix commits across candidates. A pinned view's position and answers remain unchanged during writer commits. Opening a new head view observes newer knowledge. Query operations share the existing view/owner close contract: close rejects future operations; a call that already captured state may finish. Returned rows, segments and aggregate values remain immutable heap values after view/database close and hold no file or cursor handle.

Execution is eager. Get linearly examines an entity's intervals. Select examines candidate entities' interval lists and sorts its matching rows, adding O(m log m) sorting for m matches. Scan and FilterScan visit the requested entity's retained state intervals; filtering allocates only matching clipped segments. Sum uses exact BigInteger arithmetic over present candidates.

The explicit candidate list and valid-time window bound logical scope. They do not impose a total query-memory or work budget: all matching results are materialized, and even a narrow window may inspect the full entity interval list. There is no index, pagination, streaming cursor or cancellation contract. Retained history, replay costs and writer map-copy costs remain those of the embedded engine and local database. No latency or throughput result is implied.

Verify and reproduce

The query fixtures add exact expected selection/filter answers while retaining the original temporal histories. The reference evaluates comparisons independently and resolves transaction history backwards; production reads materialized interval state. Persisted-seed comparisons also use forward dense cells to check clipped intervals and rows.

./mvnw -B -Dtest=QueryTypesTest,QueryLifecycleTest,QueryFixtureTest,QueryPropertiesTest test

Reproduce a reported seed:

./mvnw -B -Dtest=QueryPropertiesTest -Dmeridiandb.seed=42 test

Coverage includes all comparison operators, extreme values/time coordinates, absence, empty inputs, duplicate rejection, exact entity ordering, corrections, retractions, historical cutoffs and real writable reopen with checkpoint tails. Concurrent checks verify new operators within pinned views while atomic writes run. Directory-sync capability may prevent local-file cases on an unsupported provider; memory/reference checks remain independent. The full suite retains the separate durability conformance boundaries.