Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SuppressTfmSupportBuildErrors>true</SuppressTfmSupportBuildErrors>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn);CS1591;NRS003</NoWarn>
<IsWindows>$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::get_Windows())))</IsWindows>
</PropertyGroup>

Expand Down
32 changes: 32 additions & 0 deletions docs/exp/NRS003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Redis 8.10 is a relatively new release (at time of writing); the features and API may be subject to change.

This diagnostic covers the library surface for Redis 8.10 time-series features, including:

- *Series-level empty exclusion* (`TS.MRANGE` / `TS.MREVRANGE` `EXCLUDEEMPTY`): omit an entire matching
series from the reply when the queried range and options produce no reported samples for that series.
- *Label metadata queries* (`TS.QUERYLABELS`): return the distinct label names, or the distinct values of a
chosen label, across the series matching a filter.
- *Multi-series pivot ranges* (`TS.NRANGE` / `TS.NREVRANGE`): query a range across several keys and return an
outer-join-by-timestamp pivot, one row per timestamp with one value per key (missing cells reported as NaN).
- *Batched forward reads* (`TS.READ`): read a batch of samples with timestamp at or after a cursor, in
ascending order. The blocking (`BLOCK`) form is intentionally not surfaced, as it does not compose with the
SE.Redis multiplexer.

The corresponding library features must be considered subject to change:

1. Existing bindings may cease working correctly if the underlying server API changes.
2. Changes to the server API may require changes to the library API, manifesting in either/both of build-time
or run-time breaks.

While this seems *unlikely*, it must be considered a possibility. If you acknowledge this, you can suppress
this warning by adding the following to your `csproj` file:

```xml
<NoWarn>$(NoWarn);NRS003</NoWarn>
```

or more granularly / locally in C#:

```c#
#pragma warning disable NRS003
```
1 change: 1 addition & 0 deletions src/NRedisStack/Experiments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace NRedisStack
// NRS002 - Redis 8.8 multi-aggregate time-series
internal static class Experiments
{
public const string Server_8_10 = "NRS003";
// {0} is substituted with the diagnostic id, e.g. NRS042 -> https://redis.github.io/NRedisStack/exp/NRS042
public const string UrlFormat = "https://redis.github.io/NRedisStack/exp/{0}";
}
Expand Down
44 changes: 22 additions & 22 deletions src/NRedisStack/PublicAPI/PublicAPI.Shipped.txt

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions src/NRedisStack/ResponseParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,20 @@ public static IReadOnlyList<TimeSeriesTuple> ToTimeSeriesTupleArray(this RedisRe
return list;
}

public static IReadOnlyList<TimeSeriesPivotRow> ToTimeSeriesPivotRowArray(this RedisResult result)
{
RedisResult[] rows = (RedisResult[])result!;
var list = new List<TimeSeriesPivotRow>(rows.Length);
foreach (var row in rows)
{
// each row is [timestamp, [value_0, value_1, ... value_n]]; values use the same NaN-aware
// double parsing as TS.RANGE so missing samples/buckets surface as double.NaN, not null.
RedisResult[] pair = (RedisResult[])row!;
list.Add(new TimeSeriesPivotRow(ToTimeStamp(pair[0]), pair[1].ToDoubleArray()));
}
return list;
}

public static List<TimeSeriesLabel> ToLabelArray(this RedisResult result)
{
if (result.Resp3Type is ResultType.Map) // RESP3; single map
Expand Down
53 changes: 53 additions & 0 deletions src/NRedisStack/TimeSeries/DataTypes/TimeSeriesPivotRow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Text;

namespace NRedisStack.DataTypes;

/// <summary>
/// A single row of a pivot (outer-join-by-timestamp) reply from <c>TS.NRANGE</c> / <c>TS.NREVRANGE</c>:
/// a timestamp plus one cell per input key, in input-key order.
/// </summary>
public readonly struct TimeSeriesPivotRow
{
private readonly IReadOnlyList<double>? _values;

/// <summary>
/// Create a <see cref="TimeSeriesPivotRow"/>.
/// </summary>
/// <param name="timestamp">The row timestamp.</param>
/// <param name="values">One cell per input key, in input-key order.</param>
public TimeSeriesPivotRow(TimeStamp timestamp, IReadOnlyList<double> values)
{
Timestamp = timestamp;
_values = values;
}

/// <summary>
/// The row timestamp.
/// </summary>
public TimeStamp Timestamp { get; }

/// <summary>
/// The row values, one per requested aggregator, laid out in key order then aggregator order within each
/// key. With a single aggregator per key this is one value per key (<c>Values.Count == numkeys</c>); when a
/// key requests multiple aggregators, that key contributes multiple consecutive columns, so
/// <c>Values.Count</c> is the total aggregator count across all keys. A cell is <see cref="double.NaN"/>
/// when the corresponding key has no raw sample at the row timestamp, or no data for the row's aggregation
/// bucket (indistinguishable from a stored NaN) - matching how <c>TS.RANGE</c> surfaces missing values.
/// </summary>
/// <remarks>Never <see langword="null"/>; a <c>default(TimeSeriesPivotRow)</c> reports an empty list.</remarks>
public IReadOnlyList<double> Values => _values ?? Array.Empty<double>();

/// <inheritdoc/>
public override string ToString()
{
var values = Values; // coalesced, never null (guards default(TimeSeriesPivotRow))
var sb = new StringBuilder();
sb.Append("Time: ").Append(Timestamp.ToString()).Append(", Values:");
for (int i = 0; i < values.Count; i++)
{
if (i != 0) sb.Append(',');
sb.Append(values[i]);
}
return sb.ToString();
}
}
Loading
Loading