using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using InfluxDB.Client.Core;
namespace InfluxDB.Client.Linq.Internal
{
internal enum ResultFunction
{
///
/// Without result function.
///
None,
///
/// Count result function.
///
Count
}
internal enum RangeExpressionType
{
///
/// Equality comparison
///
Equal,
///
/// "Less than" comparison
///
LessThan,
///
/// "Less than or equal" comparison
///
LessThanOrEqual,
///
/// "Greater than" comparison
///
GreaterThan,
///
/// "Greater than or equal" comparison
///
GreaterThanOrEqual
}
internal class LimitOffsetAssignment
{
internal string FluxFunction;
internal string N;
internal string Offset;
}
internal class QueryAggregator
{
private string _bucketAssignment;
private string _rangeStartAssignment;
private RangeExpressionType _rangeStartExpression;
private string _rangeStopAssignment;
private RangeExpressionType _rangeStopExpression;
private readonly List _limitTailNOffsetAssignments;
private ResultFunction _resultFunction;
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()
{
_resultFunction = ResultFunction.None;
_limitTailNOffsetAssignments = new List();
_filterByTags = new List();
_filterByFields = new List();
_orders = new List<(string, string, bool, string)>();
_aggregateWindow = null;
}
internal void AddBucket(string bucket)
{
_bucketAssignment = bucket;
}
internal void AddRangeStart(string rangeStart, RangeExpressionType expressionType)
{
_rangeStartAssignment = rangeStart;
_rangeStartExpression = expressionType;
}
internal void AddRangeStop(string rangeStop, RangeExpressionType expressionType)
{
_rangeStopAssignment = rangeStop;
_rangeStopExpression = expressionType;
}
internal void AddAggregateWindow(string everyVariable, string periodVariable, string fnVariable)
{
_aggregateWindow = (everyVariable, periodVariable, fnVariable);
}
internal void AddLimitTailN(string limitNAssignment, string fluxFunction)
{
if (_limitTailNOffsetAssignments.Count > 0 && _limitTailNOffsetAssignments.Last().N == null)
{
_limitTailNOffsetAssignments.Last().FluxFunction = fluxFunction;
_limitTailNOffsetAssignments.Last().N = limitNAssignment;
}
else
{
_limitTailNOffsetAssignments.Add(new LimitOffsetAssignment
{ FluxFunction = fluxFunction, N = limitNAssignment });
}
}
internal void AddLimitTailOffset(string limitOffsetAssignment)
{
if (_limitTailNOffsetAssignments.Count > 0)
{
_limitTailNOffsetAssignments.Last().Offset = limitOffsetAssignment;
}
else
{
_limitTailNOffsetAssignments.Add(new LimitOffsetAssignment { Offset = limitOffsetAssignment });
}
}
internal void AddFilterByTags(string filter)
{
_filterByTags.Add(filter);
}
internal void AddFilterByFields(string filter)
{
_filterByFields.Add(filter);
}
internal void AddSubQueries(QueryAggregator aggregator)
{
_filterByTags.AddRange(aggregator._filterByTags);
_filterByFields.AddRange(aggregator._filterByFields);
_orders.AddRange(aggregator._orders);
}
internal void AddOrder(string column, string columnVariable, bool descending, string descendingVariable)
{
_orders.Add((column, columnVariable, descending, descendingVariable));
}
internal void AddResultFunction(ResultFunction resultFunction)
{
Arguments.CheckNotNull(resultFunction, nameof(resultFunction));
_resultFunction = resultFunction;
}
internal string BuildFluxQuery(QueryableOptimizerSettings settings)
{
Arguments.CheckNotNull(settings, nameof(settings));
var transforms = new List();
var parts = new List
{
BuildOperator("from", "bucket", _bucketAssignment),
BuildRange(transforms),
BuildFilter(_filterByTags),
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))
{
parts.Add(drop);
}
parts.Add(settings.QueryMultipleTimeSeries ? "group()" : "");
parts.Add(BuildFilter(_filterByFields));
// https://docs.influxdata.com/flux/v0.x/stdlib/universe/sort/
foreach (var ((column, columnVariable, descending, descendingVariable), index) in _orders.Select(
(value, i) => (value, i)))
{
// skip default sorting if don't query to multiple time series
if (!settings.QueryMultipleTimeSeries && index == 0 && column == "_time" && !descending)
{
continue;
}
parts.Add(BuildOperator("sort", "columns", new List { columnVariable }, "desc",
descendingVariable));
}
if (settings.AlignLimitFunctionAfterPivot)
{
AddLimitFunctions(parts);
}
if (_resultFunction != ResultFunction.None)
{
if (_resultFunction == ResultFunction.Count)
{
parts.Add("stateCount(fn: (r) => true, column: \"linq_result_column\")");
parts.Add("last(column: \"linq_result_column\")");
parts.Add("keep(columns: [\"linq_result_column\"])");
}
}
var query = new StringBuilder();
query.Append(JoinList(transforms, "\n"));
query.Append("\n\n");
query.Append(JoinList(parts, " |> "));
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)
{
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();
if (settings.DropStartColumn)
{
columns.Add("\"_start\"");
}
if (settings.DropStopColumn)
{
columns.Add("\"_stop\"");
}
if (settings.DropMeasurementColumn)
{
columns.Add("\"_measurement\"");
}
if (columns.Count == 0)
{
return null;
}
return $"drop(columns: [{string.Join(", ", columns)}])";
}
private string BuildRange(List transforms)
{
string rangeStartShift = null;
string rangeStopShift = null;
if (_rangeStartAssignment != null)
{
var startShifted = $"start_shifted = int(v: time(v: {_rangeStartAssignment}))";
if (_rangeStartExpression == RangeExpressionType.GreaterThan)
{
startShifted += " + 1";
}
transforms.Add(startShifted);
rangeStartShift = "time(v: start_shifted)";
}
if (_rangeStopAssignment != null)
{
var stopShifted = $"stop_shifted = int(v: time(v: {_rangeStopAssignment}))";
if (_rangeStopExpression == RangeExpressionType.LessThanOrEqual ||
_rangeStopExpression == RangeExpressionType.Equal)
{
stopShifted += " + 1";
}
transforms.Add(stopShifted);
rangeStopShift = "time(v: stop_shifted)";
}
return BuildOperator("range", "start", rangeStartShift, "stop", rangeStopShift);
}
private string BuildFilter(IEnumerable filterBy)
{
var filters = JoinList(filterBy, " and ");
if (filters.Length == 0)
{
return null;
}
var filter = new StringBuilder();
filter.Append("filter(fn: (r) => ");
filter.Append(filters);
filter.Append(")");
return filter.ToString();
}
private string BuildOperator(string operatorName, params object[] variables)
{
var builderVariables = new StringBuilder();
for (var i = 0; i < variables.Length; i += 2)
{
var variableName = variables[i];
var variableAssignment = variables[i + 1];
if (variableAssignment == null)
{
continue;
}
if (builderVariables.Length != 0)
{
builderVariables.Append(", ");
}
builderVariables.Append(variableName);
builderVariables.Append(": ");
if (variableAssignment is IEnumerable enumerable)
{
builderVariables.Append("[");
builderVariables.Append(JoinList(enumerable, ", "));
builderVariables.Append("]");
}
else
{
builderVariables.Append(variableAssignment);
}
}
if (builderVariables.Length == 0)
{
return null;
}
var builder = new StringBuilder();
builder.Append(operatorName);
builder.Append("(");
builder.Append(builderVariables);
builder.Append(")");
return builder.ToString();
}
private StringBuilder JoinList(IEnumerable