Skip to content

Commit

Permalink
bumped version, improved aggregate support
Browse files Browse the repository at this point in the history
  • Loading branch information
MikaelGRA committed Jun 22, 2017
1 parent acc4a0f commit 8339ede
Show file tree
Hide file tree
Showing 12 changed files with 509 additions and 439 deletions.
59 changes: 29 additions & 30 deletions src/Vibrant.InfluxDB.Client/InfluxClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,25 @@ public InfluxClient( Uri endpoint )
/// Executes an arbitrary command that returns a table as a result.
/// </summary>
/// <typeparam name="TInfluxRow"></typeparam>
/// <param name="commandOrQuery"></param>
/// <param name="command"></param>
/// <param name="db"></param>
/// <returns></returns>
public Task<InfluxResultSet<TInfluxRow>> ExecuteOperationAsync<TInfluxRow>( string commandOrQuery, string db )
public Task<InfluxResultSet<TInfluxRow>> ExecuteOperationAsync<TInfluxRow>( string command, string db )
where TInfluxRow : new()
{
return ExecuteQueryInternalAsync<TInfluxRow>( commandOrQuery, db );
return ExecuteQueryInternalAsync<TInfluxRow>( command, db );
}

/// <summary>
/// Executes an arbitrary command or query that returns a table as a result.
/// </summary>
/// <typeparam name="TInfluxRow"></typeparam>
/// <param name="commandOrQuery"></param>
/// <param name="command"></param>
/// <returns></returns>
public Task<InfluxResultSet<TInfluxRow>> ExecuteOperationAsync<TInfluxRow>( string commandOrQuery )
public Task<InfluxResultSet<TInfluxRow>> ExecuteOperationAsync<TInfluxRow>( string command )
where TInfluxRow : new()
{
return ExecuteQueryInternalAsync<TInfluxRow>( commandOrQuery );
return ExecuteQueryInternalAsync<TInfluxRow>( command );
}

/// <summary>
Expand Down Expand Up @@ -798,38 +798,37 @@ public Task DeleteRangeAsync( string db, string measurementName, DateTime from,

#endregion

internal async Task<DatabaseMeasurementInfo> GetMetaInformationAsync( string db, string measurementName, bool forceRefresh )
internal async Task<DatabaseMeasurementInfo> GetMetaInformationAsync( string db, string measurementName, TimeSpan? expiration )
{
var key = new DatabaseMeasurementInfoKey( db, measurementName );
DatabaseMeasurementInfo info;

if( !forceRefresh )
lock( _seriesMetaCache )
{
lock( _seriesMetaCache )
{
if( _seriesMetaCache.TryGetValue( key, out info ) )
{
return info;
}
}
_seriesMetaCache.TryGetValue( key, out info );
}

// get metadata information from the store
var fieldTask = ShowFieldKeysAsync( db, measurementName );
var tagTask = ShowTagKeysAsync( db, measurementName );
await Task.WhenAll( fieldTask, tagTask ).ConfigureAwait( false );

var fields = fieldTask.Result.Series.FirstOrDefault()?.Rows;
var tags = tagTask.Result.Series.FirstOrDefault()?.Rows;

info = new DatabaseMeasurementInfo();
if( fields != null )
var now = DateTime.UtcNow;
if( info != null )
{
foreach( var row in fields )
if( !expiration.HasValue ) // info never expires
{
return info;
}

if( now - info.Timestamp < expiration.Value ) // has not expired
{
info.Fields.Add( row.FieldKey );
return info;
}
}

// has expired or never existed, lets retrieve it

// get metadata information from the store
var tagsResult = await ShowTagKeysAsync( db, measurementName );
var tags = tagsResult.Series.FirstOrDefault()?.Rows;

info = new DatabaseMeasurementInfo( now );
if( tags != null )
{
foreach( var row in tags )
Expand Down Expand Up @@ -895,21 +894,21 @@ private async Task<InfluxResultSet<TInfluxRow>> ExecuteQueryInternalAsync<TInflu
where TInfluxRow : new()
{
var queryResult = await GetInternalAsync( CreateQueryUrl( query, db, options ), true ).ConfigureAwait( false );
return await ResultSetFactory.CreateAsync<TInfluxRow>( this, queryResult, db, options.Precision, false ).ConfigureAwait( false );
return await ResultSetFactory.CreateAsync<TInfluxRow>( this, queryResult, db, options.Precision, true, options.MetadataExpiration ).ConfigureAwait( false );
}

private async Task<InfluxResultSet<TInfluxRow>> ExecuteQueryInternalAsync<TInfluxRow>( string query, string db )
where TInfluxRow : new()
{
var queryResult = await GetInternalAsync( CreateQueryUrl( query, db ), false ).ConfigureAwait( false );
return await ResultSetFactory.CreateAsync<TInfluxRow>( this, queryResult, db, null, true ).ConfigureAwait( false );
return await ResultSetFactory.CreateAsync<TInfluxRow>( this, queryResult, db, null, false, null ).ConfigureAwait( false );
}

private async Task<InfluxResultSet<TInfluxRow>> ExecuteQueryInternalAsync<TInfluxRow>( string query )
where TInfluxRow : new()
{
var queryResult = await GetInternalAsync( CreateQueryUrl( query ), false ).ConfigureAwait( false );
return await ResultSetFactory.CreateAsync<TInfluxRow>( this, queryResult, null, null, false ).ConfigureAwait( false );
return await ResultSetFactory.CreateAsync<TInfluxRow>( this, queryResult, null, null, false, null ).ConfigureAwait( false );
}

private async Task<InfluxResultSet> ExecuteQueryInternalAsync( string query, string db )
Expand Down
33 changes: 33 additions & 0 deletions src/Vibrant.InfluxDB.Client/InfluxComputedAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Vibrant.InfluxDB.Client
{
/// <summary>
/// Attribute to be placed on properties that are considered fields by InfluxDB.
/// </summary>
[AttributeUsage( AttributeTargets.Property, Inherited = false, AllowMultiple = false )]
public sealed class InfluxComputedAttribute : InfluxAttribute
{
private readonly string _name;

/// <summary>
/// Constructs an InfluxFieldAttribute with the given name.
/// </summary>
/// <param name="name"></param>
public InfluxComputedAttribute( string name )
{
_name = name;
}

/// <summary>
/// Gets the name of the field used by InfluxDB.
/// </summary>
public string Name
{
get { return _name; }
}
}
}
14 changes: 14 additions & 0 deletions src/Vibrant.InfluxDB.Client/InfluxQueryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public InfluxQueryOptions()
{
Precision = null;
ChunkSize = null;
MetadataExpiration = TimeSpan.FromHours( 1 );
}

/// <summary>
Expand All @@ -30,5 +31,18 @@ public InfluxQueryOptions()
/// it uses the InfluxDB default of 10000.
/// </summary>
public int? ChunkSize { get; set; }

/// <summary>
/// Gets or sets how long before retrieved metadata about measurements
/// takes to expire and must be retrieved again.
///
/// This is only used when querying data based on the IInfluxRow interface
/// because this interface. This is because the interface has no way to
/// know which retrieved columns are fields or tags. It therefore makes an
/// implicit query to get this information from the database.
///
/// A value of null means it never expires. Default is 1 hour.
/// </summary>
public TimeSpan? MetadataExpiration { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public DatabaseMeasurementInfoKey( string db, string measurementName )

internal class DatabaseMeasurementInfo
{
internal readonly DateTime Timestamp;
internal readonly HashSet<string> Tags;
internal readonly HashSet<string> Fields;

public DatabaseMeasurementInfo()
public DatabaseMeasurementInfo( DateTime timestamp )
{
Timestamp = timestamp;
Tags = new HashSet<string>();
Fields = new HashSet<string>();
}
}
}
3 changes: 3 additions & 0 deletions src/Vibrant.InfluxDB.Client/Metadata/InfluxRowTypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ internal class InfluxRowTypeInfo<TInfluxRow>
internal readonly PropertyExpressionInfo<TInfluxRow> Timestamp;
internal readonly IReadOnlyList<PropertyExpressionInfo<TInfluxRow>> Tags;
internal readonly IReadOnlyList<PropertyExpressionInfo<TInfluxRow>> Fields;
internal readonly IReadOnlyList<PropertyExpressionInfo<TInfluxRow>> Computed;
internal readonly IReadOnlyDictionary<string, PropertyExpressionInfo<TInfluxRow>> All;
internal readonly IReadOnlyDictionary<string, PropertyExpressionInfo<TInfluxRow>> PropertiesByClrName;

internal InfluxRowTypeInfo(
PropertyExpressionInfo<TInfluxRow> timestamp,
List<PropertyExpressionInfo<TInfluxRow>> tags,
List<PropertyExpressionInfo<TInfluxRow>> fields,
List<PropertyExpressionInfo<TInfluxRow>> computed,
List<PropertyExpressionInfo<TInfluxRow>> all )
{
Timestamp = timestamp;
Tags = new List<PropertyExpressionInfo<TInfluxRow>>( tags.OrderBy( x => x.Key, StringComparer.Ordinal ) );
Fields = new List<PropertyExpressionInfo<TInfluxRow>>( fields.OrderBy( x => x.Key, StringComparer.Ordinal ) );
Computed = new List<PropertyExpressionInfo<TInfluxRow>>( computed.OrderBy( x => x.Key, StringComparer.Ordinal ) );
All = new ReadOnlyDictionary<string, PropertyExpressionInfo<TInfluxRow>>( all.ToDictionary( x => x.Key, x => x ) );
PropertiesByClrName = All.ToDictionary( x => x.Value.Property.Name, x => x.Value );

Expand Down
20 changes: 19 additions & 1 deletion src/Vibrant.InfluxDB.Client/Metadata/MetadataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal static InfluxRowTypeInfo<TInfluxRow> GetOrCreate<TInfluxRow>()

if ( !_typeCache.TryGetValue( type, out cache ) )
{
var computed = new List<PropertyExpressionInfo<TInfluxRow>>();
var tags = new List<PropertyExpressionInfo<TInfluxRow>>();
var fields = new List<PropertyExpressionInfo<TInfluxRow>>();
var all = new List<PropertyExpressionInfo<TInfluxRow>>();
Expand All @@ -31,6 +32,7 @@ internal static InfluxRowTypeInfo<TInfluxRow> GetOrCreate<TInfluxRow>()
{
var fieldAttribute = propertyInfo.GetCustomAttribute<InfluxFieldAttribute>();
var tagAttribute = propertyInfo.GetCustomAttribute<InfluxTagAttribute>();
var computedAttribute = propertyInfo.GetCustomAttribute<InfluxComputedAttribute>();
var timestampAttribute = propertyInfo.GetCustomAttribute<InfluxTimestampAttribute>();

// list all attributes so we can ensure the attributes specified on a property are valid
Expand Down Expand Up @@ -85,9 +87,25 @@ internal static InfluxRowTypeInfo<TInfluxRow> GetOrCreate<TInfluxRow>()
tags.Add( expression );
all.Add( expression );
}
else if( computedAttribute != null )
{
var expression = new PropertyExpressionInfo<TInfluxRow>( computedAttribute.Name, propertyInfo );
if( !_validFieldTypes.Contains( expression.Type ) && !expression.Type.GetTypeInfo().IsEnum )
{
throw new InfluxException( string.Format( Errors.InvalidComputedType, propertyInfo.Name, type.Name ) );
}

if( string.IsNullOrEmpty( computedAttribute.Name ) )
{
throw new InfluxException( string.Format( Errors.InvalidNameProperty, propertyInfo.Name, type.Name ) );
}

computed.Add( expression );
all.Add( expression );
}
}

cache = new InfluxRowTypeInfo<TInfluxRow>( timestamp, tags, fields, all );
cache = new InfluxRowTypeInfo<TInfluxRow>( timestamp, tags, fields, computed, all );

_typeCache.Add( typeof( TInfluxRow ), cache );
}
Expand Down
50 changes: 11 additions & 39 deletions src/Vibrant.InfluxDB.Client/Parsers/ResultSetFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,6 @@ internal static bool IsIInfluxRow<TInfluxRow>()
return typeof( IInfluxRow ).IsAssignableFrom( typeof( TInfluxRow ) );
}

private static bool HasAllColumns( DatabaseMeasurementInfo meta, List<string> columns )
{
foreach( var fieldOrTag in columns )
{
if( fieldOrTag != InfluxConstants.KeyColumn && fieldOrTag != InfluxConstants.TimeColumn && !meta.Tags.Contains( fieldOrTag ) && !meta.Fields.Contains( fieldOrTag ) )
{
return false;
}
}
return true;
}

internal static InfluxResultSet Create( QueryResult queryResult )
{
List<InfluxResult> results = new List<InfluxResult>();
Expand Down Expand Up @@ -70,12 +58,13 @@ internal static Task<InfluxResultSet<TInfluxRow>> CreateAsync<TInfluxRow>(
IEnumerable<QueryResult> queryResult,
string db,
TimestampPrecision? precision,
bool isExclusivelyFields )
bool allowMetadataQuerying,
TimeSpan? metadataExpiration )
where TInfluxRow : new()
{
if( IsIInfluxRow<TInfluxRow>() )
{
return CreateBasedOnInterfaceAsync<TInfluxRow>( client, queryResult, db, precision, isExclusivelyFields );
return CreateBasedOnInterfaceAsync<TInfluxRow>( client, queryResult, db, precision, allowMetadataQuerying, metadataExpiration );
}
else
{
Expand Down Expand Up @@ -246,7 +235,8 @@ private async static Task<InfluxResultSet<TInfluxRow>> CreateBasedOnInterfaceAsy
IEnumerable<QueryResult> queryResults,
string db,
TimestampPrecision? precision,
bool isExclusivelyFields )
bool allowMetadataQuerying,
TimeSpan? metadataExpiration )
where TInfluxRow : new()
{
// In this case, we will contruct objects based on the IInfluxRow interface
Expand Down Expand Up @@ -289,33 +279,19 @@ private async static Task<InfluxResultSet<TInfluxRow>> CreateBasedOnInterfaceAsy
{
// Get metadata information about the measurement we are querying, as we dont know
// which columns are tags/fields otherwise

// PROBLEM: Should NOT always be called!
DatabaseMeasurementInfo meta = null;
if( !isExclusivelyFields )
if( allowMetadataQuerying )
{
// get the required metadata
meta = await client.GetMetaInformationAsync( db, name, false ).ConfigureAwait( false );

// check that we have all columns, otherwise call method again
bool hasAllColumnsAndTags = HasAllColumns( meta, columns );
if( !hasAllColumnsAndTags )
{
// if we dont have all columns, attempt to query the metadata again (might have changed since last query)
meta = await client.GetMetaInformationAsync( db, name, false ).ConfigureAwait( false );
hasAllColumnsAndTags = HasAllColumns( meta, columns );

// if we still dont have all columns, we cant do anything, throw exception
if( !hasAllColumnsAndTags )
{
throw new InfluxException( Errors.IndeterminateColumns );
}
}
meta = await client.GetMetaInformationAsync( db, name, metadataExpiration ).ConfigureAwait( false ); ;
}

for( int i = 0 ; i < columns.Count ; i++ )
{
var columnName = columns[ i ];

if( isExclusivelyFields )
if( !allowMetadataQuerying )
{
setters[ i ] = ( row, fieldName, value ) => row.SetField( fieldName, value );
}
Expand All @@ -337,13 +313,9 @@ private async static Task<InfluxResultSet<TInfluxRow>> CreateBasedOnInterfaceAsy
{
setters[ i ] = ( row, tagName, value ) => row.SetTag( tagName, (string)value );
}
else if( meta.Fields.Contains( columnName ) )
{
setters[ i ] = ( row, fieldName, value ) => row.SetField( fieldName, value );
}
else
{
throw new InfluxException( string.Format( Errors.InvalidColumn, columnName ) );
setters[ i ] = ( row, fieldName, value ) => row.SetField( fieldName, value );
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Vibrant.InfluxDB.Client/Resources/Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ internal static class Errors
internal static readonly string CountNotConvertEnumToString = "Could not convert the incominng value {0} to the enum on the property {1} on the type {2}.";
internal static readonly string IndeterminateColumns = "Could not determine which columns in the returned data are tags and which are fields.";
internal static readonly string InvalidFieldType = "The property {0} on the type {1} which is used as an InfluxField must be one of the following types: string, double, long, bool, DateTime, Nullable<double>, Nullable<long>, Nullable<bool>, Nullable<DateTime> or a user-defined enum.";
internal static readonly string InvalidNameProperty = "The property {0} on the type {1} must specify a non-empty name for either an InfluxField or InfluxTag.";
internal static readonly string InvalidComputedType = "The property {0} on the type {1} which is used as an InfluxComputed must be one of the following types: string, double, long, bool, DateTime, Nullable<double>, Nullable<long>, Nullable<bool>, Nullable<DateTime> or a user-defined enum.";
internal static readonly string InvalidNameProperty = "The property {0} on the type {1} must specify a non-empty name for either an InfluxField, InfluxTag or InfluxComputed.";
internal static readonly string InvalidTagType = "The property {0} on the type {1} which is used as an InfluxTag must be either a string or a user-defined enum.";
internal static readonly string InvalidTimestampType = "The property {0} on the type {1} which is used as the InfluxTimestamp must be either a DateTime or a Nullable<DateTime>.";
internal static readonly string MultipleAttributesOnSingleProperty = "The property {0} on the type {1} has multiple InfluxAttributes. This is not allowed. Please specify only InfluxTimestamp, InfluxTag or InfluxField.";
Expand Down
3 changes: 1 addition & 2 deletions src/Vibrant.InfluxDB.Client/Vibrant.InfluxDB.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<Description>An easy-to-use client for InfluxDB that supports simple query to object mapping.</Description>
<Copyright>Copyright (c) 2015-2016 MikaelGRA</Copyright>
<AssemblyTitle>InfluxDB Client for .NET</AssemblyTitle>
<VersionPrefix>3.0.3</VersionPrefix>
<Authors>MikaelGRA</Authors>
<TargetFrameworks>net45;netstandard1.3</TargetFrameworks>
<AssemblyName>Vibrant.InfluxDB.Client</AssemblyName>
Expand All @@ -21,7 +20,7 @@
<GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Version>3.0.4</Version>
<Version>3.1.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading

0 comments on commit 8339ede

Please sign in to comment.