Skip to content

Commit

Permalink
first draft of influx string functions, fixes influxdata#499
Browse files Browse the repository at this point in the history
  • Loading branch information
jogibear9988 committed Apr 3, 2023
1 parent 30c1b61 commit 018f4bf
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Client.Linq.Test/InfluxDBQueryVisitorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ public void InitQueryApi()
_queryApi = new Mock<QueryApiSync>(options, queryService.Object, new FluxResultMapper()).Object;
}

[Test]
public void StringFunctionsQuery() {
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", _queryApi)
where s.SensorId.ToLower().Contains("aaa")
select s;
var visitor = BuildQueryVisitor(query);

const string expected = "import \"strings\"\nstart_shifted = int(v: time(v: p2))\n\nfrom(bucket: p1) |> range(start: time(v: start_shifted)) |> filter(fn: (r) => strings.containsStr(v: strings.toLower(v: r[\"sensor_id\"]), substr: p3)) |> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") |> drop(columns: [\"_start\", \"_stop\", \"_measurement\"]) |> filter(fn: (r) => strings.containsStr(v: strings.toLower(v: r[\"sensor_id\"]), substr: p3))";
var qry = visitor.BuildFluxQuery();
Assert.AreEqual(expected, visitor.BuildFluxQuery());
}

[Test]
public void DefaultQuery()
{
Expand Down
45 changes: 45 additions & 0 deletions Client.Linq/Internal/Expressions/StringFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions {
internal class StringFunction : IExpressionPart {
private readonly string functionName;
private readonly IEnumerable<IExpressionPart> expressionParts;
private readonly IEnumerable<IExpressionPart> expressionPartsPar2;

internal StringFunction(string functionName, IEnumerable<IExpressionPart> expressionParts, IEnumerable<IExpressionPart> expressionPartsPar2) {
this.functionName = functionName;
this.expressionParts = expressionParts;
this.expressionPartsPar2 = expressionPartsPar2;
}

public void AppendFlux(StringBuilder builder) {
builder.Append("strings.");
builder.Append(functionName);
builder.Append("(v: ");
foreach (var expressionPart in expressionParts) {
expressionPart.AppendFlux(builder);
}
if (functionName == "containsStr")
{
builder.Append(", substr: ");
foreach (var expressionPart in expressionPartsPar2) {
expressionPart.AppendFlux(builder);
}
}
else if (functionName == "hasPrefix") {
builder.Append(", prefix: ");
foreach (var expressionPart in expressionPartsPar2) {
expressionPart.AppendFlux(builder);
}
}
else if (functionName == "hasSuffix") {
builder.Append(", suffix: ");
foreach (var expressionPart in expressionPartsPar2) {
expressionPart.AppendFlux(builder);
}
}
builder.Append(")");
}
}
}
19 changes: 19 additions & 0 deletions Client.Linq/Internal/QueryAggregator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ internal class QueryAggregator
private ResultFunction _resultFunction;
private readonly List<string> _filterByTags;
private readonly List<string> _filterByFields;
private HashSet<string> _imports;
private readonly List<(string, string, bool, string)> _orders;
private (string Every, string Period, string Fn)? _aggregateWindow;

Expand All @@ -74,6 +75,7 @@ internal QueryAggregator()
_limitTailNOffsetAssignments = new List<LimitOffsetAssignment>();
_filterByTags = new List<string>();
_filterByFields = new List<string>();
_imports = null;
_orders = new List<(string, string, bool, string)>();
_aggregateWindow = null;
}
Expand All @@ -83,6 +85,15 @@ internal void AddBucket(string bucket)
_bucketAssignment = bucket;
}

internal void AddImport(string import)
{
if (_imports == null)
{
_imports = new HashSet<string>();
}
_imports.Add(import);
}

internal void AddRangeStart(string rangeStart, RangeExpressionType expressionType)
{
_rangeStartAssignment = rangeStart;
Expand Down Expand Up @@ -219,6 +230,7 @@ internal string BuildFluxQuery(QueryableOptimizerSettings settings)

var query = new StringBuilder();

query.Append(BuildImports());
query.Append(JoinList(transforms, "\n"));
query.Append("\n\n");
query.Append(JoinList(parts, " |> "));
Expand Down Expand Up @@ -334,6 +346,13 @@ private string BuildFilter(IEnumerable<string> filterBy)
return filter.ToString();
}

private string BuildImports()
{
if (_imports != null)
return string.Join("", _imports.Select(x => "import \"" + x + "\"\n"));
return string.Empty;
}

private string BuildOperator(string operatorName, params object[] variables)
{
var builderVariables = new StringBuilder();
Expand Down
67 changes: 67 additions & 0 deletions Client.Linq/Internal/QueryExpressionTreeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,63 @@ protected override Expression VisitMethodCall(MethodCallExpression expression)
return expression;
}

if (expression.Method.DeclaringType == typeof(string))
{
if (expression.Method.Name.Equals("ToLower"))
{
_context.QueryAggregator.AddImport("strings");
var partsCount = this._expressionParts.Count;
this.Visit(expression.Object);
var part = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
this._expressionParts.Add(new StringFunction("toLower", part, null));
return expression;
}
else if (expression.Method.Name.Equals("ToUpper"))
{
_context.QueryAggregator.AddImport("strings");
var partsCount = this._expressionParts.Count;
this.Visit(expression.Object);
var part = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
this._expressionParts.Add(new StringFunction("toUpper", part, null));
return expression;
}
else if (expression.Method.Name.Equals("Contains"))
{
_context.QueryAggregator.AddImport("strings");
var partsCount = this._expressionParts.Count;
this.Visit(expression.Object);
var part = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
partsCount = this._expressionParts.Count;
this.Visit(expression.Arguments[0]);
var part2 = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
this._expressionParts.Add(new StringFunction("containsStr", part, part2));
return expression;
}
else if (expression.Method.Name.Equals("StartsWith")) {
_context.QueryAggregator.AddImport("strings");
var partsCount = this._expressionParts.Count;
this.Visit(expression.Object);
var part = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
partsCount = this._expressionParts.Count;
this.Visit(expression.Arguments[0]);
var part2 = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
this._expressionParts.Add(new StringFunction("hasPrefix", part, part2));
return expression;
}
else if (expression.Method.Name.Equals("EndsWith")) {
_context.QueryAggregator.AddImport("strings");
var partsCount = this._expressionParts.Count;
this.Visit(expression.Object);
var part = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
partsCount = this._expressionParts.Count;
this.Visit(expression.Arguments[0]);
var part2 = this.GetAndRemoveExpressionParts(partsCount, this._expressionParts.Count);
this._expressionParts.Add(new StringFunction("hasSuffix", part, part2));
return expression;
}
}


return base.VisitMethodCall(expression);
}

Expand Down Expand Up @@ -294,6 +351,16 @@ private void NormalizeNamedFieldValue()
NormalizeNamedFieldValue();
}

private IEnumerable<IExpressionPart> GetAndRemoveExpressionParts(int start, int end)
{
var parts = this._expressionParts.GetRange(start, end - start);
for (int i = start; i < end; i++)
{
this._expressionParts.RemoveAt(i);
}
return parts;
}

/// <summary>
/// Mark variables that are use to filter by tag by tag as tag.
/// </summary>
Expand Down

0 comments on commit 018f4bf

Please sign in to comment.