diff --git a/CHANGELOG.md b/CHANGELOG.md index dd9f1dbf0..8c70ae8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## 4.2.0 [unreleased] +### Features +1. [#319](https://github.com/influxdata/influxdb-client-csharp/pull/319): Optionally align `limit()` and `tail()` before `pivot()` function [LINQ] + ### Breaking Changes 1. [#316](https://github.com/influxdata/influxdb-client-csharp/pull/316): Rename `InvocableScripts` to `InvokableScripts` diff --git a/Client.Linq.Test/InfluxDBQueryVisitorTest.cs b/Client.Linq.Test/InfluxDBQueryVisitorTest.cs index 0d01d2cac..3f3006d70 100644 --- a/Client.Linq.Test/InfluxDBQueryVisitorTest.cs +++ b/Client.Linq.Test/InfluxDBQueryVisitorTest.cs @@ -1108,6 +1108,32 @@ public void FilterByTimeAndTagWithAnds() Assert.AreEqual(expected, visitor.BuildFluxQuery()); } + [Test] + public void AlignLimitFunctionBeforePivot() + { + var query = from s in InfluxDBQueryable.Queryable("my-bucket", "my-org", _queryApi, + new QueryableOptimizerSettings { AlignLimitFunctionAfterPivot = false }) + select s; + + var visitor = BuildQueryVisitor(query, MakeExpression(query, q => q.TakeLast(10))); + var expected = "start_shifted = int(v: time(v: p2))\n\n" + + "from(bucket: p1) " + + "|> range(start: time(v: start_shifted)) " + + "|> tail(n: p3) " + + "|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") " + + "|> drop(columns: [\"_start\", \"_stop\", \"_measurement\"])"; + Assert.AreEqual(expected, visitor.BuildFluxQuery()); + + visitor = BuildQueryVisitor(query, MakeExpression(query, q => q.Take(10))); + expected = "start_shifted = int(v: time(v: p2))\n\n" + + "from(bucket: p1) " + + "|> range(start: time(v: start_shifted)) " + + "|> limit(n: p3) " + + "|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") " + + "|> drop(columns: [\"_start\", \"_stop\", \"_measurement\"])"; + Assert.AreEqual(expected, visitor.BuildFluxQuery()); + } + private InfluxDBQueryVisitor BuildQueryVisitor(IQueryable queryable, Expression expression = null) { var queryExecutor = (InfluxDBQueryExecutor)((DefaultQueryProvider)queryable.Provider).Executor; diff --git a/Client.Linq/InfluxDBQueryable.cs b/Client.Linq/InfluxDBQueryable.cs index bccaf15ae..d03e9f5b4 100644 --- a/Client.Linq/InfluxDBQueryable.cs +++ b/Client.Linq/InfluxDBQueryable.cs @@ -21,6 +21,7 @@ public QueryableOptimizerSettings() { QueryMultipleTimeSeries = false; AlignFieldsWithPivot = true; + AlignLimitFunctionAfterPivot = true; DropMeasurementColumn = true; DropStartColumn = true; DropStopColumn = true; @@ -42,6 +43,12 @@ public QueryableOptimizerSettings() /// public bool AlignFieldsWithPivot { get; set; } + /// + /// Gets or set whether the drive should align limit() and tail() functions + /// after pivot() function. + /// + public bool AlignLimitFunctionAfterPivot { get; set; } + /// /// Gets or sets whether the _measurement column will be dropped from query results. /// Setting this variable to true will change how the produced Flux Query looks like: diff --git a/Client.Linq/Internal/QueryAggregator.cs b/Client.Linq/Internal/QueryAggregator.cs index 02acd5ff0..fb5286c68 100644 --- a/Client.Linq/Internal/QueryAggregator.cs +++ b/Client.Linq/Internal/QueryAggregator.cs @@ -166,12 +166,19 @@ internal string BuildFluxQuery(QueryableOptimizerSettings settings) BuildOperator("from", "bucket", _bucketAssignment), BuildRange(transforms), BuildFilter(_filterByTags), - BuildAggregateWindow(_aggregateWindow), - settings.AlignFieldsWithPivot - ? "pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")" - : "" + BuildAggregateWindow(_aggregateWindow) }; + if (!settings.AlignLimitFunctionAfterPivot) + { + AddLimitFunctions(parts); + } + + if (settings.AlignFieldsWithPivot) + { + parts.Add("pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")"); + } + var drop = BuildDrop(settings); if (!string.IsNullOrEmpty(drop)) { @@ -195,14 +202,10 @@ internal string BuildFluxQuery(QueryableOptimizerSettings settings) descendingVariable)); } - // https://docs.influxdata.com/flux/v0.x/stdlib/universe/limit/ - foreach (var limitNOffsetAssignment in _limitTailNOffsetAssignments) - if (limitNOffsetAssignment.N != null) - { - parts.Add(BuildOperator(limitNOffsetAssignment.FluxFunction, - "n", limitNOffsetAssignment.N, - "offset", limitNOffsetAssignment.Offset)); - } + if (settings.AlignLimitFunctionAfterPivot) + { + AddLimitFunctions(parts); + } if (_resultFunction != ResultFunction.None) { @@ -223,6 +226,19 @@ internal string BuildFluxQuery(QueryableOptimizerSettings settings) return query.ToString(); } + private void AddLimitFunctions(List parts) + { + // https://docs.influxdata.com/flux/latest/stdlib/universe/limit/ + // https://docs.influxdata.com/flux/latest/stdlib/universe/tail/ + foreach (var limitNOffsetAssignment in _limitTailNOffsetAssignments) + if (limitNOffsetAssignment.N != null) + { + parts.Add(BuildOperator(limitNOffsetAssignment.FluxFunction, + "n", limitNOffsetAssignment.N, + "offset", limitNOffsetAssignment.Offset)); + } + } + private string BuildAggregateWindow((string Every, string Period, string Fn)? aggregateWindow) { if (aggregateWindow == null) diff --git a/Client.Linq/README.md b/Client.Linq/README.md index 96609083f..dfe0d4432 100644 --- a/Client.Linq/README.md +++ b/Client.Linq/README.md @@ -851,6 +851,18 @@ from(bucket: "my-bucket") |> limit(n: 10) ``` +**_Note:_** the `limit()` function can be align before `pivot()` function by: + +```c# +var optimizerSettings = + new QueryableOptimizerSettings + { + AlignLimitFunctionAfterPivot = false + }; +``` + +**_Performance:_** The `pivot()` is a [“heavy” function](https://docs.influxdata.com/influxdb/cloud/query-data/optimize-queries/#use-heavy-functions-sparingly). Using `limit()` before `pivot()` is much faster but works only if you have consistent data series. See [#318](https://github.com/influxdata/influxdb-client-csharp/issues/318) for more details. + ### TakeLast ```c# @@ -869,6 +881,17 @@ from(bucket: "my-bucket") |> tail(n: 10) ``` +**_Note:_** the `tail()` function can be align before `pivot()` function by: + +```c# +var optimizerSettings = + new QueryableOptimizerSettings + { + AlignLimitFunctionAfterPivot = false + }; +``` +**_Performance:_** The `pivot()` is a [“heavy” function](https://docs.influxdata.com/influxdb/cloud/query-data/optimize-queries/#use-heavy-functions-sparingly). Using `tail()` before `pivot()` is much faster but works only if you have consistent data series. See [#318](https://github.com/influxdata/influxdb-client-csharp/issues/318) for more details. + ### Skip ```c# diff --git a/Client.Test/AbstractItClientTest.cs b/Client.Test/AbstractItClientTest.cs index c8a90d869..a81fe090d 100644 --- a/Client.Test/AbstractItClientTest.cs +++ b/Client.Test/AbstractItClientTest.cs @@ -3,10 +3,8 @@ using System.Linq; using System.Threading.Tasks; using InfluxDB.Client.Api.Domain; -using InfluxDB.Client.Core; using InfluxDB.Client.Core.Test; using NUnit.Framework; -using Task = System.Threading.Tasks.Task; namespace InfluxDB.Client.Test {