Skip to content

Commit

Permalink
feat: support some linq string functions in where conditions, fixes i…
Browse files Browse the repository at this point in the history
  • Loading branch information
jogibear9988 committed Jul 17, 2023
1 parent 44574e0 commit fb8aeff
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## 4.13.0 [unreleased]

### Features
1. [#528](https://github.com/influxdata/influxdb-client-csharp/pull/528): Add HttpClient as a part of InfluxDBClientOptions
1. [#528](https://github.com/influxdata/influxdb-client-csharp/pull/528): Add HttpClient as a part of InfluxDBClientOptions
1. [#500](https://github.com/influxdata/influxdb-client-csharp/pull/500): Support String functions in Linq

### Dependencies
Update dependencies:
Expand Down
42 changes: 42 additions & 0 deletions Client.Linq.Test/InfluxDBQueryVisitorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,48 @@ 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 ToStringFunctionQuery()
{
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", _queryApi)
where s.Value.ToString() == "3"
select s;
var visitor = BuildQueryVisitor(query);

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

[Test]
public void ReplaceAllFunctionQuery()
{
var query = from s in InfluxDBQueryable<Sensor>.Queryable("my-bucket", "my-org", _queryApi)
where s.SensorId.ToLower().Replace("a", "b") == "b"
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.replaceAll(v: strings.toLower(v: r[\"sensor_id\"]), t: p3, u: p4) == p5)) |> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") |> drop(columns: [\"_start\", \"_stop\", \"_measurement\"]) |> filter(fn: (r) => (strings.replaceAll(v: strings.toLower(v: r[\"sensor_id\"]), t: p3, u: p4) == p5))";
var qry = visitor.BuildFluxQuery();
Assert.AreEqual(expected, visitor.BuildFluxQuery());
}

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

namespace InfluxDB.Client.Linq.Internal.Expressions.String
{
internal class ContainsStr : IExpressionPart
{
private readonly IEnumerable<IExpressionPart> _value;
private readonly IEnumerable<IExpressionPart> _substr;

internal ContainsStr(IEnumerable<IExpressionPart> value, IEnumerable<IExpressionPart> substr)
{
this._value = value;
this._substr = substr;
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("strings.containsStr(v: ");
foreach (var expressionPart in _value) expressionPart.AppendFlux(builder);
builder.Append(", substr: ");
foreach (var expressionPart in _substr) expressionPart.AppendFlux(builder);
builder.Append(")");
}
}
}
26 changes: 26 additions & 0 deletions Client.Linq/Internal/Expressions/String/HasPrefix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions.String
{
internal class HasPrefix : IExpressionPart
{
private readonly IEnumerable<IExpressionPart> _value;
private readonly IEnumerable<IExpressionPart> _prefix;

internal HasPrefix(IEnumerable<IExpressionPart> value, IEnumerable<IExpressionPart> prefix)
{
this._value = value;
this._prefix = prefix;
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("strings.hasPrefix(v: ");
foreach (var expressionPart in _value) expressionPart.AppendFlux(builder);
builder.Append(", prefix: ");
foreach (var expressionPart in _prefix) expressionPart.AppendFlux(builder);
builder.Append(")");
}
}
}
26 changes: 26 additions & 0 deletions Client.Linq/Internal/Expressions/String/HasSuffix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions.String
{
internal class HasSuffix : IExpressionPart
{
private readonly IEnumerable<IExpressionPart> _value;
private readonly IEnumerable<IExpressionPart> _suffix;

internal HasSuffix(IEnumerable<IExpressionPart> value, IEnumerable<IExpressionPart> suffix)
{
this._value = value;
this._suffix = suffix;
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("strings.hasSuffix(v: ");
foreach (var expressionPart in _value) expressionPart.AppendFlux(builder);
builder.Append(", suffix: ");
foreach (var expressionPart in _suffix) expressionPart.AppendFlux(builder);
builder.Append(")");
}
}
}
31 changes: 31 additions & 0 deletions Client.Linq/Internal/Expressions/String/ReplaceAll.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions.String
{
internal class ReplaceAll : IExpressionPart
{
private readonly IEnumerable<IExpressionPart> _value;
private readonly IEnumerable<IExpressionPart> _substring;
private readonly IEnumerable<IExpressionPart> _replacement;

internal ReplaceAll(IEnumerable<IExpressionPart> value, IEnumerable<IExpressionPart> substring,
IEnumerable<IExpressionPart> replacement)
{
this._value = value;
this._substring = substring;
this._replacement = replacement;
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("strings.replaceAll(v: ");
foreach (var expressionPart in _value) expressionPart.AppendFlux(builder);
builder.Append(", t: ");
foreach (var expressionPart in _substring) expressionPart.AppendFlux(builder);
builder.Append(", u: ");
foreach (var expressionPart in _replacement) expressionPart.AppendFlux(builder);
builder.Append(")");
}
}
}
22 changes: 22 additions & 0 deletions Client.Linq/Internal/Expressions/String/ToLower.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions.String
{
internal class ToLower : IExpressionPart
{
private readonly IEnumerable<IExpressionPart> _value;

internal ToLower(IEnumerable<IExpressionPart> value)
{
this._value = value;
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("strings.toLower(v: ");
foreach (var expressionPart in _value) expressionPart.AppendFlux(builder);
builder.Append(")");
}
}
}
22 changes: 22 additions & 0 deletions Client.Linq/Internal/Expressions/String/ToString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions.String
{
internal class ToString : IExpressionPart
{
private readonly IEnumerable<IExpressionPart> _value;

internal ToString(IEnumerable<IExpressionPart> value)
{
this._value = value;
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("string(v: ");
foreach (var expressionPart in _value) expressionPart.AppendFlux(builder);
builder.Append(")");
}
}
}
22 changes: 22 additions & 0 deletions Client.Linq/Internal/Expressions/String/ToUpper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions.String
{
internal class ToUpper : IExpressionPart
{
private readonly IEnumerable<IExpressionPart> _value;

internal ToUpper(IEnumerable<IExpressionPart> value)
{
this._value = value;
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("strings.toUpper(v: ");
foreach (var expressionPart in _value) expressionPart.AppendFlux(builder);
builder.Append(")");
}
}
}
22 changes: 22 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 @@ -83,6 +84,16 @@ 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,16 @@ 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
64 changes: 64 additions & 0 deletions Client.Linq/Internal/QueryExpressionTreeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using InfluxDB.Client.Linq.Internal.Expressions;
using InfluxDB.Client.Linq.Internal.Expressions.String;
using Remotion.Linq.Clauses;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Clauses.ResultOperators;
Expand Down Expand Up @@ -187,6 +188,62 @@ protected override Expression VisitMethodCall(MethodCallExpression expression)
return expression;
}

if (expression.Method.DeclaringType == typeof(string))
{
_context.QueryAggregator.AddImport("strings");
var partsCount = _expressionParts.Count;
Visit(expression.Object);
var part = GetAndRemoveExpressionParts(partsCount, _expressionParts.Count);
IEnumerable<IExpressionPart> part2 = null;
IEnumerable<IExpressionPart> part3 = null;
if (expression.Arguments.Count > 0)
{
partsCount = _expressionParts.Count;
Visit(expression.Arguments[0]);
part2 = GetAndRemoveExpressionParts(partsCount, _expressionParts.Count);
}

if (expression.Arguments.Count > 1)
{
partsCount = _expressionParts.Count;
Visit(expression.Arguments[1]);
part3 = GetAndRemoveExpressionParts(partsCount, _expressionParts.Count);
}

switch (expression.Method.Name)
{
case "ToLower":
_expressionParts.Add(new ToLower(part));
return expression;
case "ToUpper":
_expressionParts.Add(new ToUpper(part));
return expression;
case "Contains":
_expressionParts.Add(new ContainsStr(part, part2));
return expression;
case "StartsWith":
_expressionParts.Add(new HasPrefix(part, part2));
return expression;
case "EndsWith":
_expressionParts.Add(new HasSuffix(part, part2));
return expression;
case "Replace":
_expressionParts.Add(new ReplaceAll(part, part2, part3));
return expression;
}

throw new NotSupportedException(expression.Method.Name + " of String class is not yet supported.");
}

if (expression.Method.Name.Equals("ToString"))
{
var partsCount = _expressionParts.Count;
Visit(expression.Object);
var part = GetAndRemoveExpressionParts(partsCount, _expressionParts.Count);
_expressionParts.Add(new ToString(part));
return expression;
}

return base.VisitMethodCall(expression);
}

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

private IEnumerable<IExpressionPart> GetAndRemoveExpressionParts(int start, int end)
{
var parts = _expressionParts.GetRange(start, end - start);
for (var i = start; i < end; i++) _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 fb8aeff

Please sign in to comment.