Skip to content

Commit

Permalink
feat: Add IsMeasurement option to Column attribute for dynamic measur…
Browse files Browse the repository at this point in the history
…ement names in POCO classes (#240)
  • Loading branch information
Chris Cameron committed Sep 30, 2021
1 parent 533c46f commit f7bdf71
Show file tree
Hide file tree
Showing 15 changed files with 162 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Features
1. [#239](https://github.com/influxdata/influxdb-client-csharp/pull/239): Add support for Asynchronous queries [LINQ]
1. [#240](https://github.com/influxdata/influxdb-client-csharp/pull/240): Add IsMeasurement option to Column attribute for dynamic measurement names in POCO classes

## 3.0.0 [2021-09-17]

Expand Down
6 changes: 3 additions & 3 deletions Client.Core.Test/AbstractTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ private async Task InfluxDbRequest(HttpRequestMessage request)
try
{
var response = await httpClient.SendAsync(request);
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.IsTrue(response.IsSuccessStatusCode, $"Failed to make HTTP request: {response.ReasonPhrase}");

Thread.Sleep(DefaultInfluxDBSleep);
}
catch (Exception)
catch (Exception e)
{
Assert.Fail("Unexpected exception");
Assert.Fail("Unexpected exception: " + e);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions Client.Core/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace InfluxDB.Client.Core
/// <summary>
/// The annotation is used for mapping POCO class into line protocol.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class Measurement : Attribute
{
public string Name { get; }
Expand All @@ -18,10 +19,13 @@ public Measurement(string name)
/// <summary>
/// The annotation is used to customize bidirectional mapping between POCO and Flux query result or Line Protocol.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class Column : Attribute
{
public string Name { get; }

public bool IsMeasurement { get; set; }

public bool IsTag { get; set; }

public bool IsTimestamp { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion Client.Core/Flux/Internal/AttributesCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public PropertyInfo[] GetProperties(Type type)
}

/// <summary>
/// Get Mapping attribute for specified propery.
/// Get Mapping attribute for specified property.
/// </summary>
/// <param name="property">property of DomainObject</param>
/// <returns>Property Attribute</returns>
Expand Down
4 changes: 4 additions & 0 deletions Client.Core/Flux/Internal/FluxResultMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ internal object ToPoco(FluxRecord record, Type type)
{
var attribute = _attributesCache.GetAttribute(property);

if (attribute != null && attribute.IsMeasurement)
{
SetFieldValue(poco, property, record.GetMeasurement());
}
if (attribute != null && attribute.IsTimestamp)
{
SetFieldValue(poco, property, record.GetTime());
Expand Down
12 changes: 12 additions & 0 deletions Client.Linq.Test/DomainObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ class SensorDateTimeOffset
[Column(IsTimestamp = true)] public DateTimeOffset Timestamp { get; set; }
}

class SensorWithCustomMeasurement
{
[Column(IsMeasurement = true)]
public string Measurement { get; set; }

[Column("sensor_id", IsTag = true)]
public string SensorId { get; set; }

[Column("data")]
public int Value { get; set; }
}

class SensorCustom
{
public Guid Id { get; set; }
Expand Down
25 changes: 25 additions & 0 deletions Client.Linq.Test/InfluxDBQueryVisitorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,31 @@ where month11 > s.Timestamp
}
}

[Test]
public void ResultOperatorByMeasurement()
{
var query = from s in InfluxDBQueryable<SensorWithCustomMeasurement>.Queryable("my-bucket", "my-org", _queryApi)
where s.Value > 10
where s.Measurement == "my-measurement"
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) => (r[\"_measurement\"] == p4)) " +
"|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") " +
"|> drop(columns: [\"_start\", \"_stop\", \"_measurement\"]) " +
"|> filter(fn: (r) => (r[\"data\"] > p3))";

Assert.AreEqual(expected, visitor.BuildFluxQuery());

var ast = visitor.BuildFluxAST();

var measurementAssignment = ((OptionStatement)ast.Body[3]).Assignment as VariableAssignment;
Assert.AreEqual("p4", measurementAssignment?.Id.Name);
Assert.AreEqual("my-measurement", (measurementAssignment?.Init as StringLiteral)?.Value);
}

[Test]
public void TimestampAsDateTimeOffset()
{
Expand Down
6 changes: 6 additions & 0 deletions Client.Linq/IMemberNameResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public interface IMemberNameResolver

public enum MemberType
{
Measurement,
Tag,
Field,
Timestamp,
Expand All @@ -52,6 +53,11 @@ public MemberType ResolveMemberType(MemberInfo memberInfo)

if (attribute != null)
{
if (attribute.IsMeasurement)
{
return MemberType.Measurement;
}

if (attribute.IsTag)
{
return MemberType.Tag;
Expand Down
3 changes: 3 additions & 0 deletions Client.Linq/Internal/Expressions/ColumnName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public void AppendFlux(StringBuilder builder)
{
switch (_memberResolver.ResolveMemberType(_member))
{
case MemberType.Measurement:
builder.Append("_measurement");
break;
case MemberType.Timestamp:
builder.Append("_time");
break;
Expand Down
22 changes: 22 additions & 0 deletions Client.Linq/Internal/Expressions/MeasurementColumnName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Reflection;
using System.Text;

namespace InfluxDB.Client.Linq.Internal.Expressions
{
internal class MeasurementColumnName : IExpressionPart
{
private readonly ColumnName _delegate;

internal MeasurementColumnName(MemberInfo member, IMemberNameResolver memberNameResolver)
{
_delegate = new ColumnName(member, memberNameResolver);
}

public void AppendFlux(StringBuilder builder)
{
builder.Append("r[\"");
_delegate.AppendFlux(builder);
builder.Append("\"]");
}
}
}
3 changes: 3 additions & 0 deletions Client.Linq/Internal/QueryExpressionTreeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ protected override Expression VisitMember(MemberExpression expression)
{
switch (_context.MemberResolver.ResolveMemberType(expression.Member))
{
case MemberType.Measurement:
_expressionParts.Add(new MeasurementColumnName(expression.Member, _context.MemberResolver));
break;
case MemberType.Timestamp:
_expressionParts.Add(new TimeColumnName(expression.Member, _context.MemberResolver));
break;
Expand Down
1 change: 1 addition & 0 deletions Client.Linq/Internal/QueryVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public override void VisitWhereClause(WhereClause whereClause, QueryModel queryM
break;
// Tag
case TagColumnName _:
case MeasurementColumnName _:
tagFilter.Add(expression);
break;
// Field
Expand Down
59 changes: 58 additions & 1 deletion Client.Test/MeasurementMapperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,36 @@ public void HeavyLoad()

Assert.LessOrEqual(ts.Seconds, 10, $"Elapsed time: {elapsedTime}");
}


[Test]
public void MeasurementProperty()
{
var poco = new MeasurementPropertyPoco
{
Measurement = "poco",
Tag = "tag val",
Value = 15.444,
ValueWithoutDefaultName = 20,
ValueWithEmptyName = 25d,
Timestamp = TimeSpan.FromDays(10)
};

var lineProtocol = _mapper.ToPoint(poco, WritePrecision.S).ToLineProtocol();

Assert.AreEqual("poco,tag=tag\\ val value=15.444,ValueWithEmptyName=25,ValueWithoutDefaultName=20i 864000", lineProtocol);
}

[Test]
public void MeasurementPropertyValidation()
{
var poco = new BadMeasurementAttributesPoco
{
Measurement = "poco"
};

Assert.Throws<InvalidOperationException>(() => _mapper.ToPoint(poco, WritePrecision.S));
}

private class MyClass
{
public override string ToString()
Expand All @@ -118,5 +147,33 @@ private class Poco
[Column(IsTimestamp = true)]
public Object Timestamp { get; set; }
}

private class MeasurementPropertyPoco
{
[Column(IsMeasurement = true)]
public string Measurement { get; set; }

[Column("tag", IsTag = true)]
public string Tag { get; set; }

[Column("value")]
public Object Value { get; set; }

[Column]
public int? ValueWithoutDefaultName { get; set; }

[Column("")]
public Double? ValueWithEmptyName { get; set; }

[Column(IsTimestamp = true)]
public Object Timestamp { get; set; }
}

[Measurement("poco")]
private class BadMeasurementAttributesPoco
{
[Column(IsMeasurement = true)]
public string Measurement { get; set; }
}
}
}
3 changes: 3 additions & 0 deletions Client.Test/QueryApiTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public async Task GenericAndTypeofCalls()
Assert.AreEqual(13.00, measurements[1].Value);
Assert.IsAssignableFrom<SyncPoco>(measurementsTypeof[0]);
var cast = measurementsTypeof.Cast<SyncPoco>().ToList();
Assert.AreEqual(measurements[0].Measurement, cast[0].Measurement);
Assert.AreEqual(measurements[0].Timestamp, cast[0].Timestamp);
Assert.AreEqual(12.25, cast[0].Value);
Assert.AreEqual(13.00, cast[1].Value);
Expand Down Expand Up @@ -106,6 +107,8 @@ public async Task QueryAsyncEnumerable()

private class SyncPoco
{
[Column(IsMeasurement = true)] public string Measurement { get; set; }

[Column("id", IsTag = true)] public string Tag { get; set; }

[Column("_value")] public double Value { get; set; }
Expand Down
20 changes: 16 additions & 4 deletions Client/Internal/MeasurementMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,30 @@ internal PointData ToPoint<TM>(TM measurement, WritePrecision precision)

var measurementType = measurement.GetType();
CacheMeasurementClass(measurementType);

var measurementAttribute = (Measurement) measurementType.GetCustomAttribute(typeof(Measurement));
if (measurementAttribute == null)
var measurementColumn = CACHE[measurementType.Name].SingleOrDefault(p => p.Column.IsMeasurement);

if (((measurementAttribute == null) ^ (measurementColumn == null)) == false)
{
throw new InvalidOperationException(
$"Measurement {measurement} does not have a {typeof(Measurement)} attribute.");
$"Unable to determine Measurement for {measurement}. Does it have a {typeof(Measurement)} or IsMessage {typeof(Column)} attribute?");
}

var point = PointData.Measurement(measurementAttribute.Name);
string measurementName =
measurementAttribute == null
? (string)measurementColumn.Property.GetValue(measurement)
: measurementAttribute.Name;

var point = PointData.Measurement(measurementName);

foreach (var propertyInfo in CACHE[measurementType.Name])
{
if (propertyInfo.Column.IsMeasurement)
{
continue;
}

var value = propertyInfo.Property.GetValue(measurement);
if (value == null)
{
Expand Down

0 comments on commit f7bdf71

Please sign in to comment.