You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2023/08/22 19:20:52 UTC
[ignite-3] branch main updated: IGNITE-19542 .NET: Add BinaryTupleIgniteTupleAdapter (#2479)
This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new d15b975b97 IGNITE-19542 .NET: Add BinaryTupleIgniteTupleAdapter (#2479)
d15b975b97 is described below
commit d15b975b97ed41465b9e8b7e855ba20bff2d7f81
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Tue Aug 22 22:20:46 2023 +0300
IGNITE-19542 .NET: Add BinaryTupleIgniteTupleAdapter (#2479)
When reading data from server, currently we unpack all elements of the incoming `BinaryTuple` into `IgniteTuple`. This is extra work and extra allocations. Instead, create an adapter from `BinaryTuple` to `IIgniteTuple` like `MutableRowTupleAdapter` does in Java.
**Benchmarks**
```
BEFORE
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
|------------------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|
| ReadObjectManual | 52.66 ns | 0.326 ns | 0.305 ns | 1.00 | 0.00 | 0.0003 | 80 B |
| ReadObject | 88.41 ns | 0.425 ns | 0.398 ns | 1.68 | 0.01 | 0.0002 | 80 B |
| ReadTuple | 263.26 ns | 2.822 ns | 2.502 ns | 5.00 | 0.06 | 0.0019 | 544 B |
| ReadTupleAndFields | 268.03 ns | 3.866 ns | 3.616 ns | 5.09 | 0.09 | 0.0019 | 544 B |
AFTER
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
|------------------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|
| ReadObjectManual | 53.23 ns | 0.320 ns | 0.268 ns | 1.00 | 0.00 | 0.0003 | 80 B |
| ReadObject | 88.01 ns | 0.484 ns | 0.453 ns | 1.65 | 0.01 | 0.0002 | 80 B |
| ReadTuple | 23.66 ns | 0.274 ns | 0.257 ns | 0.44 | 0.00 | 0.0004 | 120 B |
| ReadTupleAndFields | 136.34 ns | 2.014 ns | 1.884 ns | 2.56 | 0.04 | 0.0007 | 208 B |
```
After this change, `ReadTuple` simply performs a buffer copy, but does not deserialize individual fields. `ReadTupleAndFields` unpacks all fields, and it is still faster and allocates less than before.
---
.../dotnet/Apache.Ignite.Benchmarks/Program.cs | 4 +-
.../SerializerHandlerReadBenchmarks.cs | 55 +++-----
.../Table/BinaryTupleIgniteTupleAdapterTests.cs | 111 ++++++++++++++++
.../Apache.Ignite.Tests/Table/IgniteTupleTests.cs | 36 +++---
.../dotnet/Apache.Ignite.Tests/ToStringTests.cs | 3 +-
.../BinaryTuple/BinaryTupleIgniteTupleAdapter.cs | 143 +++++++++++++++++++++
.../Internal/Table/IgniteTupleCommon.cs} | 32 ++++-
.../Table/Serialization/TupleSerializerHandler.cs | 41 +++++-
.../dotnet/Apache.Ignite/Table/IIgniteTuple.cs | 18 +++
.../dotnet/Apache.Ignite/Table/IgniteTuple.cs | 53 ++------
10 files changed, 386 insertions(+), 110 deletions(-)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
index 0c256c1385..366dde3ad2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
@@ -18,9 +18,9 @@
namespace Apache.Ignite.Benchmarks;
using BenchmarkDotNet.Running;
-using Table;
+using Table.Serialization;
internal static class Program
{
- private static void Main() => BenchmarkRunner.Run<DataStreamerBenchmark>();
+ private static void Main() => BenchmarkRunner.Run<SerializerHandlerReadBenchmarks>();
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerReadBenchmarks.cs b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerReadBenchmarks.cs
index 5517e741c3..4ad9571b2a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerReadBenchmarks.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Table/Serialization/SerializerHandlerReadBenchmarks.cs
@@ -26,44 +26,14 @@ namespace Apache.Ignite.Benchmarks.Table.Serialization
/// <summary>
/// Benchmarks for <see cref="IRecordSerializerHandler{T}.Read"/> implementations.
///
- /// Results on Intel Core i7-9700K, .NET SDK 3.1.416, Ubuntu 20.04:
- /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
- /// |----------------- |-----------:|--------:|--------:|------:|--------:|-------:|----------:|
- /// | ReadObjectManual | 210.9 ns | 0.73 ns | 0.65 ns | 1.00 | 0.00 | 0.0126 | 80 B |
- /// | ReadObject | 257.5 ns | 1.41 ns | 1.25 ns | 1.22 | 0.01 | 0.0124 | 80 B |
- /// | ReadTuple | 561.0 ns | 3.09 ns | 2.89 ns | 2.66 | 0.01 | 0.0849 | 536 B |
- /// | ReadObjectOld | 1,020.9 ns | 9.05 ns | 8.47 ns | 4.84 | 0.05 | 0.0744 | 472 B |.
+ /// Results on i9-12900H, .NET SDK 6.0.405, Ubuntu 22.04:
///
- /// Results on i7-7700HQ, .NET SDK 6.0.400, Ubuntu 20.04:
- /// MsgPack (old)
- /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
- /// |----------------- |---------:|--------:|--------:|------:|--------:|-------:|----------:|
- /// | ReadObjectManual | 289.4 ns | 2.92 ns | 2.59 ns | 1.00 | 0.00 | 0.0024 | 80 B |
- /// | ReadObject | 364.3 ns | 3.28 ns | 3.07 ns | 1.26 | 0.02 | 0.0024 | 80 B |
- /// | ReadTuple | 755.4 ns | 2.82 ns | 2.35 ns | 2.61 | 0.03 | 0.0181 | 536 B |
- ///
- /// BinaryTuple (new)
- /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
- /// |----------------- |---------:|--------:|--------:|------:|--------:|-------:|----------:|
- /// | ReadObjectManual | 299.3 ns | 3.42 ns | 3.20 ns | 1.00 | 0.00 | 0.0024 | 80 B |
- /// | ReadObject | 382.9 ns | 2.49 ns | 2.21 ns | 1.28 | 0.02 | 0.0024 | 80 B |
- /// | ReadTuple | 769.0 ns | 6.06 ns | 5.37 ns | 2.57 | 0.04 | 0.0181 | 536 B |.
- ///
- /// Comparison of MessagePack library and our own implementation, i9-12900H, .NET SDK 6.0.405, Ubuntu 22.04:
- ///
- /// MessagePack 2.1.90 (old)
- /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
- /// |----------------- |---------:|--------:|--------:|------:|--------:|-------:|----------:|
- /// | ReadObjectManual | 100.3 ns | 0.46 ns | 0.41 ns | 1.00 | 0.00 | 0.0002 | 80 B |
- /// | ReadObject | 142.3 ns | 0.35 ns | 0.31 ns | 1.42 | 0.01 | 0.0002 | 80 B |
- /// | ReadTuple | 266.8 ns | 2.52 ns | 2.35 ns | 2.66 | 0.03 | 0.0019 | 544 B |.
- ///
- /// Custom MsgPackReader (new)
- /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
- /// |----------------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|
- /// | ReadObjectManual | 38.30 ns | 0.265 ns | 0.247 ns | 1.00 | 0.00 | 0.0003 | 80 B |
- /// | ReadObject | 80.51 ns | 0.158 ns | 0.124 ns | 2.10 | 0.01 | 0.0002 | 80 B |
- /// | ReadTuple | 208.63 ns | 0.654 ns | 0.611 ns | 5.45 | 0.04 | 0.0019 | 544 B |.
+ /// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated |
+ /// |------------------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|
+ /// | ReadObjectManual | 53.23 ns | 0.320 ns | 0.268 ns | 1.00 | 0.00 | 0.0003 | 80 B |
+ /// | ReadObject | 88.01 ns | 0.484 ns | 0.453 ns | 1.65 | 0.01 | 0.0002 | 80 B |
+ /// | ReadTuple | 23.66 ns | 0.274 ns | 0.257 ns | 0.44 | 0.00 | 0.0004 | 120 B |
+ /// | ReadTupleAndFields | 136.34 ns | 2.014 ns | 1.884 ns | 2.56 | 0.04 | 0.0007 | 208 B |.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Benchmarks.")]
[MemoryDiagnoser]
@@ -102,5 +72,16 @@ namespace Apache.Ignite.Benchmarks.Table.Serialization
Consumer.Consume(res);
}
+
+ [Benchmark]
+ public void ReadTupleAndFields()
+ {
+ var reader = new MsgPackReader(SerializedData);
+ var res = TupleSerializerHandler.Instance.Read(ref reader, Schema);
+
+ Consumer.Consume(res[0]);
+ Consumer.Consume(res[1]);
+ Consumer.Consume(res[2]);
+ }
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/BinaryTupleIgniteTupleAdapterTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/BinaryTupleIgniteTupleAdapterTests.cs
new file mode 100644
index 0000000000..cd6bb62f3e
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/BinaryTupleIgniteTupleAdapterTests.cs
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Tests.Table;
+
+using System;
+using System.Collections.Generic;
+using Ignite.Sql;
+using Ignite.Table;
+using Internal.Proto.BinaryTuple;
+using Internal.Table;
+using NUnit.Framework;
+
+/// <summary>
+/// Tests for <see cref="BinaryTupleIgniteTupleAdapter"/>. Ensures consistency with <see cref="IgniteTuple"/>.
+/// </summary>
+[TestFixture]
+public class BinaryTupleIgniteTupleAdapterTests : IgniteTupleTests
+{
+ [Test]
+ public void TestUpdate()
+ {
+ var tuple = CreateTuple(new IgniteTuple
+ {
+ ["A"] = 1,
+ ["B"] = "2",
+ ["C"] = 3L,
+ ["D"] = null
+ });
+
+ Assert.AreEqual(4, tuple.FieldCount);
+
+ Assert.AreEqual(1, tuple["A"]);
+ Assert.AreEqual("2", tuple["B"]);
+ Assert.AreEqual(3L, tuple["C"]);
+ Assert.IsNull(tuple["D"]);
+
+ Assert.IsNull(tuple.GetFieldValue<object>("_tuple"));
+ Assert.IsNotNull(tuple.GetFieldValue<object>("_schema"));
+
+ tuple["A"] = 11;
+
+ Assert.AreEqual(4, tuple.FieldCount);
+
+ Assert.AreEqual(11, tuple["A"]);
+ Assert.AreEqual("2", tuple["B"]);
+ Assert.AreEqual(3L, tuple["C"]);
+ Assert.IsNull(tuple["D"]);
+
+ Assert.IsNotNull(tuple.GetFieldValue<object>("_tuple"));
+ Assert.IsNull(tuple.GetFieldValue<object>("_schema"));
+ }
+
+ protected override string GetShortClassName() => nameof(BinaryTupleIgniteTupleAdapter);
+
+ protected override IIgniteTuple CreateTuple(IIgniteTuple source)
+ {
+ var cols = new List<Column>();
+ var builder = new BinaryTupleBuilder(source.FieldCount);
+
+ for (var i = 0; i < source.FieldCount; i++)
+ {
+ var name = source.GetName(i);
+ var val = source[i]!;
+ var type = GetColumnType(val);
+ var col = new Column(name, type, true, false, 0, i, 0, 0);
+
+ cols.Add(col);
+ builder.AppendObject(val, type);
+ }
+
+ var buf = builder.Build();
+ var schema = new Schema(0, 0, 0, 0, cols);
+
+ return new BinaryTupleIgniteTupleAdapter(buf, schema, cols.Count);
+
+ static ColumnType GetColumnType(object? obj)
+ {
+ if (obj == null || obj is string)
+ {
+ return ColumnType.String;
+ }
+
+ if (obj is int)
+ {
+ return ColumnType.Int32;
+ }
+
+ if (obj is long)
+ {
+ return ColumnType.Int64;
+ }
+
+ throw new NotSupportedException("Unsupported type: " + obj.GetType());
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs
index a2eb5f6157..40ed2d7149 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs
@@ -30,7 +30,7 @@ namespace Apache.Ignite.Tests.Table
[Test]
public void TestCreateUpdateRead()
{
- IIgniteTuple tuple = new IgniteTuple();
+ IIgniteTuple tuple = CreateTuple(new IgniteTuple());
Assert.AreEqual(0, tuple.FieldCount);
tuple["foo"] = 1;
@@ -68,7 +68,7 @@ namespace Apache.Ignite.Tests.Table
[Test]
public void TestGetNullOrEmptyNameThrowsException()
{
- var tuple = new IgniteTuple { ["Foo"] = 1 };
+ var tuple = CreateTuple(new IgniteTuple { ["Foo"] = 1 });
var ex = Assert.Throws<ArgumentException>(() => tuple.GetOrdinal(string.Empty));
Assert.AreEqual("Column name can not be null or empty.", ex!.Message);
@@ -92,46 +92,44 @@ namespace Apache.Ignite.Tests.Table
[Test]
public void TestGetNonExistingNameThrowsException()
{
- var tuple = new IgniteTuple { ["Foo"] = 1 };
+ var tuple = CreateTuple(new IgniteTuple { ["Foo"] = 1 });
- var ex = Assert.Throws<KeyNotFoundException>(() =>
- {
- var unused = tuple["bar"];
- });
+ var ex = Assert.Throws<KeyNotFoundException>(() => { _ = tuple["bar"]; });
Assert.AreEqual("The given key 'BAR' was not present in the dictionary.", ex!.Message);
}
[Test]
public void TestToStringEmpty()
{
- Assert.AreEqual("IgniteTuple { }", new IgniteTuple().ToString());
+ var tuple = CreateTuple(new IgniteTuple());
+ Assert.AreEqual(GetShortClassName() + " { }", tuple.ToString());
}
[Test]
public void TestToStringOneField()
{
- var tuple = new IgniteTuple { ["foo"] = 1 };
- Assert.AreEqual("IgniteTuple { FOO = 1 }", tuple.ToString());
+ var tuple = CreateTuple(new IgniteTuple { ["foo"] = 1 });
+ Assert.AreEqual(GetShortClassName() + " { FOO = 1 }", tuple.ToString());
}
[Test]
public void TestToStringTwoFields()
{
- var tuple = new IgniteTuple
+ var tuple = CreateTuple(new IgniteTuple
{
["foo"] = 1,
["b"] = "abcd"
- };
+ });
- Assert.AreEqual("IgniteTuple { FOO = 1, B = abcd }", tuple.ToString());
+ Assert.AreEqual(GetShortClassName() + " { FOO = 1, B = abcd }", tuple.ToString());
}
[Test]
public void TestEquality()
{
- var t1 = new IgniteTuple(2) { ["k"] = 1, ["v"] = "2" };
- var t2 = new IgniteTuple(3) { ["k"] = 1, ["v"] = "2" };
- var t3 = new IgniteTuple(4) { ["k"] = 1, ["v"] = null };
+ var t1 = CreateTuple(new IgniteTuple(2) { ["k"] = 1, ["v"] = "2" });
+ var t2 = CreateTuple(new IgniteTuple(3) { ["k"] = 1, ["v"] = "2" });
+ var t3 = CreateTuple(new IgniteTuple(4) { ["k"] = 1, ["v"] = null });
Assert.AreEqual(t1, t2);
Assert.AreEqual(t2, t1);
@@ -147,11 +145,15 @@ namespace Apache.Ignite.Tests.Table
[Test]
public void TestCustomTupleEquality()
{
- var tuple = new IgniteTuple { ["key"] = 42L, ["val"] = "Val1" };
+ var tuple = CreateTuple(new IgniteTuple { ["key"] = 42L, ["val"] = "Val1" });
var customTuple = new CustomTestIgniteTuple();
Assert.IsTrue(IIgniteTuple.Equals(tuple, customTuple));
Assert.AreEqual(IIgniteTuple.GetHashCode(tuple), IIgniteTuple.GetHashCode(customTuple));
}
+
+ protected virtual string GetShortClassName() => nameof(IgniteTuple);
+
+ protected virtual IIgniteTuple CreateTuple(IIgniteTuple source) => source;
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/ToStringTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/ToStringTests.cs
index 847b934132..8194e2bc80 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/ToStringTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ToStringTests.cs
@@ -52,7 +52,8 @@ public class ToStringTests
var code = File.ReadAllText(path);
if (code.Contains("new IgniteToStringBuilder(", StringComparison.Ordinal) ||
- code.Contains("IgniteToStringBuilder.Build(", StringComparison.Ordinal))
+ code.Contains("IgniteToStringBuilder.Build(", StringComparison.Ordinal) ||
+ code.Contains("IIgniteTuple.ToString(this)", StringComparison.Ordinal))
{
continue;
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleIgniteTupleAdapter.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleIgniteTupleAdapter.cs
new file mode 100644
index 0000000000..2e4230be28
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleIgniteTupleAdapter.cs
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Internal.Proto.BinaryTuple;
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Ignite.Table;
+using Table;
+using Table.Serialization;
+
+/// <summary>
+/// Adapts <see cref="BinaryTuple"/> to <see cref="IIgniteTuple"/>, so that we can avoid extra copying and allocations when
+/// reading data from the server.
+/// </summary>
+internal sealed class BinaryTupleIgniteTupleAdapter : IIgniteTuple, IEquatable<BinaryTupleIgniteTupleAdapter>, IEquatable<IIgniteTuple>
+{
+ private readonly int _schemaFieldCount; // Key-only tuples have less fields than schema.
+
+ private Memory<byte> _data;
+
+ private Schema? _schema;
+
+ private Dictionary<string, int>? _indexes;
+
+ private IgniteTuple? _tuple;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BinaryTupleIgniteTupleAdapter"/> class.
+ /// </summary>
+ /// <param name="data">Binary tuple data.</param>
+ /// <param name="schema">Schema.</param>
+ /// <param name="fieldCount">Field count.</param>
+ public BinaryTupleIgniteTupleAdapter(Memory<byte> data, Schema schema, int fieldCount)
+ {
+ Debug.Assert(fieldCount <= schema.Columns.Count, "fieldCount <= schema.Columns.Count");
+
+ _data = data;
+ _schema = schema;
+ _schemaFieldCount = fieldCount;
+ }
+
+ /// <inheritdoc/>
+ public int FieldCount => _tuple?.FieldCount ?? _schemaFieldCount;
+
+ /// <inheritdoc/>
+ public object? this[int ordinal]
+ {
+ get => _tuple != null
+ ? _tuple[ordinal]
+ : TupleSerializerHandler.ReadObject(_data.Span, _schema!, _schemaFieldCount, ordinal);
+
+ set => InitTuple()[ordinal] = value;
+ }
+
+ /// <inheritdoc/>
+ public object? this[string name]
+ {
+ get => GetOrdinal(name) switch
+ {
+ var ordinal and >= 0 => this[ordinal],
+ _ => throw new KeyNotFoundException(
+ $"The given key '{IgniteTupleCommon.ParseColumnName(name)}' was not present in the dictionary.")
+ };
+ set => InitTuple()[name] = value;
+ }
+
+ /// <inheritdoc/>
+ public string GetName(int ordinal) => _schema != null
+ ? _schema.Columns[ordinal].Name
+ : _tuple!.GetName(ordinal);
+
+ /// <inheritdoc/>
+ public int GetOrdinal(string name)
+ {
+ if (_tuple != null)
+ {
+ return _tuple.GetOrdinal(name);
+ }
+
+ if (_indexes == null)
+ {
+ _indexes = new Dictionary<string, int>(_schema!.Columns.Count);
+
+ for (var i = 0; i < _schema.Columns.Count; i++)
+ {
+ _indexes[IgniteTupleCommon.ParseColumnName(_schema.Columns[i].Name)] = i;
+ }
+ }
+
+ return _indexes.TryGetValue(IgniteTupleCommon.ParseColumnName(name), out var index) ? index : -1;
+ }
+
+ /// <inheritdoc/>
+ public override string ToString() => IIgniteTuple.ToString(this);
+
+ /// <inheritdoc />
+ public bool Equals(BinaryTupleIgniteTupleAdapter? other) => IIgniteTuple.Equals(this, other);
+
+ /// <inheritdoc />
+ public bool Equals(IIgniteTuple? other) => IIgniteTuple.Equals(this, other);
+
+ /// <inheritdoc />
+ public override bool Equals(object? obj) => obj is IIgniteTuple other && Equals(other);
+
+ /// <inheritdoc />
+ public override int GetHashCode() => IIgniteTuple.GetHashCode(this);
+
+ private IIgniteTuple InitTuple()
+ {
+ if (_tuple != null)
+ {
+ return _tuple;
+ }
+
+ Debug.Assert(_schema != null, "_schema != null");
+
+ // Copy data to a mutable IgniteTuple.
+ _tuple = TupleSerializerHandler.ReadTuple(_data.Span, _schema, _schemaFieldCount);
+
+ // Release schema and data.
+ _schema = default;
+ _indexes = default;
+ _data = default;
+
+ return _tuple;
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/IgniteTupleCommon.cs
similarity index 52%
copy from modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
copy to modules/platforms/dotnet/Apache.Ignite/Internal/Table/IgniteTupleCommon.cs
index 0c256c1385..5cdacd83ef 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Benchmarks/Program.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/IgniteTupleCommon.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
@@ -15,12 +15,32 @@
* limitations under the License.
*/
-namespace Apache.Ignite.Benchmarks;
+namespace Apache.Ignite.Internal.Table;
-using BenchmarkDotNet.Running;
-using Table;
+using System;
-internal static class Program
+/// <summary>
+/// Common tuple utilities.
+/// </summary>
+internal static class IgniteTupleCommon
{
- private static void Main() => BenchmarkRunner.Run<DataStreamerBenchmark>();
+ /// <summary>
+ /// Parses column name. Removes quotes and converts to upper case.
+ /// </summary>
+ /// <param name="name">Name.</param>
+ /// <returns>Parsed name.</returns>
+ public static string ParseColumnName(string name)
+ {
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentException("Column name can not be null or empty.");
+ }
+
+ if (name.Length > 2 && name.StartsWith('"') && name.EndsWith('"'))
+ {
+ return name.Substring(1, name.Length - 2);
+ }
+
+ return name.ToUpperInvariant();
+ }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
index 8158148867..b919f39a16 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
@@ -19,6 +19,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
{
using System;
using System.Collections.Generic;
+ using System.Diagnostics;
using System.Linq;
using Common;
using Ignite.Table;
@@ -43,13 +44,20 @@ namespace Apache.Ignite.Internal.Table.Serialization
// No-op.
}
- /// <inheritdoc/>
- public IIgniteTuple Read(ref MsgPackReader reader, Schema schema, bool keyOnly = false)
+ /// <summary>
+ /// Reads tuple from the buffer.
+ /// </summary>
+ /// <param name="buf">Buffer.</param>
+ /// <param name="schema">Schema.</param>
+ /// <param name="count">Column count to read.</param>
+ /// <returns>Tuple.</returns>
+ public static IgniteTuple ReadTuple(ReadOnlySpan<byte> buf, Schema schema, int count)
{
- var columns = schema.Columns;
- var count = keyOnly ? schema.KeyColumnCount : columns.Count;
+ Debug.Assert(count <= schema.Columns.Count, "count <= schema.Columns.Count");
+
var tuple = new IgniteTuple(count);
- var tupleReader = new BinaryTupleReader(reader.ReadBinary(), count);
+ var tupleReader = new BinaryTupleReader(buf, count);
+ var columns = schema.Columns;
for (var index = 0; index < count; index++)
{
@@ -60,6 +68,29 @@ namespace Apache.Ignite.Internal.Table.Serialization
return tuple;
}
+ /// <summary>
+ /// Reads single column from the binary tuple.
+ /// </summary>
+ /// <param name="buf">Binary tuple buffer.</param>
+ /// <param name="schema">Schema.</param>
+ /// <param name="count">Column count.</param>
+ /// <param name="index">Column index.</param>
+ /// <returns>Column value.</returns>
+ public static object? ReadObject(ReadOnlySpan<byte> buf, Schema schema, int count, int index)
+ {
+ var tupleReader = new BinaryTupleReader(buf, count);
+ var column = schema.Columns[index];
+
+ return tupleReader.GetObject(index, column.Type, column.Scale);
+ }
+
+ /// <inheritdoc/>
+ public IIgniteTuple Read(ref MsgPackReader reader, Schema schema, bool keyOnly = false) =>
+ new BinaryTupleIgniteTupleAdapter(
+ data: reader.ReadBinary().ToArray(),
+ schema: schema,
+ fieldCount: keyOnly ? schema.KeyColumnCount : schema.Columns.Count);
+
/// <inheritdoc/>
public void Write(ref BinaryTupleBuilder tupleBuilder, IIgniteTuple record, Schema schema, int columnCount, Span<byte> noValueSet)
{
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/IIgniteTuple.cs b/modules/platforms/dotnet/Apache.Ignite/Table/IIgniteTuple.cs
index fcb338101e..44fff1b2cd 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/IIgniteTuple.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/IIgniteTuple.cs
@@ -128,5 +128,23 @@ namespace Apache.Ignite.Table
return true;
}
+
+ /// <summary>
+ /// Converts tuple to string.
+ /// </summary>
+ /// <param name="tuple">Tuple.</param>
+ /// <returns>String representation.</returns>
+ public static string ToString(IIgniteTuple tuple)
+ {
+ IgniteArgumentCheck.NotNull(tuple, nameof(tuple));
+ var builder = new IgniteToStringBuilder(tuple.GetType());
+
+ for (var i = 0; i < tuple.FieldCount; i++)
+ {
+ builder.Append(tuple[i], tuple.GetName(i));
+ }
+
+ return builder.Build();
+ }
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs b/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs
index 1ba36e86c3..937f60a24f 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs
@@ -20,12 +20,12 @@ namespace Apache.Ignite.Table
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
- using Internal.Common;
+ using Internal.Table;
/// <summary>
/// Ignite tuple.
/// </summary>
- public sealed class IgniteTuple : IIgniteTuple, IEquatable<IgniteTuple>
+ public sealed class IgniteTuple : IIgniteTuple, IEquatable<IgniteTuple>, IEquatable<IIgniteTuple>
{
/** Key-value pairs. */
[SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Private.")]
@@ -57,10 +57,10 @@ namespace Apache.Ignite.Table
/// <inheritdoc/>
public object? this[string name]
{
- get => _pairs[_indexes[ParseName(name)]].Value;
+ get => _pairs[_indexes[IgniteTupleCommon.ParseColumnName(name)]].Value;
set
{
- name = ParseName(name);
+ name = IgniteTupleCommon.ParseColumnName(name);
var pair = (name, value);
@@ -81,52 +81,21 @@ namespace Apache.Ignite.Table
public string GetName(int ordinal) => _pairs[ordinal].Key;
/// <inheritdoc/>
- public int GetOrdinal(string name) => _indexes.TryGetValue(ParseName(name), out var index) ? index : -1;
+ public int GetOrdinal(string name) => _indexes.TryGetValue(IgniteTupleCommon.ParseColumnName(name), out var index) ? index : -1;
/// <inheritdoc />
- public override string ToString()
- {
- var builder = new IgniteToStringBuilder(GetType());
-
- for (var i = 0; i < FieldCount; i++)
- {
- builder.Append(this[i], GetName(i));
- }
-
- return builder.Build();
- }
+ public override string ToString() => IIgniteTuple.ToString(this);
/// <inheritdoc />
- public bool Equals(IgniteTuple? other)
- {
- return IIgniteTuple.Equals(this, other);
- }
+ public bool Equals(IgniteTuple? other) => IIgniteTuple.Equals(this, other);
/// <inheritdoc />
- public override bool Equals(object? obj)
- {
- return obj is IgniteTuple other && Equals(other);
- }
+ public bool Equals(IIgniteTuple? other) => IIgniteTuple.Equals(this, other);
/// <inheritdoc />
- public override int GetHashCode()
- {
- return IIgniteTuple.GetHashCode(this);
- }
-
- private static string ParseName(string name)
- {
- if (string.IsNullOrEmpty(name))
- {
- throw new ArgumentException("Column name can not be null or empty.");
- }
+ public override bool Equals(object? obj) => obj is IIgniteTuple other && Equals(other);
- if (name.Length > 2 && name.StartsWith('"') && name.EndsWith('"'))
- {
- return name.Substring(1, name.Length - 2);
- }
-
- return name.ToUpperInvariant();
- }
+ /// <inheritdoc />
+ public override int GetHashCode() => IIgniteTuple.GetHashCode(this);
}
}