Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
76 changes: 76 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();
}
}
264 changes: 264 additions & 0 deletions src/NRedisStack/TimeSeries/ITimeSeriesCommands.cs

Large diffs are not rendered by default.

266 changes: 266 additions & 0 deletions src/NRedisStack/TimeSeries/ITimeSeriesCommandsAsync.cs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/NRedisStack/TimeSeries/Literals/CommandArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ internal class TimeSeriesArgs
public const string DEBUG = "DEBUG";
public const string BUCKETTIMESTAMP = "BUCKETTIMESTAMP";
public const string EMPTY = "EMPTY";
public const string EXCLUDEEMPTY = "EXCLUDEEMPTY";
public const string VALUES = "VALUES";
public const string MAX_COUNT = "MAX_COUNT";
public const String IGNORE = "IGNORE";
}
4 changes: 4 additions & 0 deletions src/NRedisStack/TimeSeries/Literals/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ internal class TS
public const string MGET = "TS.MGET";
public const string INFO = "TS.INFO";
public const string QUERYINDEX = "TS.QUERYINDEX";
public const string QUERYLABELS = "TS.QUERYLABELS";
public const string NRANGE = "TS.NRANGE";
public const string NREVRANGE = "TS.NREVRANGE";
public const string READ = "TS.READ";
}
46 changes: 46 additions & 0 deletions src/NRedisStack/TimeSeries/Literals/Enums/TimeSeriesRangeFlags.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

namespace NRedisStack.Literals.Enums;

/// <summary>
/// Flags controlling the behaviour of the multi-series range commands
/// (<c>TS.MRANGE</c> / <c>TS.MREVRANGE</c>). Consolidates the individual
/// <c>LATEST</c>, <c>WITHLABELS</c>, <c>EMPTY</c> and <c>EXCLUDEEMPTY</c> modifiers.
/// </summary>
[Flags]
public enum TimeSeriesRangeFlags
{
/// <summary>
/// No modifiers.
/// </summary>
None = 0,

/// <summary>
/// <c>LATEST</c>: for a compaction, also report the compacted value of the latest, possibly partial, bucket.
/// Ignored for non-compaction series.
/// </summary>
Latest = 1 << 0,

/// <summary>
/// <c>EMPTY</c>: when aggregating, also report aggregations for empty buckets <em>within</em> each reported
/// series. Requires an aggregation to be specified. This is the opposite concern to <see cref="ExcludeEmpty"/>,
/// which acts at the whole-series level.
/// </summary>
Empty = 1 << 1,

/// <summary>
/// <c>WITHLABELS</c>: include the label-value pairs of each series in the reply. Cannot be combined with
/// an explicit <c>selectLabels</c> collection.
/// </summary>
WithLabels = 1 << 2,

/// <summary>
/// <c>EXCLUDEEMPTY</c>: omit an entire matching series from the reply when the queried range and options
/// produce no reported samples for that series. This is the opposite concern to <see cref="Empty"/>, which
/// acts on empty buckets <em>within</em> a reported series.
/// <para>
/// Mutually exclusive with grouping (<c>GROUPBY … REDUCE …</c>): the client performs no local validation,
/// so combining this flag with a group-by results in the server error being propagated unchanged.
/// </para>
/// </summary>
ExcludeEmpty = 1 << 3,
}
Loading
Loading