using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Text;
using InfluxDB.Client.Api.Domain;
using InfluxDB.Client.Core;
using NodaTime;
namespace InfluxDB.Client.Writes
{
///
/// Point defines the values that will be written to the database.
/// See Go Implementation.
///
public partial class PointData : IEquatable
{
private static readonly DateTime EpochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private readonly string _measurementName;
private readonly ImmutableSortedDictionary _tags = ImmutableSortedDictionary
.Empty;
private readonly ImmutableSortedDictionary _fields =
ImmutableSortedDictionary.Empty;
public readonly WritePrecision Precision;
private readonly BigInteger? _time;
private PointData(string measurementName)
{
Arguments.CheckNonEmptyString(measurementName, "Measurement name");
_measurementName = measurementName;
Precision = WritePrecision.Ns;
}
///
/// Create a new Point withe specified a measurement name.
///
/// the measurement name
/// the new Point
public static PointData Measurement(string measurementName)
{
return new PointData(measurementName);
}
private PointData(string measurementName,
WritePrecision precision,
BigInteger? time,
ImmutableSortedDictionary tags,
ImmutableSortedDictionary fields)
{
_measurementName = measurementName;
Precision = precision;
_time = time;
_tags = tags;
_fields = fields;
}
///
/// Adds or replaces a tag value for a point.
///
/// the tag name
/// the tag value
/// this
public PointData Tag(string name, string value)
{
var isEmptyValue = string.IsNullOrEmpty(value);
var tags = _tags;
if (isEmptyValue)
{
if (tags.ContainsKey(name))
{
Trace.TraceWarning(
$"Empty tags will cause deletion of, tag [{name}], measurement [{_measurementName}]");
}
else
{
Trace.TraceWarning($"Empty tags has no effect, tag [{name}], measurement [{_measurementName}]");
return this;
}
}
if (tags.ContainsKey(name))
{
tags = tags.Remove(name);
}
if (!isEmptyValue)
{
tags = tags.Add(name, value);
}
return new PointData(_measurementName,
Precision,
_time,
tags,
_fields);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, byte value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, float value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, double value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, decimal value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, long value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, ulong value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, uint value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, string value)
{
return PutField(name, value);
}
///
/// Add a field with a value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, bool value)
{
return PutField(name, value);
}
///
/// Add a field with an value.
///
/// the field name
/// the field value
/// this
public PointData Field(string name, object value)
{
return PutField(name, value);
}
///
/// Updates the timestamp for the point.
///
/// the timestamp
/// the timestamp precision
///
public PointData Timestamp(long timestamp, WritePrecision timeUnit)
{
return new PointData(_measurementName,
timeUnit,
timestamp,
_tags,
_fields);
}
///
/// Updates the timestamp for the point represented by .
///
/// the timestamp
/// the timestamp precision
///
public PointData Timestamp(TimeSpan timestamp, WritePrecision timeUnit)
{
var time = TimeSpanToBigInteger(timestamp, timeUnit);
return new PointData(_measurementName,
timeUnit,
time,
_tags,
_fields);
}
///
/// Updates the timestamp for the point represented by .
///
/// the timestamp
/// the timestamp precision
///
public PointData Timestamp(DateTime timestamp, WritePrecision timeUnit)
{
var utcTimestamp = timestamp.Kind switch
{
DateTimeKind.Local => timestamp.ToUniversalTime(),
DateTimeKind.Unspecified => DateTime.SpecifyKind(timestamp, DateTimeKind.Utc),
var _ => timestamp
};
var timeSpan = utcTimestamp.Subtract(EpochStart);
return Timestamp(timeSpan, timeUnit);
}
///
/// Updates the timestamp for the point represented by .
///
/// the timestamp
/// the timestamp precision
///
public PointData Timestamp(DateTimeOffset timestamp, WritePrecision timeUnit)
{
return Timestamp(timestamp.UtcDateTime, timeUnit);
}
///
/// Updates the timestamp for the point represented by .
///
/// the timestamp
/// the timestamp precision
///
public PointData Timestamp(Instant timestamp, WritePrecision timeUnit)
{
var time = InstantToBigInteger(timestamp, timeUnit);
return new PointData(_measurementName,
timeUnit,
time,
_tags,
_fields);
}
///
/// Has point any fields?
///
/// true, if the point contains any fields, false otherwise.
public bool HasFields()
{
return _fields.Count > 0;
}
///
/// The Line Protocol
///
/// with the default values
///
public string ToLineProtocol(PointSettings pointSettings = null)
{
var sb = new StringBuilder();
EscapeKey(sb, _measurementName, false);
AppendTags(sb, pointSettings);
var appendedFields = AppendFields(sb);
if (!appendedFields)
{
return "";
}
AppendTime(sb);
return sb.ToString();
}
private PointData PutField(string name, object value)
{
Arguments.CheckNonEmptyString(name, "Field name");
var fields = _fields;
if (fields.ContainsKey(name))
{
fields = fields.Remove(name);
}
fields = fields.Add(name, value);
return new PointData(_measurementName,
Precision,
_time,
_tags,
fields);
}
private static BigInteger TimeSpanToBigInteger(TimeSpan timestamp, WritePrecision timeUnit)
{
BigInteger time;
switch (timeUnit)
{
case WritePrecision.Ns:
time = timestamp.Ticks * 100;
break;
case WritePrecision.Us:
time = (BigInteger)(timestamp.Ticks * 0.1);
break;
case WritePrecision.Ms:
time = (BigInteger)timestamp.TotalMilliseconds;
break;
case WritePrecision.S:
time = (BigInteger)timestamp.TotalSeconds;
break;
default:
throw new ArgumentOutOfRangeException(nameof(timeUnit), timeUnit,
"WritePrecision value is not supported");
}
return time;
}
private static BigInteger InstantToBigInteger(Instant timestamp, WritePrecision timeUnit)
{
BigInteger time;
switch (timeUnit)
{
case WritePrecision.S:
time = timestamp.ToUnixTimeSeconds();
break;
case WritePrecision.Ms:
time = timestamp.ToUnixTimeMilliseconds();
break;
case WritePrecision.Us:
time = (long)(timestamp.ToUnixTimeTicks() * 0.1);
break;
case WritePrecision.Ns:
time = (timestamp - NodaConstants.UnixEpoch).ToBigIntegerNanoseconds();
break;
default:
throw new ArgumentOutOfRangeException(nameof(timeUnit), timeUnit,
"WritePrecision value is not supported");
}
return time;
}
///
/// Appends the tags.
///
/// The writer.
/// The point settings.
private void AppendTags(StringBuilder writer, PointSettings pointSettings)
{
IReadOnlyDictionary entries;
if (pointSettings == null)
{
entries = _tags;
}
else
{
var defaultTags = pointSettings.GetDefaultTags();
try
{
entries = _tags.AddRange(defaultTags);
}
catch (ArgumentException)
{
// Most cases don't expect to override existing content
// override don't consider as best practice
// therefore it a trade-off between being less efficient
// on the default behavior or on the override scenario
var builder = _tags.ToBuilder();
foreach (var item in defaultTags)
{
var name = item.Key;
if (!builder.ContainsKey(name)) // existing tags overrides
{
builder.Add(name, item.Value);
}
}
entries = builder;
}
}
foreach (var keyValue in entries)
{
var key = keyValue.Key;
var value = keyValue.Value;
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
{
continue;
}
writer.Append(',');
EscapeKey(writer, key);
writer.Append('=');
EscapeKey(writer, value);
}
writer.Append(' ');
}
///
/// Appends the fields.
///
/// The sb.
///
private bool AppendFields(StringBuilder sb)
{
var appended = false;
foreach (var keyValue in _fields)
{
var key = keyValue.Key;
var value = keyValue.Value;
if (IsNotDefined(value))
{
continue;
}
EscapeKey(sb, key);
sb.Append('=');
if (value is double || value is float)
{
sb.Append(((IConvertible)value).ToString(CultureInfo.InvariantCulture));
}
else if (value is uint || value is ulong || value is ushort)
{
sb.Append(((IConvertible)value).ToString(CultureInfo.InvariantCulture));
sb.Append('u');
}
else if (value is byte || value is int || value is long || value is sbyte || value is short)
{
sb.Append(((IConvertible)value).ToString(CultureInfo.InvariantCulture));
sb.Append('i');
}
else if (value is bool b)
{
sb.Append(b ? "true" : "false");
}
else if (value is string s)
{
sb.Append('"');
EscapeValue(sb, s);
sb.Append('"');
}
else if (value is IConvertible c)
{
sb.Append(c.ToString(CultureInfo.InvariantCulture));
}
else
{
sb.Append('"');
EscapeValue(sb, value.ToString());
sb.Append('"');
}
sb.Append(',');
appended = true;
}
if (appended)
{
sb.Remove(sb.Length - 1, 1);
}
return appended;
}
///
/// Appends the time.
///
/// The sb.
private void AppendTime(StringBuilder sb)
{
if (_time == null)
{
return;
}
sb.Append(' ');
sb.Append(((BigInteger)_time).ToString(CultureInfo.InvariantCulture));
}
///
/// Escapes the key.
///
/// The sb.
/// The key.
/// Configure to escaping equal.
private void EscapeKey(StringBuilder sb, string key, bool escapeEqual = true)
{
foreach (var c in key)
{
switch (c)
{
case '\n':
sb.Append("\\n");
continue;
case '\r':
sb.Append("\\r");
continue;
case '\t':
sb.Append("\\t");
continue;
case ' ':
case ',':
sb.Append("\\");
break;
case '=':
if (escapeEqual)
{
sb.Append("\\");
}
break;
}
sb.Append(c);
}
}
///
/// Escapes the value.
///
/// The sb.
/// The value.
private void EscapeValue(StringBuilder sb, string value)
{
foreach (var c in value)
{
switch (c)
{
case '\\':
case '\"':
sb.Append("\\");
break;
}
sb.Append(c);
}
}
///
/// Determines whether [is not defined] [the specified value].
///
/// The value.
///
/// true if [is not defined] [the specified value]; otherwise, false.
///
private bool IsNotDefined(object value)
{
return value == null
|| value is double d && (double.IsInfinity(d) || double.IsNaN(d))
|| value is float f && (float.IsInfinity(f) || float.IsNaN(f));
}
///
/// Determines whether the specified , is equal to this instance.
///
/// The to compare with this instance.
///
/// true if the specified is equal to this instance; otherwise, false.
///
public override bool Equals(object obj)
{
return Equals(obj as PointData);
}
///
/// Indicates whether the current object is equal to another object of the same type.
///
/// An object to compare with this object.
///
/// true if the current object is equal to the other parameter; otherwise, false.
///
public bool Equals(PointData other)
{
if (other == null)
{
return false;
}
var otherTags = other._tags;
var result = _tags.Count == otherTags.Count &&
_tags.All(pair =>
{
var key = pair.Key;
var value = pair.Value;
return otherTags.ContainsKey(key) &&
otherTags[key] == value;
});
var otherFields = other._fields;
result = result && _fields.Count == otherFields.Count &&
_fields.All(pair =>
{
var key = pair.Key;
var value = pair.Value;
return otherFields.ContainsKey(key) &&
Equals(otherFields[key], value);
});
result = result &&
_measurementName == other._measurementName &&
Precision == other.Precision &&
EqualityComparer.Default.Equals(_time, other._time);
return result;
}
///
/// Returns a hash code for this instance.
///
///
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
///
public override int GetHashCode()
{
var hashCode = 318335609;
hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_measurementName);
hashCode = hashCode * -1521134295 + Precision.GetHashCode();
hashCode = hashCode * -1521134295 + _time.GetHashCode();
foreach (var pair in _tags)
{
hashCode = hashCode * -1521134295 + pair.Key?.GetHashCode() ?? 0;
hashCode = hashCode * -1521134295 + pair.Value?.GetHashCode() ?? 0;
}
foreach (var pair in _fields)
{
hashCode = hashCode * -1521134295 + pair.Key?.GetHashCode() ?? 0;
hashCode = hashCode * -1521134295 + pair.Value?.GetHashCode() ?? 0;
}
return hashCode;
}
///
/// Implements the operator ==.
///
/// The left.
/// The right.
///
/// The result of the operator.
///
public static bool operator ==(PointData left, PointData right)
{
return EqualityComparer.Default.Equals(left, right);
}
///
/// Implements the operator !=.
///
/// The left.
/// The right.
///
/// The result of the operator.
///
public static bool operator !=(PointData left, PointData right)
{
return !(left == right);
}
}
}