From 12cdbee8d9eab47d7b2453af580c18a5fe4b763d Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 3 Feb 2022 15:38:48 +0100 Subject: [PATCH 1/8] feat: prepare supports for aggregateWindow in LINQ --- Client.Linq.Test/DomainObjects.cs | 8 ++ Client.Linq.Test/InfluxDBQueryVisitorTest.cs | 106 ++++++++++++++++++ Client.Linq.Test/VariableAggregatorTest.cs | 44 ++++++++ Client.Linq/InfluxDBQueryable.cs | 25 +++++ Client.Linq/Internal/QueryAggregator.cs | 28 +++++ .../Internal/QueryExpressionTreeVisitor.cs | 55 ++++++++- Client.Linq/Internal/VariableAggregator.cs | 12 +- Client/InfluxDB.Client.Api/Domain/Duration.cs | 4 +- 8 files changed, 275 insertions(+), 7 deletions(-) create mode 100644 Client.Linq.Test/VariableAggregatorTest.cs diff --git a/Client.Linq.Test/DomainObjects.cs b/Client.Linq.Test/DomainObjects.cs index 9228c5db2..b2ce56bc7 100644 --- a/Client.Linq.Test/DomainObjects.cs +++ b/Client.Linq.Test/DomainObjects.cs @@ -86,4 +86,12 @@ public class DataEntityWithLong { public long EndWithTicks { get; set; } } + + class SensorDateTimeAsField + { + [Column("data")] + public int Value { get; set; } + + [Column( "dataTime")] public DateTime DateTimeField { get; set; } + } } \ No newline at end of file diff --git a/Client.Linq.Test/InfluxDBQueryVisitorTest.cs b/Client.Linq.Test/InfluxDBQueryVisitorTest.cs index f1469340e..fe7c7b434 100644 --- a/Client.Linq.Test/InfluxDBQueryVisitorTest.cs +++ b/Client.Linq.Test/InfluxDBQueryVisitorTest.cs @@ -929,6 +929,112 @@ public void FilterByLong() Assert.AreEqual("p3", endWithTicksAssignment?.Id.Name); Assert.AreEqual("637656739543829486", (endWithTicksAssignment?.Init as IntegerLiteral)?.Value); } + + [Test] + public void AggregateWindow() + { + var query = from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40), "mean") + where s.Value == 5 + select s; + var visitor = BuildQueryVisitor(query); + + StringAssert.Contains("aggregateWindow(every: p3, period: p4, fn: p5)", visitor.BuildFluxQuery()); + + var ast = visitor.BuildFluxAST(); + + Assert.NotNull(ast); + Assert.NotNull(ast.Body); + Assert.AreEqual(6, ast.Body.Count); + + var everyAssignment = ((OptionStatement) ast.Body[2]).Assignment as VariableAssignment; + Assert.AreEqual("p3", everyAssignment?.Id.Name); + Assert.AreEqual(20000000, (everyAssignment.Init as DurationLiteral)?.Values[0].Magnitude); + Assert.AreEqual("us", (everyAssignment.Init as DurationLiteral)?.Values[0].Unit); + + var periodAssignment = ((OptionStatement) ast.Body[3]).Assignment as VariableAssignment; + Assert.AreEqual("p4", periodAssignment?.Id.Name); + Assert.AreEqual(40000000, (periodAssignment.Init as DurationLiteral)?.Values[0].Magnitude); + Assert.AreEqual("us", (periodAssignment.Init as DurationLiteral)?.Values[0].Unit); + + var fnAssignment = ((OptionStatement) ast.Body[4]).Assignment as VariableAssignment; + Assert.AreEqual("p5", fnAssignment?.Id.Name); + Assert.AreEqual("mean", (fnAssignment.Init as Identifier)?.Name); + } + + [Test] + public void AggregateWindowFluxQuery() + { + var queries = new[] + { + ( + from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40), "mean") + select s, + "aggregateWindow(every: p3, period: p4, fn: p5)", + "" + ), + ( + from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), null, "mean") + select s, + "aggregateWindow(every: p3, fn: p4)", + "" + ), + ( + from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), null, "mean") + where s.Value == 5 + select s, + "aggregateWindow(every: p3, fn: p4)", + " |> filter(fn: (r) => (r[\"data\"] == p5))" + ), + ( + from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.Value == 5 + where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), null, "mean") + select s, + "aggregateWindow(every: p4, fn: p5)", + " |> filter(fn: (r) => (r[\"data\"] == p3))" + ), + ( + from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.Deployment == "prod" + where s.Value == 5 + where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), null, "mean") + select s, + "filter(fn: (r) => (r[\"deployment\"] == p3)) |> aggregateWindow(every: p5, fn: p6)", + " |> filter(fn: (r) => (r[\"data\"] == p4))" + ), + ( + from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.Deployment == "prod" && s.Value == 5 && s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), null, "mean") + select s, + "filter(fn: (r) => (r[\"deployment\"] == p3)) |> aggregateWindow(every: p5, fn: p6)", + " |> filter(fn: (r) => (r[\"data\"] == p4))" + ) + }; + + foreach (var (queryable, expected, filter) in queries) + { + var visitor = BuildQueryVisitor(queryable); + + var flux = "start_shifted = int(v: time(v: p2))\n\nfrom(bucket: p1) |> range(start: time(v: start_shifted)) |> " + expected + " |> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") |> drop(columns: [\"_start\", \"_stop\", \"_measurement\"])" + filter; + Assert.AreEqual(flux, visitor.BuildFluxQuery()); + } + } + + [Test] + public void AggregateWindowOnlyForTimestamp() + { + var query = from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi) + where s.DateTimeField.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40), "mean") + where s.Value == 5 + select s; + + var nse = Assert.Throws(() => BuildQueryVisitor(query)); + Assert.AreEqual("AggregateWindow() has to be used only for Timestamp member, e.g. [Column(IsTimestamp = true)].", nse?.Message); + } private InfluxDBQueryVisitor BuildQueryVisitor(IQueryable queryable, Expression expression = null) { diff --git a/Client.Linq.Test/VariableAggregatorTest.cs b/Client.Linq.Test/VariableAggregatorTest.cs new file mode 100644 index 000000000..bde2ef224 --- /dev/null +++ b/Client.Linq.Test/VariableAggregatorTest.cs @@ -0,0 +1,44 @@ +using System; +using InfluxDB.Client.Api.Domain; +using InfluxDB.Client.Core.Test; +using InfluxDB.Client.Linq.Internal; +using NUnit.Framework; + +namespace Client.Linq.Test +{ + [TestFixture] + public class VariableAggregatorTest : AbstractTest + { + [Test] + public void TimeStamp() + { + var data = new[] + { + ( + TimeSpan.FromMilliseconds(1), + 1000 + ), + ( + TimeSpan.FromMilliseconds(-1), + -1000 + ), + ( + TimeSpan.FromDays(2 * 365), + 63072000000000 + ) + }; + + foreach (var (timeSpan, expected) in data) + { + var aggregator = new VariableAggregator(); + aggregator.AddNamedVariable(timeSpan); + + var duration = + (((aggregator.GetStatements()[0] as OptionStatement)?.Assignment as VariableAssignment)?.Init as + DurationLiteral)?.Values[0]; + Assert.NotNull(duration); + Assert.AreEqual(expected, duration.Magnitude); + } + } + } +} \ No newline at end of file diff --git a/Client.Linq/InfluxDBQueryable.cs b/Client.Linq/InfluxDBQueryable.cs index 5824c86d1..d72b4b5e7 100644 --- a/Client.Linq/InfluxDBQueryable.cs +++ b/Client.Linq/InfluxDBQueryable.cs @@ -237,5 +237,30 @@ public static InfluxDBQueryable ToInfluxQueryable(this IQueryable sourc return queryable; } + + /// + /// The extension to use Flux window operator. For more info see https://docs.influxdata.com/flux/v0.x/stdlib/universe/aggregatewindow/ + /// + /// + /// + /// var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", _queryApi) + /// where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40)) + /// where s.Value == 5 + /// select s; + /// + /// + /// + /// The entity value which is market as a Timestamp. + /// Duration of time between windows. + /// Duration of the window. + /// Aggregate or selector function used to operate on each window of time. + /// NotSupportedException if it's called outside LINQ expression. + /// Caused by calling outside of LINQ expression. + // ReSharper disable UnusedParameter.Global + public static bool AggregateWindow(this DateTime timestamp, TimeSpan every, TimeSpan? period = null, string fn = "mean") + { + throw new NotSupportedException("This should be used only in LINQ expression. " + + "Something like: 'where s.Timestamp.AggregateWindow(\"20s\", \"40s\")'."); + } } } \ No newline at end of file diff --git a/Client.Linq/Internal/QueryAggregator.cs b/Client.Linq/Internal/QueryAggregator.cs index 18b758796..350bb112c 100644 --- a/Client.Linq/Internal/QueryAggregator.cs +++ b/Client.Linq/Internal/QueryAggregator.cs @@ -65,6 +65,7 @@ internal class QueryAggregator private readonly List _filterByTags; private readonly List _filterByFields; private readonly List<(string, string, bool, string)> _orders; + private (string Every, string Period, string Fn)? _aggregateWindow; internal QueryAggregator() { @@ -73,6 +74,7 @@ internal QueryAggregator() _filterByTags = new List(); _filterByFields = new List(); _orders = new List<(string, string, bool, string)>(); + _aggregateWindow = null; } internal void AddBucket(string bucket) @@ -91,6 +93,12 @@ internal void AddRangeStop(string rangeStop, RangeExpressionType expressionType) _rangeStopAssignment = rangeStop; _rangeStopExpression = expressionType; } + + public void AddAggregateWindow(string everyVariable, string periodVariable, string fnVariable) + { + _aggregateWindow = (everyVariable, periodVariable, fnVariable); + } + internal void AddLimitN(string limitNAssignment) { @@ -155,6 +163,7 @@ internal string BuildFluxQuery(QueryableOptimizerSettings settings) BuildOperator("from", "bucket", _bucketAssignment), BuildRange(transforms), BuildFilter(_filterByTags), + BuildAggregateWindow(_aggregateWindow), "pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")" }; @@ -209,6 +218,25 @@ internal string BuildFluxQuery(QueryableOptimizerSettings settings) return query.ToString(); } + private string BuildAggregateWindow((string Every, string Period, string Fn)? aggregateWindow) + { + if (aggregateWindow == null) + { + return null; + } + + var (every, period, fn) = aggregateWindow.Value; + var list = new List + { + $"every: {every}", + period != null ? $"period: {period}" : null, + $"fn: {fn}" + }; + + + return $"aggregateWindow({JoinList(list, ", ")})"; + } + private string BuildDrop(QueryableOptimizerSettings settings) { var columns = new List(); diff --git a/Client.Linq/Internal/QueryExpressionTreeVisitor.cs b/Client.Linq/Internal/QueryExpressionTreeVisitor.cs index bfde9ea5f..12134eda4 100644 --- a/Client.Linq/Internal/QueryExpressionTreeVisitor.cs +++ b/Client.Linq/Internal/QueryExpressionTreeVisitor.cs @@ -2,12 +2,17 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using InfluxDB.Client.Api.Domain; using InfluxDB.Client.Core; using InfluxDB.Client.Linq.Internal.Expressions; using Remotion.Linq.Clauses; using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Clauses.ResultOperators; using Remotion.Linq.Parsing; +using BinaryExpression = System.Linq.Expressions.BinaryExpression; +using Expression = System.Linq.Expressions.Expression; +using MemberExpression = System.Linq.Expressions.MemberExpression; +using UnaryExpression = System.Linq.Expressions.UnaryExpression; namespace InfluxDB.Client.Linq.Internal { @@ -140,7 +145,51 @@ protected override Expression VisitUnary(UnaryExpression expression) return base.VisitUnary(expression); } - + + protected override Expression VisitMethodCall(MethodCallExpression expression) + { + if (expression.Method.Name.Equals("AggregateWindow")) + { + var member = (MemberExpression)expression.Arguments[0]; + if (_context.MemberResolver.ResolveMemberType(member.Member) != MemberType.Timestamp) + { + throw new NotSupportedException( + "AggregateWindow() has to be used only for Timestamp member, e.g. [Column(IsTimestamp = true)]."); + } + + // + // every + // + var every = (TimeSpan) ((ConstantExpression)expression.Arguments[1]).Value; + Arguments.CheckNotNull(every, "every"); + var everyVariable = _context.Variables.AddNamedVariable(every); + + // + // period + // + string periodVariable = null; + var period = ((ConstantExpression)expression.Arguments[2]).Value as TimeSpan?; + if (period.HasValue) + { + Arguments.CheckNotNull(period, "period"); + periodVariable = _context.Variables.AddNamedVariable(period); + } + + // + // fn + // + var fn = ((ConstantExpression)expression.Arguments[3]).Value as string; + Arguments.CheckNonEmptyString(fn, "fn"); + var fnVariable = _context.Variables.AddNamedVariable(new Identifier("Identifier", "mean")); + + _context.QueryAggregator.AddAggregateWindow(everyVariable, periodVariable, fnVariable); + + return expression; + } + + return base.VisitMethodCall(expression); + } + protected override Exception CreateUnhandledItemException(T unhandledItem, string visitMethod) { var message = $"The expression '{unhandledItem}', type: '{typeof(T)}' is not supported."; @@ -353,7 +402,7 @@ internal static void NormalizeExpressions(List parts) foreach (var index in indexes) { // () - if (parts[index + 1] is RightParenthesis) + if (parts.Count > index + 1 && parts[index + 1] is RightParenthesis) { parts.RemoveAt(index + 1); parts.RemoveAt(index); @@ -369,6 +418,8 @@ internal static void NormalizeExpressions(List parts) { parts.RemoveAt(parts.Count - 1); parts.RemoveAt(0); + + NormalizeExpressions(parts); } } } diff --git a/Client.Linq/Internal/VariableAggregator.cs b/Client.Linq/Internal/VariableAggregator.cs index 428705478..f5f6b853c 100644 --- a/Client.Linq/Internal/VariableAggregator.cs +++ b/Client.Linq/Internal/VariableAggregator.cs @@ -73,11 +73,17 @@ private Expression CreateExpression(NamedVariable variable) { var expressions = e.Cast() - .Select(o => new NamedVariable { Value = o, IsTag = variable.IsTag }) - .Select(CreateExpression) - .ToList(); + .Select(o => new NamedVariable { Value = o, IsTag = variable.IsTag }) + .Select(CreateExpression) + .ToList(); return new ArrayExpression("ArrayExpression", expressions); } + case TimeSpan timeSpan: + var timeSpanTotalMilliseconds = 1000.0 * timeSpan.TotalMilliseconds; + var duration = new Duration("Duration", (long)timeSpanTotalMilliseconds, "us"); + return new DurationLiteral("DurationLiteral", new List { duration }); + case Expression e: + return e; default: return CreateStringLiteral(variable); } diff --git a/Client/InfluxDB.Client.Api/Domain/Duration.cs b/Client/InfluxDB.Client.Api/Domain/Duration.cs index 2bfd15c90..8bd577d6c 100644 --- a/Client/InfluxDB.Client.Api/Domain/Duration.cs +++ b/Client/InfluxDB.Client.Api/Domain/Duration.cs @@ -35,7 +35,7 @@ public partial class Duration : IEquatable /// Type of AST node. /// magnitude. /// unit. - public Duration(string type = default(string), int? magnitude = default(int?), string unit = default(string)) + public Duration(string type = default(string), long magnitude = default(long), string unit = default(string)) { this.Type = type; this.Magnitude = magnitude; @@ -53,7 +53,7 @@ public partial class Duration : IEquatable /// Gets or Sets Magnitude /// [DataMember(Name="magnitude", EmitDefaultValue=false)] - public int? Magnitude { get; set; } + public long Magnitude { get; set; } /// /// Gets or Sets Unit From 9a844e36682ad61b000e8273a51d05250fa76fde Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 3 Feb 2022 15:45:34 +0100 Subject: [PATCH 2/8] chore: polish --- Client.Linq/InfluxDBQueryable.cs | 4 ++-- Client.Linq/Internal/QueryAggregator.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Client.Linq/InfluxDBQueryable.cs b/Client.Linq/InfluxDBQueryable.cs index d72b4b5e7..2a8443eb4 100644 --- a/Client.Linq/InfluxDBQueryable.cs +++ b/Client.Linq/InfluxDBQueryable.cs @@ -244,7 +244,7 @@ public static InfluxDBQueryable ToInfluxQueryable(this IQueryable sourc /// /// /// var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", _queryApi) - /// where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40)) + /// where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40), "mean") /// where s.Value == 5 /// select s; /// @@ -260,7 +260,7 @@ public static InfluxDBQueryable ToInfluxQueryable(this IQueryable sourc public static bool AggregateWindow(this DateTime timestamp, TimeSpan every, TimeSpan? period = null, string fn = "mean") { throw new NotSupportedException("This should be used only in LINQ expression. " + - "Something like: 'where s.Timestamp.AggregateWindow(\"20s\", \"40s\")'."); + "Something like: 'where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40), \"mean\")'."); } } } \ No newline at end of file diff --git a/Client.Linq/Internal/QueryAggregator.cs b/Client.Linq/Internal/QueryAggregator.cs index 350bb112c..4fe0a0266 100644 --- a/Client.Linq/Internal/QueryAggregator.cs +++ b/Client.Linq/Internal/QueryAggregator.cs @@ -94,7 +94,7 @@ internal void AddRangeStop(string rangeStop, RangeExpressionType expressionType) _rangeStopExpression = expressionType; } - public void AddAggregateWindow(string everyVariable, string periodVariable, string fnVariable) + internal void AddAggregateWindow(string everyVariable, string periodVariable, string fnVariable) { _aggregateWindow = (everyVariable, periodVariable, fnVariable); } From e918d656cebcbf8930aa9565918865d5951ca064 Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 3 Feb 2022 16:09:25 +0100 Subject: [PATCH 3/8] chore: add integration test --- Client.Linq.Test/ItInfluxDBQueryableTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Client.Linq.Test/ItInfluxDBQueryableTest.cs b/Client.Linq.Test/ItInfluxDBQueryableTest.cs index 173936234..cca64c9ce 100644 --- a/Client.Linq.Test/ItInfluxDBQueryableTest.cs +++ b/Client.Linq.Test/ItInfluxDBQueryableTest.cs @@ -454,6 +454,23 @@ orderby s.Timestamp Assert.AreEqual(8, count); } + + [Test] + public void QueryAggregateWindow() + { + var query = from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _client.GetQueryApiSync()) + where s.Timestamp.AggregateWindow(TimeSpan.FromDays(4), null, "mean") + where s.Timestamp > new DateTime(2020, 11, 15, 0, 0, 0, DateTimeKind.Utc) + where s.Timestamp < new DateTime(2020, 11, 18, 0, 0, 0, DateTimeKind.Utc) + select s; + + var sensors = query.ToList(); + + Assert.AreEqual(2, sensors.Count); + // (28 + 12 + 89) / 3 = 43 + Assert.AreEqual(43, sensors[0].Value); + Assert.AreEqual(43, sensors.Last().Value); + } [TearDown] protected void After() From 1064d2ee1a0da41fe306decb8971f997b2dff3ed Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 3 Feb 2022 20:53:56 +0100 Subject: [PATCH 4/8] chore: regenerate Duration --- Client/InfluxDB.Client.Api/Domain/Duration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/InfluxDB.Client.Api/Domain/Duration.cs b/Client/InfluxDB.Client.Api/Domain/Duration.cs index 8bd577d6c..6e5f36952 100644 --- a/Client/InfluxDB.Client.Api/Domain/Duration.cs +++ b/Client/InfluxDB.Client.Api/Domain/Duration.cs @@ -35,7 +35,7 @@ public partial class Duration : IEquatable /// Type of AST node. /// magnitude. /// unit. - public Duration(string type = default(string), long magnitude = default(long), string unit = default(string)) + public Duration(string type = default(string), long? magnitude = default(long?), string unit = default(string)) { this.Type = type; this.Magnitude = magnitude; @@ -53,7 +53,7 @@ public partial class Duration : IEquatable /// Gets or Sets Magnitude /// [DataMember(Name="magnitude", EmitDefaultValue=false)] - public long Magnitude { get; set; } + public long? Magnitude { get; set; } /// /// Gets or Sets Unit From 6b59c2d8ad0e1dd09bbbe21a40123dfb5f74a951 Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 3 Feb 2022 21:50:47 +0100 Subject: [PATCH 5/8] docs: update README.md --- Client.Linq/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Client.Linq/README.md b/Client.Linq/README.md index 9e203c346..e8bc73631 100644 --- a/Client.Linq/README.md +++ b/Client.Linq/README.md @@ -955,6 +955,29 @@ from(bucket: "my-bucket") |> filter(fn: (r) => contains(value: r["data"], set: [15, 28])) ``` +## Custom LINQ operators + +### AggregateWindow + +The `AggregateWindow` applies an aggregate function to fixed windows of time. +Can be used only for field which is defined as `timestamp` - `[Column(IsTimestamp = true)]`. +For more info about `aggregateWindow() function` see Flux's documentation - https://docs.influxdata.com/flux/v0.x/stdlib/universe/aggregatewindow/. + +```c# +var query = from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", queryApi) + where s.Timestamp.AggregateWindow(TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(40), "mean") + select s; +``` + +Flux Query: +```flux +from(bucket: "my-bucket") + |> range(start: 0) + |> aggregateWindow(every: 20s, period: 40s, fn: mean) + |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") + |> drop(columns: ["_start", "_stop", "_measurement"]) +``` + ## Domain Converter There is also possibility to use custom domain converter to transform data from/to your `DomainObject`. From d351791140eea39ac13d389e3a2ca9c9b7e68f80 Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 3 Feb 2022 21:55:31 +0100 Subject: [PATCH 6/8] docs: update README.md --- Client.Linq/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Client.Linq/README.md b/Client.Linq/README.md index e8bc73631..7b75b8dde 100644 --- a/Client.Linq/README.md +++ b/Client.Linq/README.md @@ -37,6 +37,8 @@ This section contains links to the client library documentation. - [Count](#count) - [LongCount](#longcount) - [Contains](#contains) +- [Custom LINQ operators](#custom-linq-operators) + - [AggregateWindow](#aggregatewindow) - [Domain Converter](#domain-converter) - [How to debug output Flux Query](#how-to-debug-output-flux-query) - [How to filter by Measurement](#how-to-filter-by-measurement) @@ -960,7 +962,7 @@ from(bucket: "my-bucket") ### AggregateWindow The `AggregateWindow` applies an aggregate function to fixed windows of time. -Can be used only for field which is defined as `timestamp` - `[Column(IsTimestamp = true)]`. +Can be used only for a field which is defined as `timestamp` - `[Column(IsTimestamp = true)]`. For more info about `aggregateWindow() function` see Flux's documentation - https://docs.influxdata.com/flux/v0.x/stdlib/universe/aggregatewindow/. ```c# From 0bec8e7c34a85b022f8cf11ba0fa2050f23e1b0d Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Thu, 3 Feb 2022 22:03:36 +0100 Subject: [PATCH 7/8] docs: update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c4276b33..5f36785d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## 3.3.0 [2022-02-04] +### Breaking Changes +Changed type of `Duration.magnitude` from `int?` to `long?`. + +### Features +1. [#282](https://github.com/influxdata/influxdb-client-csharp/pull/282): Add support for AggregateWindow function [LINQ] + ### Bug Fixes 1. [#277](https://github.com/influxdata/influxdb-client-csharp/pull/277): Add missing PermissionResources from Cloud API definition 1. [#281](https://github.com/influxdata/influxdb-client-csharp/pull/281): Serialization query response into POCO with optional DateTime From f81b1b2c483242cf7e2376543bbcfb1ac08d3c7d Mon Sep 17 00:00:00 2001 From: Jakub Bednar Date: Fri, 4 Feb 2022 08:28:37 +0100 Subject: [PATCH 8/8] docs: update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f36785d6..b6b8c93cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,13 @@ ## 3.4.0 [unreleased] -## 3.3.0 [2022-02-04] - ### Breaking Changes Changed type of `Duration.magnitude` from `int?` to `long?`. ### Features 1. [#282](https://github.com/influxdata/influxdb-client-csharp/pull/282): Add support for AggregateWindow function [LINQ] +## 3.3.0 [2022-02-04] + ### Bug Fixes 1. [#277](https://github.com/influxdata/influxdb-client-csharp/pull/277): Add missing PermissionResources from Cloud API definition 1. [#281](https://github.com/influxdata/influxdb-client-csharp/pull/281): Serialization query response into POCO with optional DateTime