Repository navigation
Add EXCLUDEEMPTY option to TS.MRANGE / TS.MREVRANGE - #99
Conversation
Redis 8.10 introduces EXCLUDEEMPTY to omit series with no samples in the queried range. Adds excludeEmpty/isExcludeEmpty to RsTsMRangeOptions and appends EXCLUDEEMPTY as the final command token (after FILTER/GROUPBY) via the shared MRANGE/MREVRANGE executor, signaling an error when combined with GROUPBY as Redis itself does. The executor is also split into smaller private helpers to keep method length within the project's lint limits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mumez
left a comment
There was a problem hiding this comment.
Hermes Agent Code Review
Verdict: Changes Requested re EXCLUDEEMPTY placement (1 warning + 3 suggestions).
Solid, well-tested feature: the shared-executor reuse for MREVRANGE, the deliberate exclusion of EXCLUDEEMPTY from asArray, and the conflicting-group validation all follow the package conventions. CI is green on Pharo 11/12/13. My comments are below — the main one is the wire-order discrepancy between the implementation and the PR's own documented design.
⚠️ Warning
- RsRedisEndpoint.extension.st:623 —
EXCLUDEEMPTYis emitted before theFILTERtoken, but both the official Redis grammar and this PR's own feature doc require it to be the last argument (afterFILTER filterExpr...and after anyGROUPBY). CI passes against Redis Stack, so the current server tolerates the position, but the code does not match the documented contract.
💡 Suggestions
- RsTsMRangeOptionsFrom / tsGroupByFrom / tsMRangeAggregationOptionsFrom (543–570) — three identical "if block nil else build" shapes differing only in the instantiated class. DRY up into one helper, or the duplication is fine if you prefer explicit call sites.
- tsValidateExcludeEmpty:notWithGroupBy: (583) — the
notWithGroupBy:selector reads awkwardly; consider a name that states the constraint positively. - RsTsMRangeTest.class.st:193 —
raise: Erroris too broad; assert the specificRsErrorso it can't pass on an unrelated failure.
✅ Looks Good
EXCLUDEEMPTYcorrectly omitted fromasArray— exactly as documented.- Single shared executor covers both TS.MRANGE and TS.MREVRANGE with a dedicated MREVRANGE test.
- EXCLUDEEMPTY+GROUPBY conflict raised client-side as
RsError invalidArguments, mirroring the server rule.
Reviewed by Hermes Agent
| (options isNil or: [ options isWithLabels not and: [ options selectedLabels isNil ] ]) | ||
| ifTrue: [ args add: 'WITHLABELS' ] ]. | ||
| aggOptions ifNotNil: [ args addAll: aggOptions asArray ]. | ||
| (options notNil and: [ options isExcludeEmpty ]) ifTrue: [ args add: 'EXCLUDEEMPTY' ]. |
There was a problem hiding this comment.
EXCLUDEEMPTY is appended right after the AGGREGATION options and before args add: 'FILTER', so the wire order is ... AGGREGATION ... EXCLUDEEMPTY FILTER filterExpr.... The PR body and the feature doc both state EXCLUDEEMPTY is the last token (after FILTER filterExpr... and after the optional GROUPBY ... REDUCE ... clause), and the official grammar ... FILTER <l=v...> [GROUPBY label REDUCE reducer] [EXCLUDEEMPTY] agrees. Since you require the flag to be excluded from asArray specifically because it "belongs at the very end of the whole command", it should be appended at the very end — i.e. after the groupBy ifNotNil: [args addAll: groupBy asArray] line, not after aggOptions. Tests pass against the current Redis Stack, so the server tolerates the earlier position, but the code should match your own documented design to avoid breaking on stricter parsing. Fix: move this append to the last statement of the method.
| ] | ||
|
|
||
| { #category : '*RediStick-TimeSeries-private' } | ||
| RsRedisEndpoint >> tsValidateExcludeEmpty: options notWithGroupBy: groupBy [ |
There was a problem hiding this comment.
💡 Naming / class comments — the selector tsValidateExcludeEmpty:notWithGroupBy: is hard to read (a notWithGroupBy: keyword reads oddly). Consider naming it so the constraint is stated positively, e.g. tsValidateExcludeEmptyCompatibleWithGroupBy:... or fold the check into a single well-named validator. Not blocking.
| aggregationBy: [ :agg | agg sum; bucketDuration: 1000 ] | ||
| groupBy: [ :g | g label: 'region'; reduce: 'sum' ] | ||
| using: [ :opts | opts excludeEmpty ] | ||
| ] raise: Error |
There was a problem hiding this comment.
💡 raise: Error is too broad — it will pass if any error is raised, not just the intended invalid-arguments signal. Assert the specific error class (RsError, ideally the invalidArguments instance) so the test actually guards the client-side validation and won't turn green on an unrelated failure.
Rename tsValidateExcludeEmpty:notWithGroupBy: to
tsValidateExcludeEmpty:compatibleWithGroupBy: for a clearer positive
constraint name, and tighten the GROUPBY-conflict test to assert
RsError specifically instead of the broader Error.
Kept EXCLUDEEMPTY positioned before FILTER, not after as originally
suggested: verified empirically against a live Redis 8.10 server that
FILTER swallows a trailing EXCLUDEEMPTY as an unterminated filter
expression ("ERR TSDB: failed parsing labels"). Redis's own syntax_fmt
metadata disagrees with its own worked example and with actual server
behavior here; the doc's worked example and the server are the source
of truth. Added a comment documenting this so it isn't "fixed" again
based on the misleading metadata field. Full RediStick-TimeSeries-Tests
suite still green (212/212).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Thanks for the review! Addressed the naming and test-assertion suggestions in 6ae5a71. Regarding the
This actually matches the Redis docs' own worked example ( |
Summary
Changes
RsTsMRangeOptions: addedexcludeEmpty/isExcludeEmptyaccessors (defaulting tofalse) and a CRC-style class comment;EXCLUDEEMPTYis intentionally kept out ofasArray's output since it belongs at the end of the command, not among the pre-FILTER options.RsRedisEndpoint.extension.st:tsMRangeArgsFor:cmdName:range:options:aggOptions:filterBuilder:groupBy:now appendsEXCLUDEEMPTYas the final token whenoptions isExcludeEmpty;tsExecuteMRange:rangeBy:filterBy:aggregationBy:groupBy:using:was refactored into smaller private helpers (tsMRangeOptionsFrom:,tsMRangeAggregationOptionsFrom:,tsGroupByFrom:,tsReconcileGroupBy:with:,tsValidateExcludeEmpty:notWithGroupBy:,tsMRangeResultFor:range:options:aggOptions:filterBuilder:groupBy:) and now signalsRsError invalidArgumentswhenexcludeEmptyis combined withgroupBy:, matching Redis's own restriction.RsTsMRangeOptionsTest,RsTsMRangeTest,RsTsMRevRangeTest.doc/scripting-features/feature-ts-mrange-mrevrange-excludeempty.scripting.mddocumenting the orchestration used to implement this.Commits Included
ee6fccc Add EXCLUDEEMPTY option to TS.MRANGE / TS.MREVRANGE
Files Changed
doc/scripting-features/feature-ts-mrange-mrevrange-excludeempty.scripting.md
src/RediStick-TimeSeries-Tests/RsTsMRangeOptionsTest.class.st
src/RediStick-TimeSeries-Tests/RsTsMRangeTest.class.st
src/RediStick-TimeSeries-Tests/RsTsMRevRangeTest.class.st
src/RediStick-TimeSeries/RsRedisEndpoint.extension.st
src/RediStick-TimeSeries/RsTsMRangeOptions.class.st