From 51594ad4b6901238fc2edce5c3801233bf30cd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Bedn=C3=A1=C5=99?= Date: Thu, 21 Sep 2023 13:10:46 +0200 Subject: [PATCH] Feat query structured (#51) * feat: QueryPoints base * feat: guess types on no metadata * docs: PointData method comments * refactor: check if measurement before metadata * style: comment, lint * feat: PointDataValues, Mutable PointData * fix: renamed methods * feat: PointDataValues QueryPoints * feat: use only reference types in PointData * feat: fromValues * refactor: removed unused methods, added comments * fix: code style * fix: code style * fix: lang version * docs: use 7.0 dotnet for codeQL * Revert "fix: lang version" This reverts commit 4d126c4f36c3e3eeb96f645df3eab12efeb5ad4a. * docs: use 7.0 dotnet for codeQL --------- Co-authored-by: Sciator <39964450+Sciator@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 + .gitignore | 2 + Client.Test.Integration/QueryWriteTest.cs | 4 +- Client.Test/InfluxDBClientQueryTest.cs | 2 +- Client.Test/InfluxDBClientWriteTest.cs | 8 +- Client.Test/Write/PointDataBuilderTest.cs | 234 -------- Client.Test/Write/PointDataTest.cs | 254 ++++----- Client/InfluxDBClient.cs | 73 +++ Client/Internal/AssemblyHelper.cs | 1 - Client/Internal/FlightSqlExtensions.cs | 3 +- Client/Write/PointData.Builder.cs | 255 --------- Client/Write/PointData.cs | 506 +++++++++-------- Client/Write/PointDataValues.cs | 626 ++++++++++++++++++++++ Examples/IOx/IOxExample.cs | 48 +- README.md | 4 +- 15 files changed, 1165 insertions(+), 861 deletions(-) delete mode 100644 Client.Test/Write/PointDataBuilderTest.cs delete mode 100644 Client/Write/PointData.Builder.cs create mode 100644 Client/Write/PointDataValues.cs diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7c83563..506a9d0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -18,6 +18,12 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 7.0.x - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.gitignore b/.gitignore index f7e6122..963596f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /.idea/ +/.vscode/ +/.vs/ # Build results [Dd]ebug/ diff --git a/Client.Test.Integration/QueryWriteTest.cs b/Client.Test.Integration/QueryWriteTest.cs index 045e462..1f39b19 100644 --- a/Client.Test.Integration/QueryWriteTest.cs +++ b/Client.Test.Integration/QueryWriteTest.cs @@ -98,7 +98,7 @@ public async Task WriteDontFailForEmptyData() Token = _token }); - await client.WritePointAsync(PointData.Measurement("cpu").AddTag("tag", "c")); + await client.WritePointAsync(PointData.Measurement("cpu").SetTag("tag", "c")); } [Test] @@ -112,7 +112,7 @@ public async Task CanDisableCertificateValidation() DisableServerCertificateValidation = true }); - await client.WritePointAsync(PointData.Measurement("cpu").AddTag("tag", "c")); + await client.WritePointAsync(PointData.Measurement("cpu").SetTag("tag", "c")); } diff --git a/Client.Test/InfluxDBClientQueryTest.cs b/Client.Test/InfluxDBClientQueryTest.cs index 0f34d52..eabcf6c 100644 --- a/Client.Test/InfluxDBClientQueryTest.cs +++ b/Client.Test/InfluxDBClientQueryTest.cs @@ -25,7 +25,7 @@ public void AlreadyDisposed() }); Assert.That(ae, Is.Not.Null); - Assert.That(ae.Message, Is.EqualTo("Cannot access a disposed object.\nObject name: 'InfluxDBClient'.")); + Assert.That(ae.Message, Is.EqualTo($"Cannot access a disposed object.{Environment.NewLine}Object name: 'InfluxDBClient'.")); } [Test] diff --git a/Client.Test/InfluxDBClientWriteTest.cs b/Client.Test/InfluxDBClientWriteTest.cs index 0c3ebc3..e86627a 100644 --- a/Client.Test/InfluxDBClientWriteTest.cs +++ b/Client.Test/InfluxDBClientWriteTest.cs @@ -55,7 +55,7 @@ public async Task BodyPoint() _client = new InfluxDBClient(MockServerUrl, organization: "org", database: "database"); - await _client.WritePointAsync(PointData.Measurement("cpu").AddTag("tag", "c").AddField("field", 1)); + await _client.WritePointAsync(PointData.Measurement("cpu").SetTag("tag", "c").SetField("field", 1)); var requests = MockServer.LogEntries.ToList(); Assert.That(requests[0].RequestMessage.BodyData?.BodyAsString, Is.EqualTo("cpu,tag=c field=1i")); @@ -124,7 +124,7 @@ public void AlreadyDisposed() }); Assert.That(ae, Is.Not.Null); - Assert.That(ae.Message, Is.EqualTo("Cannot access a disposed object.\nObject name: 'InfluxDBClient'.")); + Assert.That(ae.Message, Is.EqualTo($"Cannot access a disposed object.{Environment.NewLine}Object name: 'InfluxDBClient'.")); } [Test] @@ -230,8 +230,8 @@ public async Task PrecisionBody() .RespondWith(Response.Create().WithStatusCode(204)); var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(123_000_000_000L); await _client.WritePointAsync(point, precision: WritePrecision.S); diff --git a/Client.Test/Write/PointDataBuilderTest.cs b/Client.Test/Write/PointDataBuilderTest.cs deleted file mode 100644 index ca7c267..0000000 --- a/Client.Test/Write/PointDataBuilderTest.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using InfluxDB3.Client.Write; - -namespace InfluxDB3.Client.Test.Write -{ - [TestFixture] - public class PointDataBuilderTest - { - [Test] - public void BuilderValuesToPoint() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("log", "some_log") - .AddField("level", 2); - - var point = builder.ToPointData(); - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe,log=some_log level=2i")); - } - - [Test] - public void TagEmptyRemovesTagValue() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("log", "to_delete") - .AddTag("log", "") - .AddField("level", 2); - - var point = builder.ToPointData(); - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); - } - - [Test] - public void ReplaceTagValue() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("log", "old_log") - .AddTag("log", "new_log") - .AddField("level", 2); - - var point = builder.ToPointData(); - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe,log=new_log level=2i")); - } - - [Test] - public void ReplaceTagValueInNewPoint() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("log", "old_log") - .AddField("level", 2); - - var point = builder.ToPointData(); - - builder.AddTag("log", "new_log"); - - var anotherPoint = builder.ToPointData(); - Assert.Multiple(() => - { - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe,log=old_log level=2i")); - Assert.That(anotherPoint.ToLineProtocol(), Is.EqualTo("h2o,location=europe,log=new_log level=2i")); - }); - } - - [Test] - public void TagEmptyKey() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("", "warn") - .AddField("level", 2); - - var point = builder.ToPointData(); - - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); - } - - [Test] - public void TagEmptyValue() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("log", "") - .AddField("level", 2); - - var point = builder.ToPointData(); - - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); - } - - [Test] - public void ReplaceFieldValue() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe2") - .AddField("level", 2) - .AddField("level", 3); - - var point = builder.ToPointData(); - - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe2 level=3i")); - } - - [Test] - public void MultipleFields() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe2") - .AddField("levelA", 2) - .AddField("levelB", 3); - - var point = builder.ToPointData(); - - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe2 levelA=2i,levelB=3i")); - } - - [Test] - public void FieldNullValue() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) - .AddField("warning", null!); - - var point = builder.ToPointData(); - - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); - } - - [Test] - public void Time() - { - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) - .SetTimestamp(123L); - - var point = builder.ToPointData(); - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 123")); - - var dateTime = new DateTime(2015, 10, 15, 8, 20, 15, DateTimeKind.Utc); - builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) - .SetTimestamp(dateTime); - - point = builder.ToPointData(); - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 1444897215000000000")); - - builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) - .SetTimestamp(TimeSpan.FromDays(1)); - - point = builder.ToPointData(); - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 86400000000000")); - - var offset = DateTimeOffset.FromUnixTimeSeconds(15678); - builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) - .SetTimestamp(offset); - - point = builder.ToPointData(); - Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 15678000000000")); - } - - [Test] - public void DateTimeMustBeUtc() - { - var dateTime = new DateTime(2015, 10, 15, 8, 20, 15); - - var builder = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2); - - Assert.Throws(() => builder.SetTimestamp(dateTime)); - } - - [Test] - public void HasFields() - { - Assert.Multiple(() => - { - Assert.That(PointData.Builder.Measurement("h2o").HasFields(), Is.False); - Assert.That(PointData.Builder.Measurement("h2o").AddTag("location", "europe").HasFields(), Is.False); - Assert.That(PointData.Builder.Measurement("h2o").AddField("level", "2").HasFields(), Is.True); - Assert.That( - PointData.Builder.Measurement("h2o").AddTag("location", "europe").AddField("level", "2").HasFields(), - Is.True); - }); - } - - [Test] - public void FieldTypes() - { - var point = PointData.Builder.Measurement("h2o").AddTag("location", "europe") - .AddField("long", 1L) - .AddField("double", 250.69D) - .AddField("float", 35.0F) - .AddField("integer", 7) - .AddField("short", (short)8) - // ReSharper disable once RedundantCast - .AddField("byte", (byte)9) - .AddField("ulong", (ulong)10) - .AddField("uint", (uint)11) - .AddField("sbyte", (sbyte)12) - .AddField("ushort", (ushort)13) - .AddField("point", 13.3) - .AddField("decimal", (decimal)25.6) - .AddField("boolean", false) - .AddField("string", "string value"); - - const string expected = - "h2o,location=europe boolean=false,byte=9i,decimal=25.6,double=250.69,float=35,integer=7i,long=1i," + - "point=13.300000000000001,sbyte=12i,short=8i,string=\"string value\",uint=11u,ulong=10u,ushort=13u"; - - Assert.That(point.ToPointData().ToLineProtocol(), Is.EqualTo(expected)); - } - - [Test] - public void UseGenericObjectAsFieldValue() - { - var point = PointData.Builder.Measurement("h2o") - .AddTag("location", "europe") - .AddField("custom-object", new GenericObject { Value1 = "test", Value2 = 10 }); - - Assert.That(point.ToPointData().ToLineProtocol(), - Is.EqualTo("h2o,location=europe custom-object=\"test-10\"")); - } - } -} \ No newline at end of file diff --git a/Client.Test/Write/PointDataTest.cs b/Client.Test/Write/PointDataTest.cs index 7600b4f..d39f78e 100644 --- a/Client.Test/Write/PointDataTest.cs +++ b/Client.Test/Write/PointDataTest.cs @@ -11,10 +11,10 @@ public class PointDataTest public void TagEmptyTagValue() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("log", "to_delete") - .AddTag("log", "") - .AddField("level", 2); + .SetTag("location", "europe") + .SetTag("log", "to_delete") + .SetTag("log", "") + .SetField("level", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); } @@ -23,10 +23,10 @@ public void TagEmptyTagValue() public void TagEscapingKeyAndValue() { var point = PointData.Measurement("h\n2\ro\t_data") - .AddTag("new\nline", "new\nline") - .AddTag("carriage\rreturn", "carriage\rreturn") - .AddTag("t\tab", "t\tab") - .AddField("level", 2); + .SetTag("new\nline", "new\nline") + .SetTag("carriage\rreturn", "carriage\rreturn") + .SetTag("t\tab", "t\tab") + .SetField("level", 2); Assert.That( point.ToLineProtocol(), Is.EqualTo("h\\n2\\ro\\t_data,carriage\\rreturn=carriage\\rreturn,new\\nline=new\\nline,t\\tab=t\\tab level=2i")); @@ -36,67 +36,33 @@ public void TagEscapingKeyAndValue() public void EqualSignEscaping() { var point = PointData.Measurement("h=2o") - .AddTag("l=ocation", "e=urope") - .AddField("l=evel", 2); + .SetTag("l=ocation", "e=urope") + .SetField("l=evel", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h=2o,l\\=ocation=e\\=urope l\\=evel=2i")); } - [Test] - [SuppressMessage("Assertion", "NUnit2010:Use EqualConstraint for better assertion messages in case of failure")] - [SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")] - public void Immutability() - { - var point = PointData.Measurement("h2 o") - .AddTag("location", "europe"); - - var point1 = point - .AddTag("TAG", "VALX") - .AddField("level", 2); - - var point2 = point - .AddTag("TAG", "VALX") - .AddField("level", 2); - - var point3 = point - .AddTag("TAG", "VALY") - .AddField("level", 2); - - Assert.Multiple(() => - { - Assert.That(point2, Is.EqualTo(point1)); - Assert.That(point1, Is.Not.EqualTo(point)); - Assert.That(ReferenceEquals(point1, point2), Is.False); - Assert.That(point1 == point3, Is.False); - Assert.That(point1 != point3, Is.True); - Assert.That(point1.Equals(null), Is.False); - Assert.That(point1.Equals(10), Is.False); - Assert.That(point1.GetHashCode(), Is.Not.EqualTo(point3.GetHashCode())); - Assert.That(point1, Is.Not.EqualTo(point3)); - }); - } - [Test] public void MeasurementEscape() { var point = PointData.Measurement("h2 o") - .AddTag("location", "europe") - .AddTag("", "warn") - .AddField("level", 2); + .SetTag("location", "europe") + .SetTag("", "warn") + .SetField("level", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2\\ o,location=europe level=2i")); point = PointData.Measurement("h2=o") - .AddTag("location", "europe") - .AddTag("", "warn") - .AddField("level", 2); + .SetTag("location", "europe") + .SetTag("", "warn") + .SetField("level", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2=o,location=europe level=2i")); point = PointData.Measurement("h2,o") - .AddTag("location", "europe") - .AddTag("", "warn") - .AddField("level", 2); + .SetTag("location", "europe") + .SetTag("", "warn") + .SetField("level", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2\\,o,location=europe level=2i")); } @@ -105,9 +71,9 @@ public void MeasurementEscape() public void TagEmptyKey() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("", "warn") - .AddField("level", 2); + .SetTag("location", "europe") + .SetTag("", "warn") + .SetField("level", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); } @@ -116,9 +82,9 @@ public void TagEmptyKey() public void TagEmptyValue() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("log", "") - .AddField("level", 2); + .SetTag("location", "europe") + .SetTag("log", "") + .SetField("level", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); } @@ -127,10 +93,10 @@ public void TagEmptyValue() public void OverrideTagField() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddTag("location", "europe2") - .AddField("level", 2) - .AddField("level", 3); + .SetTag("location", "europe") + .SetTag("location", "europe2") + .SetField("level", 2) + .SetField("level", 3); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe2 level=3i")); } @@ -138,22 +104,22 @@ public void OverrideTagField() [Test] public void FieldTypes() { - var point = PointData.Measurement("h2o").AddTag("location", "europe") - .AddField("long", 1L) - .AddField("double", 250.69D) - .AddField("float", 35.0F) - .AddField("integer", 7) - .AddField("short", (short)8) + var point = PointData.Measurement("h2o").SetTag("location", "europe") + .SetField("long", 1L) + .SetField("double", 250.69D) + .SetField("float", 35.0F) + .SetField("integer", 7) + .SetField("short", (short)8) // ReSharper disable once RedundantCast - .AddField("byte", (byte)9) - .AddField("ulong", (ulong)10) - .AddField("uint", (uint)11) - .AddField("sbyte", (sbyte)12) - .AddField("ushort", (ushort)13) - .AddField("point", 13.3) - .AddField("decimal", (decimal)25.6) - .AddField("boolean", false) - .AddField("string", "string value"); + .SetField("byte", (byte)9) + .SetField("ulong", (ulong)10) + .SetField("uint", (uint)11) + .SetField("sbyte", (sbyte)12) + .SetField("ushort", (ushort)13) + .SetField("point", 13.3) + .SetField("decimal", (decimal)25.6) + .SetField("boolean", false) + .SetField("string", "string value"); const string expected = "h2o,location=europe boolean=false,byte=9i,decimal=25.6,double=250.69,float=35,integer=7i,long=1i," + "point=13.300000000000001,sbyte=12i,short=8i,string=\"string value\",uint=11u,ulong=10u,ushort=13u"; @@ -165,11 +131,11 @@ public void FieldTypes() public void DoubleFormat() { var point = PointData.Measurement("sensor") - .AddField("double", 250.69D) - .AddField("double15", 15.333333333333333D) - .AddField("double16", 16.3333333333333333D) - .AddField("double17", 17.33333333333333333D) - .AddField("example", 459.29587181322927); + .SetField("double", 250.69D) + .SetField("double15", 15.333333333333333D) + .SetField("double16", 16.3333333333333333D) + .SetField("double17", 17.33333333333333333D) + .SetField("example", 459.29587181322927); const string expected = "sensor double=250.69,double15=15.333333333333332,double16=16.333333333333332," + "double17=17.333333333333332,example=459.29587181322927"; @@ -181,9 +147,9 @@ public void DoubleFormat() public void FieldNullValue() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) - .AddField("warning", null!); + .SetTag("location", "europe") + .SetField("level", 2) + .SetField("warning", null!); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); } @@ -192,14 +158,14 @@ public void FieldNullValue() public void FieldEscape() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", "string esc\\ape value"); + .SetTag("location", "europe") + .SetField("level", "string esc\\ape value"); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=\"string esc\\\\ape value\"")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", "string esc\"ape value"); + .SetTag("location", "europe") + .SetField("level", "string esc\"ape value"); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=\"string esc\\\"ape value\"")); } @@ -208,8 +174,8 @@ public void FieldEscape() public void Time() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(123L); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 123")); @@ -219,29 +185,29 @@ public void Time() public void TimePrecision() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(123_000_000_000L); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 123000000000")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(123_000_000L, WritePrecision.Us); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 123000000000")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(123_000L, WritePrecision.Ms); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 123000000000")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(123L, WritePrecision.S); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 123000000000")); @@ -251,8 +217,8 @@ public void TimePrecision() public void LineProtocolTimePrecision() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(123_000_000_000L); Assert.Multiple(() => @@ -269,22 +235,22 @@ public void LineProtocolTimePrecision() public void TimeSpanFormatting() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(TimeSpan.FromDays(1)); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 86400000000000")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(TimeSpan.FromHours(356)); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 1281600000000000")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(TimeSpan.FromSeconds(156)); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 156000000000")); @@ -296,8 +262,8 @@ public void DateTimeFormatting() var dateTime = new DateTime(2015, 10, 15, 8, 20, 15, DateTimeKind.Utc); var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(dateTime); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 1444897215000000000")); @@ -305,23 +271,23 @@ public void DateTimeFormatting() dateTime = new DateTime(2015, 10, 15, 8, 20, 15, 750, DateTimeKind.Utc); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", false) + .SetTag("location", "europe") + .SetField("level", false) .SetTimestamp(dateTime); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=false 1444897215750000000")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", true) + .SetTag("location", "europe") + .SetField("level", true) .SetTimestamp(DateTime.UtcNow); var lineProtocol = point.ToLineProtocol(); Assert.That(lineProtocol, Does.Not.Contain(".")); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", true) + .SetTag("location", "europe") + .SetField("level", true) .SetTimestamp(DateTime.UtcNow); lineProtocol = point.ToLineProtocol(); @@ -334,8 +300,8 @@ public void DateTimeUnspecified() var dateTime = new DateTime(2015, 10, 15, 8, 20, 15, DateTimeKind.Unspecified); var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(dateTime); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 1444897215000000000")); @@ -347,15 +313,15 @@ public void DateTimeLocal() var dateTime = new DateTime(2015, 10, 15, 8, 20, 15, DateTimeKind.Local); var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(dateTime); var lineProtocolLocal = point.ToLineProtocol(); point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(TimeZoneInfo.ConvertTimeToUtc(dateTime)); var lineProtocolUtc = point.ToLineProtocol(); @@ -368,8 +334,8 @@ public void DateTimeOffsetFormatting() var offset = DateTimeOffset.FromUnixTimeSeconds(15678); var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("level", 2) + .SetTag("location", "europe") + .SetField("level", 2) .SetTimestamp(offset); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i 15678000000000")); @@ -381,9 +347,9 @@ public void HasFields() Assert.Multiple(() => { Assert.That(PointData.Measurement("h2o").HasFields(), Is.False); - Assert.That(PointData.Measurement("h2o").AddTag("location", "europe").HasFields(), Is.False); - Assert.That(PointData.Measurement("h2o").AddField("level", "2").HasFields(), Is.True); - Assert.That(PointData.Measurement("h2o").AddTag("location", "europe").AddField("level", "2").HasFields(), Is.True); + Assert.That(PointData.Measurement("h2o").SetTag("location", "europe").HasFields(), Is.False); + Assert.That(PointData.Measurement("h2o").SetField("level", "2").HasFields(), Is.True); + Assert.That(PointData.Measurement("h2o").SetTag("location", "europe").SetField("level", "2").HasFields(), Is.True); }); } @@ -391,14 +357,14 @@ public void HasFields() public void InfinityValues() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("double-infinity-positive", double.PositiveInfinity) - .AddField("double-infinity-negative", double.NegativeInfinity) - .AddField("double-nan", double.NaN) - .AddField("flout-infinity-positive", float.PositiveInfinity) - .AddField("flout-infinity-negative", float.NegativeInfinity) - .AddField("flout-nan", float.NaN) - .AddField("level", 2); + .SetTag("location", "europe") + .SetField("double-infinity-positive", double.PositiveInfinity) + .SetField("double-infinity-negative", double.NegativeInfinity) + .SetField("double-nan", double.NaN) + .SetField("flout-infinity-positive", float.PositiveInfinity) + .SetField("flout-infinity-negative", float.NegativeInfinity) + .SetField("flout-nan", float.NaN) + .SetField("level", 2); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe level=2i")); } @@ -407,13 +373,13 @@ public void InfinityValues() public void OnlyInfinityValues() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("double-infinity-positive", double.PositiveInfinity) - .AddField("double-infinity-negative", double.NegativeInfinity) - .AddField("double-nan", double.NaN) - .AddField("flout-infinity-positive", float.PositiveInfinity) - .AddField("flout-infinity-negative", float.NegativeInfinity) - .AddField("flout-nan", float.NaN); + .SetTag("location", "europe") + .SetField("double-infinity-positive", double.PositiveInfinity) + .SetField("double-infinity-negative", double.NegativeInfinity) + .SetField("double-nan", double.NaN) + .SetField("flout-infinity-positive", float.PositiveInfinity) + .SetField("flout-infinity-negative", float.NegativeInfinity) + .SetField("flout-nan", float.NaN); Assert.That(point.ToLineProtocol(), Is.EqualTo("")); } @@ -422,8 +388,8 @@ public void OnlyInfinityValues() public void UseGenericObjectAsFieldValue() { var point = PointData.Measurement("h2o") - .AddTag("location", "europe") - .AddField("custom-object", new GenericObject { Value1 = "test", Value2 = 10 }); + .SetTag("location", "europe") + .SetField("custom-object", new GenericObject { Value1 = "test", Value2 = 10 }); Assert.That(point.ToLineProtocol(), Is.EqualTo("h2o,location=europe custom-object=\"test-10\"")); } diff --git a/Client/InfluxDBClient.cs b/Client/InfluxDBClient.cs index 192f8c0..0d64b28 100644 --- a/Client/InfluxDBClient.cs +++ b/Client/InfluxDBClient.cs @@ -160,6 +160,79 @@ public InfluxDBClient(ClientConfig config) } } + /// + /// Query data from InfluxDB IOx into PointData structure using FlightSQL. + /// + /// The SQL query string to execute. + /// The type of query sent to InfluxDB. Default to 'SQL'. + /// The database to be used for InfluxDB operations. + /// Batches of rows + /// The client is already disposed + public async IAsyncEnumerable QueryPoints(string query, QueryType? queryType = null, + string? database = null) + { + await foreach (var batch in QueryBatches(query, queryType, database).ConfigureAwait(false)) + { + var rowCount = batch.Column(0).Length; + for (var i = 0; i < rowCount; i++) + { + var point = new PointDataValues(); + for (var j = 0; j < batch.ColumnCount; j++) + { + var schema = batch.Schema.FieldsList[j]; + var fullName = schema.Name; + + if (batch.Column(j) is not ArrowArray array) + continue; + + var objectValue = array.GetObjectValue(i); + if (objectValue is null) + continue; + + if ((fullName == "measurement" || fullName == "iox::measurement") && objectValue is string) + { + point = point.SetMeasurement((string)objectValue); + continue; + } + + if (!schema.HasMetadata) + { + if (fullName == "time" && objectValue is DateTimeOffset timestamp) + { + point = point.SetTimestamp(timestamp); + } + else + // just push as field If you don't know what type is it + point = point.SetField(fullName, objectValue); + + continue; + } + + var type = schema.Metadata["iox::column::type"]; + var parts = type.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + var valueType = parts[2]; + // string fieldType = parts.Length > 3 ? parts[3] : ""; + + if (valueType == "field") + { + point = point.SetField(fullName, objectValue); + } + else if (valueType == "tag") + { + point = point.SetTag(fullName, (string)objectValue); + } + else if (valueType == "timestamp" && objectValue is DateTimeOffset timestamp) + { + point = point.SetTimestamp(timestamp); + } + + } + + yield return point; + } + } + } + /// /// Query data from InfluxDB IOx using FlightSQL. /// diff --git a/Client/Internal/AssemblyHelper.cs b/Client/Internal/AssemblyHelper.cs index bb69cd6..41029aa 100644 --- a/Client/Internal/AssemblyHelper.cs +++ b/Client/Internal/AssemblyHelper.cs @@ -1,4 +1,3 @@ -using System; using System.Reflection; namespace InfluxDB3.Client.Internal diff --git a/Client/Internal/FlightSqlExtensions.cs b/Client/Internal/FlightSqlExtensions.cs index d6e2398..66bdc24 100644 --- a/Client/Internal/FlightSqlExtensions.cs +++ b/Client/Internal/FlightSqlExtensions.cs @@ -1,6 +1,5 @@ using System; using Apache.Arrow; -using Apache.Arrow.Arrays; using Array = Apache.Arrow.Array; namespace InfluxDB3.Client.Internal; @@ -14,7 +13,7 @@ internal static class FlightSqlExtensions /// Array /// Row index /// - /// Type of array is not supported + /// Type of array is not supported internal static object? GetObjectValue(this Array array, int index) { return array switch diff --git a/Client/Write/PointData.Builder.cs b/Client/Write/PointData.Builder.cs deleted file mode 100644 index 9b60e4a..0000000 --- a/Client/Write/PointData.Builder.cs +++ /dev/null @@ -1,255 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Numerics; -using InfluxDB3.Client.Internal; - -namespace InfluxDB3.Client.Write -{ - public partial class PointData - { - public sealed class Builder - { - private readonly string _measurementName; - private readonly Dictionary _tags = new(); - private readonly Dictionary _fields = new(); - - private BigInteger? _time; - - private Builder(string measurementName) - { - Arguments.CheckNonEmptyString(measurementName, "Measurement name"); - - _measurementName = measurementName; - } - - /// - /// Create a new Point withe specified a measurement name. - /// - /// the measurement name - /// the new Point - public static Builder Measurement(string measurementName) - { - return new Builder(measurementName); - } - - /// - /// Adds or replaces a tag value for a point. - /// - /// the tag name - /// the tag value - /// this - public Builder AddTag(string name, string value) - { - var isEmptyValue = string.IsNullOrEmpty(value); - if (isEmptyValue) - { - if (_tags.ContainsKey(name)) - { - Trace.TraceWarning( - $"Empty tags will cause deletion of, tag [{name}], measurement [{_measurementName}]"); - _tags.Remove(name); - } - else - { - Trace.TraceWarning($"Empty tags has no effect, tag [{name}], measurement [{_measurementName}]"); - } - } - else - { - _tags[name] = value; - } - - return this; - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, byte value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, float value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, double value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, decimal value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, long value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, ulong value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, uint value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, string value) - { - return PutField(name, value); - } - - /// - /// Add a field with a value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, bool value) - { - return PutField(name, value); - } - - /// - /// Add a field with an value. - /// - /// the field name - /// the field value - /// this - public Builder AddField(string name, object value) - { - return PutField(name, value); - } - - /// - /// Updates the timestamp for the point. - /// - /// the timestamp - /// the timestamp precision. Default is 'nanoseconds'. - /// - public Builder SetTimestamp(long timestamp, WritePrecision? timeUnit = null) - { - _time = LongToBigInteger(timestamp, timeUnit); - return this; - } - - /// - /// Updates the timestamp for the point represented by . - /// - /// the timestamp - /// - public Builder SetTimestamp(TimeSpan timestamp) - { - _time = TimeSpanToBigInteger(timestamp); - return this; - } - - /// - /// Updates the timestamp for the point represented by . - /// - /// the timestamp - /// - public Builder SetTimestamp(DateTime timestamp) - { - if (timestamp != null && timestamp.Kind != DateTimeKind.Utc) - { - throw new ArgumentException("Timestamps must be specified as UTC", nameof(timestamp)); - } - - var timeSpan = timestamp.Subtract(EpochStart); - - return SetTimestamp(timeSpan); - } - - /// - /// Updates the timestamp for the point represented by . - /// - /// the timestamp - /// - public Builder SetTimestamp(DateTimeOffset timestamp) - { - return SetTimestamp(timestamp.UtcDateTime); - } - - /// - /// Has point any fields? - /// - /// true, if the point contains any fields, false otherwise. - public bool HasFields() - { - return _fields.Count > 0; - } - - /// - /// The PointData - /// - /// - public PointData ToPointData() - { - return new PointData(_measurementName, _time, - new SortedDictionary(_tags), - new SortedDictionary(_fields)); - } - - private Builder PutField(string name, object value) - { - Arguments.CheckNonEmptyString(name, "Field name"); - - _fields[name] = value; - return this; - } - } - } -} \ No newline at end of file diff --git a/Client/Write/PointData.cs b/Client/Write/PointData.cs index 8da8e5c..10119fd 100644 --- a/Client/Write/PointData.cs +++ b/Client/Write/PointData.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; -using System.Linq; using System.Numerics; using System.Text; -using InfluxDB3.Client.Internal; namespace InfluxDB3.Client.Write { @@ -13,45 +10,123 @@ namespace InfluxDB3.Client.Write /// Point defines the values that will be written to the database. /// See Go Implementation. /// - public partial class PointData : IEquatable + public class PointData : IEquatable { - private static readonly DateTime EpochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private const long C1000 = 1000L; + private const long C1000000 = C1000 * 1000L; + private const long C1000000000 = C1000000 * 1000L; - private readonly string _measurementName; + private readonly PointDataValues _values; - private readonly SortedDictionary _tags = new(); - private readonly SortedDictionary _fields = new(); - private readonly BigInteger? _time; + public PointData(PointDataValues values) { _values = values; } - private const long C1000 = 1000L; - private const long C1000000 = C1000 * 1000L; - private const long C1000000000 = C1000000 * 1000L; + /// + /// Create a new Point with specified a measurement name. + /// + /// the measurement name + /// the new Point + public static PointData Measurement(string measurementName) + { + return new PointData(new PointDataValues()).SetMeasurement(measurementName); + } - private PointData(string measurementName) + /// + /// Create a new Point with given values. + /// + /// the point values + /// the new Point + public static PointData FromValues(PointDataValues values) { - Arguments.CheckNonEmptyString(measurementName, "Measurement name"); + if (values.GetMeasurement() is null) + { + throw new Exception("Missing measurement!"); + } + return new PointData(values); + } - _measurementName = measurementName; + /// + /// Get measurement name. + /// + /// Measurement name + public string GetMeasurement() + { + return _values.GetMeasurement() ?? throw new Exception("Missing measurement!"); } /// - /// Create a new Point withe specified a measurement name. + /// Create new Point with this values and provided measurement. /// /// the measurement name - /// the new Point - public static PointData Measurement(string measurementName) + /// copy of this Point with given measurement name + public PointData SetMeasurement(string measurementName) + { + _values.SetMeasurement(measurementName); + return this; + } + + /// + /// Get timestamp. Can be null if not set. + /// + /// timestamp or null + public BigInteger? GetTimestamp() + { + return _values.GetTimestamp(); + } + + /// + /// Updates the timestamp for the point. + /// + /// the timestamp + /// the timestamp precision. Default is 'nanoseconds'. + /// + public PointData SetTimestamp(long timestamp, WritePrecision? timeUnit = null) + { + _values.SetTimestamp(timestamp, timeUnit); + return this; + } + + /// + /// Updates the timestamp for the point represented by . + /// + /// the timestamp + /// + public PointData SetTimestamp(TimeSpan timestamp) + { + _values.SetTimestamp(timestamp); + return this; + } + + /// + /// Updates the timestamp for the point represented by . + /// + /// the timestamp + /// + public PointData SetTimestamp(DateTime timestamp) { - return new PointData(measurementName); + _values.SetTimestamp(timestamp); + return this; } - private PointData(string measurementName, BigInteger? time, SortedDictionary tags, - SortedDictionary fields) + /// + /// Updates the timestamp for the point represented by . + /// + /// the timestamp + /// + public PointData SetTimestamp(DateTimeOffset timestamp) { - _measurementName = measurementName; - _time = time; - _tags = tags; - _fields = fields; + return SetTimestamp(timestamp.UtcDateTime); + } + + /// + /// Gets value of tag with given name. Returns null if tag not found. + /// + /// + /// the tag name + /// tag value or null + public string? GetTag(string name) + { + return _values.GetTag(name); } /// @@ -60,202 +135,280 @@ private PointData(string measurementName, BigInteger? time, SortedDictionarythe tag name /// the tag value /// this - public PointData AddTag(string name, string value) + public PointData SetTag(string name, string value) { - var isEmptyValue = string.IsNullOrEmpty(value); - var tags = new SortedDictionary(_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; - } - } + _values.SetTag(name, value); + return this; + } - if (tags.ContainsKey(name)) - { - tags.Remove(name); - } + /// + /// Removes a tag with the specified name if it exists; otherwise, it does nothing. + /// + /// the tag name + /// this + public PointData RemoveTag(string name) + { + _values.RemoveTag(name); + return this; + } + + /// + /// Gets an array of tag names. + /// + /// An array of tag names + public string[] GetTagNames() + { + return _values.GetTagNames(); + } - if (!isEmptyValue) - { - tags.Add(name, value); - } - return new PointData(_measurementName, _time, tags, _fields); + /// + /// Gets the float field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The float field value or null + public double? GetFloatField(string name) + { + return _values.GetFloatField(name); } /// - /// Add a field with a value. + /// Adds or replaces a float field. /// /// the field name /// the field value /// this - public PointData AddField(string name, byte value) + public PointData SetFloatField(string name, double value) + { + _values.SetFloatField(name, value); + return this; + } + + /// + /// Gets the integer field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The integer field value or null + public long? GetIntegerField(string name) { - return PutField(name, value); + return _values.GetIntegerField(name); } /// - /// Add a field with a value. + /// Adds or replaces a integer field. /// /// the field name /// the field value /// this - public PointData AddField(string name, float value) + public PointData SetIntegerField(string name, long value) { - return PutField(name, value); + _values.SetIntegerField(name, value); + return this; } /// - /// Add a field with a value. + /// Gets the uinteger field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The uinteger field value or null + public ulong? GetUintegerField(string name) + { + return _values.GetUintegerField(name); + } + + /// + /// Adds or replaces a uinteger field. /// /// the field name /// the field value /// this - public PointData AddField(string name, double value) + public PointData SetUintegerField(string name, ulong value) { - return PutField(name, value); + _values.SetUintegerField(name, value); + return this; } /// - /// Add a field with a value. + /// Gets the string field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The string field value or null + public string? GetStringField(string name) + { + return _values.GetStringField(name); + } + + /// + /// Adds or replaces a string field. /// /// the field name /// the field value /// this - public PointData AddField(string name, decimal value) + public PointData SetStringField(string name, string value) + { + _values.SetStringField(name, value); + return this; + } + + /// + /// Gets the bool field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The bool field value or null + public bool? GetBooleanField(string name) { - return PutField(name, value); + return _values.GetBooleanField(name); } /// - /// Add a field with a value. + /// Adds or replaces a bool field. /// /// the field name /// the field value /// this - public PointData AddField(string name, long value) + public PointData SetBooleanField(string name, bool value) + { + _values.SetBooleanField(name, value); + return this; + } + + + /// + /// Get field of given name. Can be null if field doesn't exist. + /// + /// Field as object + public object? GetField(string name) + { + return _values.GetField(name); + } + + /// + /// Get field of given name as type. Can be null if field doesn't exist. + /// + /// Field as given type + /// Field doesn't match given type + public T? GetField(string name) where T : struct + { + return _values.GetField(name); + } + + /// + /// Gets the type of field with given name, if it exists. + /// If the field is not present, returns null. + /// + /// the field name + /// The field type or null. + public Type? GetFieldType(string name) { - return PutField(name, value); + return _values.GetFieldType(name); } /// - /// Add a field with a value. + /// Adds or replaces a field with a value. /// /// the field name /// the field value /// this - public PointData AddField(string name, ulong value) + public PointData SetField(string name, double value) { - return PutField(name, value); + _values.SetField(name, value); + return this; } /// - /// Add a field with a value. + /// Adds or replaces a field with a value. /// /// the field name /// the field value /// this - public PointData AddField(string name, uint value) + public PointData SetField(string name, long value) { - return PutField(name, value); + _values.SetField(name, value); + return this; } /// - /// Add a field with a value. + /// Adds or replaces a field with a value. /// /// the field name /// the field value /// this - public PointData AddField(string name, string value) + public PointData SetField(string name, ulong value) { - return PutField(name, value); + _values.SetField(name, value); + return this; } /// - /// Add a field with a value. + /// Adds or replaces a field with a value. /// /// the field name /// the field value /// this - public PointData AddField(string name, bool value) + public PointData SetField(string name, string value) { - return PutField(name, value); + _values.SetField(name, value); + return this; } /// - /// Add a field with an value. + /// Adds or replaces a field with a value. /// /// the field name /// the field value /// this - public PointData AddField(string name, object value) + public PointData SetField(string name, bool value) { - return PutField(name, value); + _values.SetField(name, value); + return this; } /// - /// Updates the timestamp for the point. + /// Adds or replaces a field with an value. /// - /// the timestamp - /// the timestamp precision. Default is 'nanoseconds'. - /// - public PointData SetTimestamp(long timestamp, WritePrecision? timeUnit = null) + /// the field name + /// the field value + /// this + public PointData SetField(string name, object value) { - return new PointData(_measurementName, - LongToBigInteger(timestamp, timeUnit), - _tags, - _fields); + _values.SetField(name, value); + return this; } /// - /// Updates the timestamp for the point represented by . + /// Add fields according to their type. /// - /// the timestamp - /// - public PointData SetTimestamp(TimeSpan timestamp) + /// the name-value dictionary + /// this + public PointData SetFields(Dictionary fields) { - var time = TimeSpanToBigInteger(timestamp); - return new PointData(_measurementName, - time, - _tags, - _fields); + _values.SetFields(fields); + return this; } /// - /// Updates the timestamp for the point represented by . + /// Removes a field with the specified name if it exists; otherwise, it does nothing. /// - /// the timestamp - /// - public PointData SetTimestamp(DateTime timestamp) + /// The name of the field to be removed. + /// this + public PointData RemoveField(string name) { - var utcTimestamp = timestamp.Kind switch - { - DateTimeKind.Local => timestamp.ToUniversalTime(), - DateTimeKind.Unspecified => DateTime.SpecifyKind(timestamp, DateTimeKind.Utc), - _ => timestamp - }; - - var timeSpan = utcTimestamp.Subtract(EpochStart); - - return SetTimestamp(timeSpan); + _values.RemoveField(name); + return this; } /// - /// Updates the timestamp for the point represented by . + /// Gets an array of field names associated with this object. /// - /// the timestamp - /// - public PointData SetTimestamp(DateTimeOffset timestamp) + /// An array of field names. + public string[] GetFieldNames() { - return SetTimestamp(timestamp.UtcDateTime); + return _values.GetFieldNames(); } /// @@ -264,7 +417,16 @@ public PointData SetTimestamp(DateTimeOffset timestamp) /// true, if the point contains any fields, false otherwise. public bool HasFields() { - return _fields.Count > 0; + return _values.HasFields(); + } + + /// + /// Creates a deep copy of this object. + /// + /// A new instance with copied values. + public PointData Copy() + { + return new PointData(_values.Copy()); } /// @@ -276,7 +438,7 @@ public string ToLineProtocol(WritePrecision? timeUnit = null) { var sb = new StringBuilder(); - EscapeKey(sb, _measurementName, false); + EscapeKey(sb, _values.GetMeasurement()!, false); AppendTags(sb); var appendedFields = AppendFields(sb); if (!appendedFields) @@ -289,65 +451,25 @@ public string ToLineProtocol(WritePrecision? timeUnit = null) return sb.ToString(); } - private PointData PutField(string name, object value) - { - Arguments.CheckNonEmptyString(name, "Field name"); - - var fields = new SortedDictionary(_fields); - if (fields.ContainsKey(name)) - { - fields.Remove(name); - } - - fields.Add(name, value); - - return new PointData(_measurementName, - _time, - _tags, - fields); - } - - private static BigInteger TimeSpanToBigInteger(TimeSpan timestamp) - { - return timestamp.Ticks * 100; - } - - private static BigInteger LongToBigInteger(long timestamp, WritePrecision? timeUnit = null) - { - switch (timeUnit ?? WritePrecision.Ns) - { - case WritePrecision.Us: - return timestamp * C1000; - case WritePrecision.Ms: - return timestamp * C1000000; - case WritePrecision.S: - return timestamp * C1000000000; - case WritePrecision.Ns: - default: - return timestamp; - } - } - /// /// Appends the tags. /// /// The writer. private void AppendTags(StringBuilder writer) { - foreach (var keyValue in _tags) + foreach (var name in _values.GetTagNames()) { - var key = keyValue.Key; - var value = keyValue.Value; + var value = _values.GetTag(name); - if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) { continue; } writer.Append(','); - EscapeKey(writer, key); + EscapeKey(writer, name); writer.Append('='); - EscapeKey(writer, value); + EscapeKey(writer, value!); } writer.Append(' '); @@ -362,17 +484,16 @@ private bool AppendFields(StringBuilder sb) { var appended = false; - foreach (var keyValue in _fields) + foreach (var name in _values.GetFieldNames()) { - var key = keyValue.Key; - var value = keyValue.Value; + var value = _values.GetField(name)!; if (IsNotDefined(value)) { continue; } - EscapeKey(sb, key); + EscapeKey(sb, name); sb.Append('='); if (value is float) @@ -434,12 +555,13 @@ private bool AppendFields(StringBuilder sb) /// private void AppendTime(StringBuilder sb, WritePrecision? writePrecision) { - if (_time == null) + var time = _values.GetTimestamp(); + if (time == null) { return; } - var timestamp = (BigInteger)_time; + var timestamp = (BigInteger)time; switch (writePrecision ?? WritePrecision.Ns) { case WritePrecision.Us: @@ -559,31 +681,7 @@ public bool Equals(PointData? other) 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 && - EqualityComparer.Default.Equals(_time, other._time); - - return result; + return _values.Equals(other._values); } /// @@ -594,23 +692,7 @@ public bool Equals(PointData? other) /// public override int GetHashCode() { - var hashCode = 318335609; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_measurementName); - 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; + return _values.GetHashCode(); } /// diff --git a/Client/Write/PointDataValues.cs b/Client/Write/PointDataValues.cs new file mode 100644 index 0000000..a3c5c6d --- /dev/null +++ b/Client/Write/PointDataValues.cs @@ -0,0 +1,626 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; +using InfluxDB3.Client.Internal; + +namespace InfluxDB3.Client.Write +{ + /// + /// Point defines the values that will be written to the database. + /// See Go Implementation. + /// + public class PointDataValues : IEquatable + { + private static readonly DateTime EpochStart = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private string? _measurementName; + + private SortedDictionary _tags = new(); + private SortedDictionary _fields = new(); + + private BigInteger? _time; + + private const long C1000 = 1000L; + private const long C1000000 = C1000 * 1000L; + private const long C1000000000 = C1000000 * 1000L; + + /// + /// Create a new Point withe specified a measurement name. + /// + /// the measurement name + /// the new Point + public static PointDataValues Measurement(string measurementName) + { + return new PointDataValues().SetMeasurement(measurementName); + } + + /// + /// Get measurement name. + /// + /// Measurement name + public string? GetMeasurement() + { + return _measurementName; + } + + /// + /// Create new Point with this values and provided measurement. + /// + /// the measurement name + /// copy of this Point with given measurement name + public PointDataValues SetMeasurement(string measurementName) + { + _measurementName = measurementName; + return this; + } + + /// + /// Get timestamp. Can be null if not set. + /// + /// timestamp or null + public BigInteger? GetTimestamp() + { + return _time; + } + + /// + /// Updates the timestamp for the point. + /// + /// the timestamp + /// the timestamp precision. Default is 'nanoseconds'. + /// + public PointDataValues SetTimestamp(long timestamp, WritePrecision? timeUnit = null) + { + _time = LongToBigInteger(timestamp, timeUnit); + return this; + } + + /// + /// Updates the timestamp for the point represented by . + /// + /// the timestamp + /// + public PointDataValues SetTimestamp(TimeSpan timestamp) + { + _time = TimeSpanToBigInteger(timestamp); + return this; + } + + /// + /// Updates the timestamp for the point represented by . + /// + /// the timestamp + /// + public PointDataValues SetTimestamp(DateTime timestamp) + { + var utcTimestamp = timestamp.Kind switch + { + DateTimeKind.Local => timestamp.ToUniversalTime(), + DateTimeKind.Unspecified => DateTime.SpecifyKind(timestamp, DateTimeKind.Utc), + _ => timestamp + }; + + var timeSpan = utcTimestamp.Subtract(EpochStart); + + return SetTimestamp(timeSpan); + } + + /// + /// Updates the timestamp for the point represented by . + /// + /// the timestamp + /// + public PointDataValues SetTimestamp(DateTimeOffset timestamp) + { + return SetTimestamp(timestamp.UtcDateTime); + } + + /// + /// Gets value of tag with given name. Returns null if tag not found. + /// + /// + /// the tag name + /// tag value or null + public string? GetTag(string name) + { + return _tags.TryGetValue(name, out var value) ? value : null; + } + + /// + /// Adds or replaces a tag value for a point. + /// + /// the tag name + /// the tag value + /// this + public PointDataValues SetTag(string name, string value) + { + var isEmptyValue = string.IsNullOrEmpty(value); + if (isEmptyValue) + { + if (_tags.ContainsKey(name)) + { + Trace.TraceWarning( + $"Empty tags will cause deletion of, tag [{name}], measurement [{_measurementName}]"); + _tags.Remove(name); + } + else + { + Trace.TraceWarning($"Empty tags has no effect, tag [{name}], measurement [{_measurementName}]"); + } + } + else + { + _tags[name] = value; + } + + return this; + } + + /// + /// Removes a tag with the specified name if it exists; otherwise, it does nothing. + /// + /// the tag name + /// this + public PointDataValues RemoveTag(string name) + { + _tags.Remove(name); + return this; + } + + /// + /// Gets an array of tag names. + /// + /// An array of tag names + public string[] GetTagNames() + { + return _tags.Keys.ToArray(); + } + + /// + /// Gets the float field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The float field value or null + public double? GetFloatField(string name) + { + return _fields.TryGetValue(name, out var result) ? (double)result : null; + } + + /// + /// Adds or replaces a float field. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetFloatField(string name, double value) + { + SetField(name, value); + return this; + } + + /// + /// Gets the integer field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The integer field value or null + public long? GetIntegerField(string name) + { + return _fields.TryGetValue(name, out var result) ? (long)result : null; + } + + /// + /// Adds or replaces a integer field. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetIntegerField(string name, long value) + { + SetField(name, value); + return this; + } + + /// + /// Gets the uinteger field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The uinteger field value or null + public ulong? GetUintegerField(string name) + { + return _fields.TryGetValue(name, out var result) ? (ulong)result : null; + } + + /// + /// Adds or replaces a uinteger field. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetUintegerField(string name, ulong value) + { + SetField(name, value); + return this; + } + + /// + /// Gets the string field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The string field value or null + public string? GetStringField(string name) + { + return _fields.TryGetValue(name, out var result) ? (string)result : null; + } + + /// + /// Adds or replaces a string field. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetStringField(string name, string value) + { + SetField(name, value); + return this; + } + + /// + /// Gets the bool field value associated with the specified name. + /// If the field is not present, returns null. + /// + /// the field name + /// The bool field value or null + public bool? GetBooleanField(string name) + { + return _fields.TryGetValue(name, out var result) ? (bool)result : null; + } + + /// + /// Adds or replaces a bool field. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetBooleanField(string name, bool value) + { + SetField(name, value); + return this; + } + + + /// + /// Get field of given name. Can be null if field doesn't exist. + /// + /// Field as object + public object? GetField(string name) + { + return _fields.TryGetValue(name, out var value) ? value : null; + } + + /// + /// Get field of given name as type. Can be null if field doesn't exist. + /// + /// Field as given type + /// Field doesn't match given type + public T? GetField(string name) where T : struct + { + return _fields.TryGetValue(name, out var value) ? (T)value : null; + } + + /// + /// Gets the type of field with given name, if it exists. + /// If the field is not present, returns null. + /// + /// the field name + /// The field type or null. + public Type? GetFieldType(string name) + { + return _fields.TryGetValue(name, out var value) ? value.GetType() : null; + } + + /// + /// Adds or replaces a field with a value. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetField(string name, double value) + { + return SetField(name, (object)value); + } + + /// + /// Adds or replaces a field with a value. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetField(string name, long value) + { + return SetField(name, (object)value); + } + + /// + /// Adds or replaces a field with a value. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetField(string name, ulong value) + { + return SetField(name, (object)value); + } + + /// + /// Adds or replaces a field with a value. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetField(string name, string value) + { + return SetField(name, (object)value); + } + + /// + /// Adds or replaces a field with a value. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetField(string name, bool value) + { + return SetField(name, (object)value); + } + + /// + /// Adds or replaces a field with an value. + /// + /// the field name + /// the field value + /// this + public PointDataValues SetField(string name, object value) + { + Arguments.CheckNonEmptyString(name, "Field name"); + + _fields[name] = value; + return this; + } + + /// + /// Add fields according to their type. + /// + /// the name-value dictionary + /// this + public PointDataValues SetFields(Dictionary fields) + { + foreach (var item in fields) + { + SetField(item.Key, item.Value); + } + return this; + } + + /// + /// Removes a field with the specified name if it exists; otherwise, it does nothing. + /// + /// The name of the field to be removed. + /// this + public PointDataValues RemoveField(string name) + { + _fields.Remove(name); + return this; + } + + /// + /// Gets an array of field names associated with this object. + /// + /// An array of field names. + public string[] GetFieldNames() + { + return _fields.Keys.ToArray(); + } + + /// + /// Has point any fields? + /// + /// true, if the point contains any fields, false otherwise. + public bool HasFields() + { + return _fields.Count > 0; + } + + /// + /// Creates a copy of this object. + /// + /// A new instance with same values. + public PointDataValues Copy() + { + return new PointDataValues + { + _measurementName = _measurementName, + _tags = new SortedDictionary(_tags), + _fields = new SortedDictionary(_fields), + _time = _time, + }; + } + + /// + /// Creates new Point with this as values with given measurement. + /// + /// the point measurement + /// Point from this values with given measurement. + public PointData AsPoint(string measurement) + { + SetMeasurement(measurement); + return AsPoint(); + } + + /// + /// Creates new Point with this as values. + /// + /// Point from this values. + public PointData AsPoint() + { + return PointData.FromValues(this); + } + + /// + /// Creates new Point with this as values with given measurement. + /// + /// the point measurement + /// Point from this values with given measurement. + public PointData AsPointData(string measurement) + { + return AsPoint(measurement); + } + + + /// + /// Creates new Point with this as values. + /// + /// Point from this values. + public PointData AsPointData() + { + return AsPoint(); + } + + private static BigInteger TimeSpanToBigInteger(TimeSpan timestamp) + { + return timestamp.Ticks * 100; + } + + private static BigInteger LongToBigInteger(long timestamp, WritePrecision? timeUnit = null) + { + switch (timeUnit ?? WritePrecision.Ns) + { + case WritePrecision.Us: + return timestamp * C1000; + case WritePrecision.Ms: + return timestamp * C1000000; + case WritePrecision.S: + return timestamp * C1000000000; + case WritePrecision.Ns: + default: + return timestamp; + } + } + + /// + /// 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 PointDataValues); + } + + /// + /// 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(PointDataValues? 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 && + 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 ?? string.Empty); + 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 ==(PointDataValues? left, PointDataValues? right) + { + if (left is null && right is null) + return true; + if (left is null || right is null) + return false; + + return EqualityComparer.Default.Equals(left, right); + } + + /// + /// Implements the operator !=. + /// + /// The left. + /// The right. + /// + /// The result of the operator. + /// + public static bool operator !=(PointDataValues left, PointDataValues right) + { + return !(left == right); + } + } +} \ No newline at end of file diff --git a/Examples/IOx/IOxExample.cs b/Examples/IOx/IOxExample.cs index 55c3e63..4de311e 100644 --- a/Examples/IOx/IOxExample.cs +++ b/Examples/IOx/IOxExample.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; using InfluxDB3.Client; using InfluxDB3.Client.Query; @@ -20,8 +21,8 @@ static async Task Main(string[] args) // Write by Point // var point = PointData.Measurement("temperature") - .AddTag("location", "west") - .AddField("value", 55.15) + .SetTag("location", "west") + .SetField("value", 55.15) .SetTimestamp(DateTime.UtcNow.AddSeconds(-10)); await client.WritePointAsync(point: point); @@ -41,8 +42,6 @@ static async Task Main(string[] args) Console.WriteLine("{0,-30}{1,-15}{2,-15}", row[0], row[1], row[2]); } - Console.WriteLine(); - // // Query by InfluxQL // @@ -53,5 +52,46 @@ static async Task Main(string[] args) { Console.WriteLine("{0,-30}{1,-15}", row[1], row[2]); } + + // + // SQL Query all PointDataValues + // + const string sql2 = "select *, 'temperature' as measurement from temperature order by time desc limit 5"; + Console.WriteLine(); + Console.WriteLine("simple query to poins with measurement manualy specified"); + await foreach (var row in client.QueryPoints(query: sql2, queryType: QueryType.SQL)) + { + // Console.WriteLine(row.ToLineProtocol()); + continue; + } + + // + // SQL Query windows + // + const string sql3 = @" + SELECT + date_bin('5 minutes', ""time"") as time, + AVG(""value"") as avgvalue + FROM ""temperature"" + WHERE + ""time"" >= now() - interval '1 hour' + GROUP BY time + ORDER BY time DESC + limit 3 + ; + "; + Console.WriteLine(); + Console.WriteLine("more complex query to poins WITHOUT measurement manualy specified"); + await foreach (var row in client.QueryPoints(query: sql3, queryType: QueryType.SQL)) + { + // Console.WriteLine(row.ToLineProtocol()); + } + + Console.WriteLine(); + Console.WriteLine("simple InfluxQL query to points. InfluxQL sends measurement in query"); + await foreach (var row in client.QueryPoints(query: influxQL, queryType: QueryType.InfluxQL)) + { + // Console.WriteLine(row.ToLineProtocol()); + } } } \ No newline at end of file diff --git a/README.md b/README.md index 949e2e5..9da322c 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ to insert data, you can use code like this: // Write by Point // var point = PointData.Measurement("temperature") - .AddTag("location", "west") - .AddField("value", 55.15) + .SetTag("location", "west") + .SetField("value", 55.15) .SetTimestamp(DateTime.UtcNow.AddSeconds(-10)); await client.WritePointAsync(point: point);