Skip to content

TimeSeries: support multiple aggregators in TS.RANGE/TS.MRANGE (CAE-792) - #4504

Merged
uglide merged 17 commits into
masterfrom
topic/ggivo/CAE-792-mulitple-aggregators
May 18, 2026
Merged

uglide merged 17 commits into
masterfrom
topic/ggivo/CAE-792-mulitple-aggregators

Conversation

@ggivo

@ggivo ggivo commented Apr 28, 2026 •

Copy link
Copy Markdown
Collaborator

Extend the existing tsRange/tsRevRange/tsMRange/tsMRevRange family to accept multiple aggregators in a single call, matching the new server syntax AGGREGATION agg1,agg2,…,aggN bucketDur (RedisTimeSeries 8.8).

Changes:

  • TSElement: carry List<Double> values internally; existing(long, double) constructor preserved (wraps in a singleton list); getValue() returns values.get(0) for backward compatibility; new getValues() exposes the full positional list.
  • TSRangeParams / TSMRangeParams: new aggregation(AggregationType[],long) setter; Wire format is identical to the previous single-aggregator output when length == 1.
  • TimeSeriesBuilderFactory.TIMESERIES_ELEMENT_LIST: parse positional [ts, v1, …, vN] samples; size-2 takes the existing single-value path, size > 2 builds a multi-value TSElement.

No new public methods on RedisTimeSeriesCommands / RedisTimeSeriesPipelineCommands


Note

Medium Risk
Changes the TSElement data model and time series range parameter encoding to support multi-aggregator responses, which may affect equality/hashCode semantics and downstream code that assumes a single value per sample.

Overview
Adds support for RedisTimeSeries multi-aggregator syntax (AGGREGATION a,b,c bucket) across TS.RANGE/TS.REVRANGE and TS.MRANGE/TS.MREVRANGE by allowing TSRangeParams/TSMRangeParams to accept AggregationType[] and encoding aggregators as a comma-joined token.

Updates TSElement to carry multiple values per timestamp (getValues()), while keeping getValue() returning the first value for backward compatibility, and extends response parsing to handle [ts, v1, ..., vN] samples.

Expands test coverage for wire-format validation and new multi-aggregator behaviors (including empty results) and adjusts cluster time series ITs to disable the new label-based multi-range tests; also updates pom.xml formatter includes for timeseries tests.

Reviewed by Cursor Bugbot for commit 23a26e9. Bugbot is set up for automated code reviews on this repo. Configure here.

ggivo added 2 commits April 28, 2026 15:56
Extend the existing tsRange/tsRevRange/tsMRange/tsMRevRange family to
accept multiple aggregators in a single call, matching the new server
syntax `AGGREGATION agg1,agg2,…,aggN bucketDur` (RedisTimeSeries 8.8).

Changes:
- TSElement: carry `List<Double> values` internally;
  existing`(long, double)` constructor preserved (wraps in a singleton list);
  `getValue()` returns `values.get(0)` for backward compatibility;
  new `getValues()` exposes the full positional list.
- TSRangeParams / TSMRangeParams:
  new `aggregation(AggregationType[],long)` setter;
  Wire format is identical to the previous single-aggregator output when length == 1.
- TimeSeriesBuilderFactory.TIMESERIES_ELEMENT_LIST:
  parse positional `[ts, v1, …, vN]` samples;
  size-2 takes the existing single-value path,
  size > 2 builds a multi-value TSElement.

No new public methods on RedisTimeSeriesCommands / RedisTimeSeriesPipelineCommands
@jit-ci

jit-ci Bot commented Apr 28, 2026 •

Copy link
Copy Markdown

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

@github-actions

github-actions Bot commented Apr 28, 2026 •

Copy link
Copy Markdown

Test Results

  191 files  ± 0    191 suites  ±0   9m 44s ⏱️ -16s
7 690 tests +16  7 200 ✅  - 422  490 💤 +438  0 ❌ ±0 
7 698 runs  + 4  7 200 ✅  - 442  498 💤 +446  0 ❌ ±0 

Results for commit 23a26e9. ± Comparison against base commit a3d6d95.

This pull request removes 14 and adds 30 tests. Note that renamed tests count towards both.
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[1] ‑ testScorer(Scorer, double, double)[1][1]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[1] ‑ testScorer(Scorer, double, double)[1][2]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[1] ‑ testScorer(Scorer, double, double)[1][3]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[1] ‑ testScorer(Scorer, double, double)[1][4]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[1] ‑ testScorer(Scorer, double, double)[1][5]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[1] ‑ testScorer(Scorer, double, double)[1][6]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[1] ‑ testScorer(Scorer, double, double)[1][7]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[2] ‑ testScorer(Scorer, double, double)[2][1]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[2] ‑ testScorer(Scorer, double, double)[2][2]
redis.clients.jedis.commands.unified.search.FTHybridCommandsTestBase$SupportedScorersTest[2] ‑ testScorer(Scorer, double, double)[2][3]
…
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ mRangeMultipleAggregators()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ mRangeMultipleAggregatorsEmptyResult()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ mRevRangeMultipleAggregators()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ mRevRangeMultipleAggregatorsEmptyResult()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ rangeMultipleAggregators()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ rangeMultipleAggregatorsEmptyResult()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ rangeSingleAggregatorViaArraySetter()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ revRangeMultipleAggregators()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[1] ‑ revRangeMultipleAggregatorsEmptyResult()[1]
redis.clients.jedis.commands.unified.client.timeseries.TimeSeriesRedisClientCommandsIT[2] ‑ mRangeMultipleAggregators()[2]
…
This pull request skips 408 tests.
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][10]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][11]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][12]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][1]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][2]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][3]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][4]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][5]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][6]
redis.clients.jedis.commands.commandobjects.CommandObjectsJsonCommandsTest[1] ‑ testJsonSetWithFphaParams(String, Keyword, FphaType)[1][7]
…

♻️ This comment has been updated with latest results.

Comment thread src/main/java/redis/clients/jedis/timeseries/TSElement.java Outdated
Comment thread src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java Outdated
Comment thread src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java
Comment thread src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java
 - aggregator null check
 - accidentally commented lines
@ggivo
ggivo force-pushed the topic/ggivo/CAE-792-mulitple-aggregators branch from 8222930 to 2ce2dce Compare April 28, 2026 17:47
Comment thread src/main/java/redis/clients/jedis/timeseries/TSRangeParams.java
 - unmodifiable list
Comment thread src/main/java/redis/clients/jedis/timeseries/TSElement.java
@ggivo
ggivo requested review from atakavci and uglide April 29, 2026 05:12
@ggivo ggivo added the feature label May 5, 2026
@ggivo ggivo added this to the 8.0.0 milestone May 5, 2026
Comment thread src/main/java/redis/clients/jedis/timeseries/TSElement.java
Comment thread src/main/java/redis/clients/jedis/timeseries/TSMRangeParams.java Outdated
Comment thread src/main/java/redis/clients/jedis/timeseries/TSMRangeParams.java Outdated
Co-authored-by: Igor Malinovskiy <u.glide@gmail.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e60d8a1. Configure here.

public TSRangeParams aggregation(AggregationType[] aggregators, long bucketDuration) {
if (aggregators != null) {
if (aggregators.length == 0) {
throw new IllegalArgumentException("At least one aggregator is required");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent error messages between TSRangeParams and TSMRangeParams

Low Severity

TSRangeParams throws "At least one aggregator is required" for an empty aggregators array, while TSMRangeParams throws "Aggregators must be non-null and non-empty" for the same condition. These two parallel methods perform identical validation but use different wording, making the API inconsistent. The PR discussion explicitly suggested the TSMRangeParams wording as the preferred message.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e60d8a1. Configure here.

@uglide
uglide merged commit cb1bc39 into master May 18, 2026
20 checks passed
@uglide
uglide deleted the topic/ggivo/CAE-792-mulitple-aggregators branch May 18, 2026 15:19
ggivo added a commit that referenced this pull request May 26, 2026
…ow-up to #4504) (#4537)

* TimeSeries: use singletonList for single-value TSElement (follow-up to #4504)

* unused import

* TimeSeries: reduce overhead on single-aggregator TSElement path (follow-up to #4504)

* TSElement: make equals symmetric across MultiValueTSElement when values match

* typo

* address review comments - equals/hashcode consistency;

---------

Co-authored-by: atakavci <a_takavci@yahoo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants