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 2022/10/17 07:34:20 UTC
[ignite-3] branch main updated: IGNITE-16226 .NET: Add KeyValueBinaryView (#1212)
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 5ff6f1ecb9 IGNITE-16226 .NET: Add KeyValueBinaryView (#1212)
5ff6f1ecb9 is described below
commit 5ff6f1ecb981b379f591b922759c9676e1e6f38a
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Mon Oct 17 10:34:15 2022 +0300
IGNITE-16226 .NET: Add KeyValueBinaryView (#1212)
Added `IKeyValueView<IIgniteTuple, IIgniteTuple> KeyValueBinaryView` to `ITable`.
KV view is just a different representation of the `RecordView`, where every record is split into two parts.
To reuse `RecordView` code, we wrap key and value into `KvPair` at the API entry point, and unwrap back only at the serializer level.
---
.../dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs | 9 +-
.../dotnet/Apache.Ignite.Tests/OptionTests.cs | 4 +-
.../Table/KeyValueViewBinaryTests.cs | 291 +++++++++++++++++++++
.../Table/RecordViewBinaryTests.cs | 2 +-
.../Table/RecordViewDefaultMappingTest.cs | 3 +-
.../Table/RecordViewPrimitiveTests.cs | 4 +-
.../dotnet/Apache.Ignite/ClientOperationType.cs | 5 +
.../dotnet/Apache.Ignite/Compute/ICompute.cs | 3 +-
.../Apache.Ignite/Internal/Compute/Compute.cs | 4 +-
.../Apache.Ignite/Internal/Proto/ClientOp.cs | 3 +
.../Internal/Proto/ClientOpExtensions.cs | 1 +
.../Apache.Ignite/Internal/Table/KeyValueView.cs | 166 ++++++++++++
.../Apache.Ignite/Internal/Table/RecordView.cs | 141 +++++++---
.../dotnet/Apache.Ignite/Internal/Table/Schema.cs | 8 +-
.../Table/{Schema.cs => Serialization/KvPair.cs} | 28 +-
.../Table/Serialization/ObjectSerializerHandler.cs | 2 +-
.../Table/Serialization/RecordSerializer.cs | 40 ++-
.../Serialization/TuplePairSerializerHandler.cs | 114 ++++++++
.../Table/Serialization/TupleSerializerHandler.cs | 2 +-
.../dotnet/Apache.Ignite/Internal/Table/Table.cs | 16 +-
modules/platforms/dotnet/Apache.Ignite/Option.cs | 21 +-
.../dotnet/Apache.Ignite/RetryReadPolicy.cs | 1 +
.../dotnet/Apache.Ignite/Table/IKeyValueView.cs | 203 ++++++++++++++
.../dotnet/Apache.Ignite/Table/IRecordView.cs | 9 +-
.../platforms/dotnet/Apache.Ignite/Table/ITable.cs | 8 +-
25 files changed, 1002 insertions(+), 86 deletions(-)
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
index 1ee92bc602..5e4f997359 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteTestsBase.cs
@@ -58,6 +58,8 @@ namespace Apache.Ignite.Tests
protected IRecordView<IIgniteTuple> TupleView { get; private set; } = null!;
+ protected IKeyValueView<IIgniteTuple, IIgniteTuple> KvView => Table.KeyValueBinaryView;
+
protected IRecordView<Poco> PocoView { get; private set; } = null!;
[OneTimeSetUp]
@@ -89,8 +91,11 @@ namespace Apache.Ignite.Tests
Assert.AreEqual(_eventListener.BuffersReturned, _eventListener.BuffersRented);
}
- protected static IIgniteTuple GetTuple(long id, string? val = null) =>
- new IgniteTuple { [KeyCol] = id, [ValCol] = val };
+ protected static IIgniteTuple GetTuple(long id) => new IgniteTuple { [KeyCol] = id };
+
+ protected static IIgniteTuple GetTuple(long id, string? val) => new IgniteTuple { [KeyCol] = id, [ValCol] = val };
+
+ protected static IIgniteTuple GetTuple(string? val) => new IgniteTuple { [ValCol] = val };
protected static Poco GetPoco(long id, string? val = null) => new() {Key = id, Val = val};
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/OptionTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/OptionTests.cs
index 955b8f8414..14b9c1d458 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/OptionTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/OptionTests.cs
@@ -49,7 +49,7 @@ public sealed class OptionTests
[Test]
public void TestEquality()
{
- Assert.AreEqual(Option.Some(123), (Option<int>)123);
+ Assert.AreEqual(Option.Some(123), Option.Some(123));
Assert.AreNotEqual(Option.Some(123), Option.Some(124));
}
@@ -102,4 +102,4 @@ public sealed class OptionTests
Assert.AreEqual("Option { HasValue = True, Value = 123 }", Option.Some(123).ToString());
Assert.AreEqual("Option { HasValue = True, Value = Foo }", Option.Some("Foo").ToString());
}
-}
\ No newline at end of file
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewBinaryTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewBinaryTests.cs
new file mode 100644
index 0000000000..de36205632
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/KeyValueViewBinaryTests.cs
@@ -0,0 +1,291 @@
+/*
+ * 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 System.Linq;
+using System.Threading.Tasks;
+using Ignite.Table;
+using NUnit.Framework;
+
+/// <summary>
+/// Tests for key-value tuple view.
+/// </summary>
+public class KeyValueViewBinaryTests : IgniteTestsBase
+{
+ [TearDown]
+ public async Task CleanTable()
+ {
+ await TupleView.DeleteAllAsync(null, Enumerable.Range(-1, 12).Select(x => GetTuple(x)));
+ }
+
+ [Test]
+ public async Task TestPutGet()
+ {
+ await KvView.PutAsync(null, GetTuple(1L), GetTuple("val"));
+
+ (IIgniteTuple res, _) = await KvView.GetAsync(null, GetTuple(1L));
+
+ Assert.AreEqual("val", res[0]);
+ Assert.AreEqual("val", res[ValCol]);
+ }
+
+ [Test]
+ public async Task TestGetNonExistentKeyReturnsEmptyOption()
+ {
+ (IIgniteTuple res, bool hasRes) = await KvView.GetAsync(null, GetTuple(-111L));
+
+ Assert.IsFalse(hasRes);
+ Assert.IsNull(res);
+ }
+
+ [Test]
+ public async Task TestGetAll()
+ {
+ await KvView.PutAsync(null, GetTuple(7L), GetTuple("val1"));
+ await KvView.PutAsync(null, GetTuple(8L), GetTuple("val2"));
+
+ IDictionary<IIgniteTuple, IIgniteTuple> res = await KvView.GetAllAsync(null, Enumerable.Range(-1, 100).Select(x => GetTuple(x)).ToList());
+ IDictionary<IIgniteTuple, IIgniteTuple> resEmpty = await KvView.GetAllAsync(null, Array.Empty<IIgniteTuple>());
+
+ Assert.AreEqual(2, res.Count);
+ Assert.AreEqual("val1", res[GetTuple(7L)][0]);
+ Assert.AreEqual("val2", res[GetTuple(8L)][0]);
+
+ Assert.AreEqual(0, resEmpty.Count);
+ }
+
+ [Test]
+ public void TestGetAllWithNullKeyThrowsArgumentException()
+ {
+ var ex = Assert.ThrowsAsync<ArgumentNullException>(async () =>
+ await KvView.GetAllAsync(null, new[] { GetTuple(1L), null! }));
+
+ Assert.AreEqual("Value cannot be null. (Parameter 'key')", ex!.Message);
+ }
+
+ [Test]
+ public void TestPutNullThrowsArgumentException()
+ {
+ var keyEx = Assert.ThrowsAsync<ArgumentNullException>(async () => await KvView.PutAsync(null, null!, null!));
+ Assert.AreEqual("Value cannot be null. (Parameter 'key')", keyEx!.Message);
+
+ var valEx = Assert.ThrowsAsync<ArgumentNullException>(async () => await KvView.PutAsync(null, GetTuple(1L), null!));
+ Assert.AreEqual("Value cannot be null. (Parameter 'val')", valEx!.Message);
+ }
+
+ [Test]
+ public async Task TestContains()
+ {
+ await KvView.PutAsync(null, GetTuple(7L), GetTuple("val1"));
+
+ bool res1 = await KvView.ContainsAsync(null, GetTuple(7L));
+ bool res2 = await KvView.ContainsAsync(null, GetTuple(8L));
+
+ Assert.IsTrue(res1);
+ Assert.IsFalse(res2);
+ }
+
+ [Test]
+ public async Task TestPutAll()
+ {
+ await KvView.PutAllAsync(null, new Dictionary<IIgniteTuple, IIgniteTuple>());
+ await KvView.PutAllAsync(
+ null,
+ Enumerable.Range(-1, 7).Select(x => new KeyValuePair<IIgniteTuple, IIgniteTuple>(GetTuple(x), GetTuple("v" + x))));
+
+ IDictionary<IIgniteTuple, IIgniteTuple> res = await KvView.GetAllAsync(null, Enumerable.Range(-10, 20).Select(x => GetTuple(x)));
+
+ Assert.AreEqual(7, res.Count);
+
+ for (int i = -1; i < 6; i++)
+ {
+ IIgniteTuple val = res[GetTuple(i)];
+ Assert.AreEqual("v" + i, val[ValCol]);
+ }
+ }
+
+ [Test]
+ public async Task TestGetAndPut()
+ {
+ Option<IIgniteTuple> res1 = await KvView.GetAndPutAsync(null, GetTuple(1), GetTuple("1"));
+ Option<IIgniteTuple> res2 = await KvView.GetAndPutAsync(null, GetTuple(1), GetTuple("2"));
+ Option<IIgniteTuple> res3 = await KvView.GetAsync(null, GetTuple(1));
+
+ Assert.IsFalse(res1.HasValue);
+ Assert.IsTrue(res2.HasValue);
+ Assert.IsTrue(res3.HasValue);
+
+ Assert.AreEqual("1", res2.Value[0]);
+ Assert.AreEqual("2", res3.Value[0]);
+ }
+
+ [Test]
+ public async Task TestPutIfAbsent()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ bool res1 = await KvView.PutIfAbsentAsync(null, GetTuple(1), GetTuple("11"));
+ Option<IIgniteTuple> res2 = await KvView.GetAsync(null, GetTuple(1));
+
+ bool res3 = await KvView.PutIfAbsentAsync(null, GetTuple(2), GetTuple("2"));
+ Option<IIgniteTuple> res4 = await KvView.GetAsync(null, GetTuple(2));
+
+ Assert.IsFalse(res1);
+ Assert.AreEqual("1", res2.Value[0]);
+
+ Assert.IsTrue(res3);
+ Assert.AreEqual("2", res4.Value[0]);
+ }
+
+ [Test]
+ public async Task TestRemove()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ bool res1 = await KvView.RemoveAsync(null, GetTuple(1));
+ bool res2 = await KvView.RemoveAsync(null, GetTuple(2));
+ bool res3 = await KvView.ContainsAsync(null, GetTuple(1));
+
+ Assert.IsTrue(res1);
+ Assert.IsFalse(res2);
+ Assert.IsFalse(res3);
+ }
+
+ [Test]
+ public async Task TestRemoveExact()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ bool res1 = await KvView.RemoveAsync(null, GetTuple(1), GetTuple("111"));
+ bool res2 = await KvView.RemoveAsync(null, GetTuple(1), GetTuple("1"));
+ bool res3 = await KvView.ContainsAsync(null, GetTuple(1));
+
+ Assert.IsFalse(res1);
+ Assert.IsTrue(res2);
+ Assert.IsFalse(res3);
+ }
+
+ [Test]
+ public async Task TestRemoveAll()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ IList<IIgniteTuple> res1 = await KvView.RemoveAllAsync(null, Enumerable.Range(-1, 8).Select(x => GetTuple(x, "foo")));
+ bool res2 = await KvView.ContainsAsync(null, GetTuple(1));
+
+ Assert.AreEqual(new[] { -1, 0, 2, 3, 4, 5, 6 }, res1.Select(x => x[0]).OrderBy(x => x));
+ Assert.IsFalse(res2);
+ }
+
+ [Test]
+ public async Task TestRemoveAllExact()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ IList<IIgniteTuple> res1 = await KvView.RemoveAllAsync(
+ null,
+ Enumerable.Range(-1, 8).Select(x => new KeyValuePair<IIgniteTuple, IIgniteTuple>(GetTuple(x), GetTuple(x.ToString()))));
+
+ bool res2 = await KvView.ContainsAsync(null, GetTuple(1));
+
+ Assert.AreEqual(new[] { -1, 0, 2, 3, 4, 5, 6 }, res1.Select(x => x[0]).OrderBy(x => x));
+ Assert.IsFalse(res2);
+ }
+
+ [Test]
+ public async Task TestGetAndRemove()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ (IIgniteTuple val1, bool hasVal1) = await KvView.GetAndRemoveAsync(null, GetTuple(1));
+ (IIgniteTuple val2, bool hasVal2) = await KvView.GetAndRemoveAsync(null, GetTuple(1));
+
+ Assert.IsTrue(hasVal1);
+ Assert.AreEqual("1", val1[0]);
+
+ Assert.IsFalse(hasVal2);
+ Assert.IsNull(val2);
+ }
+
+ [Test]
+ public async Task TestReplace()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ bool res1 = await KvView.ReplaceAsync(null, GetTuple(0), GetTuple("00"));
+ Option<IIgniteTuple> res2 = await KvView.GetAsync(null, GetTuple(0));
+
+ bool res3 = await KvView.ReplaceAsync(null, GetTuple(1), GetTuple("11"));
+ Option<IIgniteTuple> res4 = await KvView.GetAsync(null, GetTuple(1));
+
+ Assert.IsFalse(res1);
+ Assert.IsFalse(res2.HasValue);
+
+ Assert.IsTrue(res3);
+ Assert.IsTrue(res4.HasValue);
+ Assert.AreEqual("11", res4.Value[0]);
+ }
+
+ [Test]
+ public async Task TestReplaceExact()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ bool res1 = await KvView.ReplaceAsync(transaction: null, key: GetTuple(0), oldVal: GetTuple("0"), newVal: GetTuple("00"));
+ Option<IIgniteTuple> res2 = await KvView.GetAsync(null, GetTuple(0));
+
+ bool res3 = await KvView.ReplaceAsync(transaction: null, key: GetTuple(1), oldVal: GetTuple("1"), newVal: GetTuple("11"));
+ Option<IIgniteTuple> res4 = await KvView.GetAsync(null, GetTuple(1));
+
+ bool res5 = await KvView.ReplaceAsync(transaction: null, key: GetTuple(2), oldVal: GetTuple("1"), newVal: GetTuple("22"));
+ Option<IIgniteTuple> res6 = await KvView.GetAsync(null, GetTuple(1));
+
+ Assert.IsFalse(res1);
+ Assert.IsFalse(res2.HasValue);
+
+ Assert.IsTrue(res3);
+ Assert.IsTrue(res4.HasValue);
+ Assert.AreEqual("11", res4.Value[0]);
+
+ Assert.IsFalse(res5);
+ Assert.AreEqual("11", res6.Value[0]);
+ }
+
+ [Test]
+ public async Task TestGetAndReplace()
+ {
+ await KvView.PutAsync(null, GetTuple(1), GetTuple("1"));
+
+ Option<IIgniteTuple> res1 = await KvView.GetAndReplaceAsync(null, GetTuple(0), GetTuple("00"));
+ Option<IIgniteTuple> res2 = await KvView.GetAsync(null, GetTuple(0));
+
+ Option<IIgniteTuple> res3 = await KvView.GetAndReplaceAsync(null, GetTuple(1), GetTuple("11"));
+ Option<IIgniteTuple> res4 = await KvView.GetAsync(null, GetTuple(1));
+
+ Assert.IsFalse(res1.HasValue);
+ Assert.IsFalse(res2.HasValue);
+
+ Assert.IsTrue(res3.HasValue);
+ Assert.AreEqual("1", res3.Value[0]);
+
+ Assert.IsTrue(res4.HasValue);
+ Assert.AreEqual("11", res4.Value[0]);
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
index 5264aa42ff..553ca549be 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
@@ -35,7 +35,7 @@ namespace Apache.Ignite.Tests.Table
[TearDown]
public async Task CleanTable()
{
- await TupleView.DeleteAllAsync(null, Enumerable.Range(-1, 12).Select(x => GetTuple(x)));
+ await TupleView.DeleteAllAsync(null, Enumerable.Range(-1, 50).Select(x => GetTuple(x)));
}
[Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewDefaultMappingTest.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewDefaultMappingTest.cs
index 639584283d..39eb7dd43a 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewDefaultMappingTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewDefaultMappingTest.cs
@@ -74,7 +74,8 @@ namespace Apache.Ignite.Tests.Table
Assert.AreEqual("2", res.Val);
}
- private T Get<T>(T key) => Table.GetRecordView<T>().GetAsync(null, key).GetAwaiter().GetResult().Value;
+ private T Get<T>(T key)
+ where T : notnull => Table.GetRecordView<T>().GetAsync(null, key).GetAwaiter().GetResult().Value;
private class FieldsTest
{
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
index 97bfb330ef..ea7fe988e3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPrimitiveTests.cs
@@ -67,6 +67,7 @@ public class RecordViewPrimitiveTests : IgniteTestsBase
}
private static async Task TestKey<T>(T val, IRecordView<T> recordView)
+ where T : notnull
{
// Tests EmitWriter.
await recordView.UpsertAsync(null, val);
@@ -82,6 +83,7 @@ public class RecordViewPrimitiveTests : IgniteTestsBase
}
private async Task TestKey<T>(T val, string tableName)
+ where T : notnull
{
var table = await Client.Tables.GetTableAsync(tableName);
@@ -89,4 +91,4 @@ public class RecordViewPrimitiveTests : IgniteTestsBase
await TestKey(val, table!.GetRecordView<T>());
}
-}
\ No newline at end of file
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite/ClientOperationType.cs b/modules/platforms/dotnet/Apache.Ignite/ClientOperationType.cs
index fffd72639a..4d544cf5c9 100644
--- a/modules/platforms/dotnet/Apache.Ignite/ClientOperationType.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/ClientOperationType.cs
@@ -111,6 +111,11 @@ namespace Apache.Ignite
/// </summary>
TupleGetAndDelete,
+ /// <summary>
+ /// Contains key (<see cref="IKeyValueView{TK,TV}.ContainsAsync"/>).
+ /// </summary>
+ TupleContainsKey,
+
/// <summary>
/// Compute (<see cref="ICompute.ExecuteAsync{T}"/>, <see cref="ICompute.BroadcastAsync{T}"/>).
/// </summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite/Compute/ICompute.cs b/modules/platforms/dotnet/Apache.Ignite/Compute/ICompute.cs
index 7ab817b05f..4e37892e59 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Compute/ICompute.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Compute/ICompute.cs
@@ -58,7 +58,8 @@ namespace Apache.Ignite.Compute
/// <typeparam name="T">Job result type.</typeparam>
/// <typeparam name="TKey">Key type.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
- Task<T> ExecuteColocatedAsync<T, TKey>(string tableName, TKey key, string jobClassName, params object[] args);
+ Task<T> ExecuteColocatedAsync<T, TKey>(string tableName, TKey key, string jobClassName, params object[] args)
+ where TKey : notnull;
/// <summary>
/// Executes a compute job represented by the given class on all of the specified nodes.
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
index 24b0ada1d7..b3dc4a8b0f 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
@@ -76,7 +76,8 @@ namespace Apache.Ignite.Internal.Compute
.ConfigureAwait(false);
/// <inheritdoc/>
- public async Task<T> ExecuteColocatedAsync<T, TKey>(string tableName, TKey key, string jobClassName, params object[] args) =>
+ public async Task<T> ExecuteColocatedAsync<T, TKey>(string tableName, TKey key, string jobClassName, params object[] args)
+ where TKey : notnull =>
await ExecuteColocatedAsync<T, TKey>(
tableName,
key,
@@ -198,6 +199,7 @@ namespace Apache.Ignite.Internal.Compute
Func<Table, IRecordSerializerHandler<TKey>> serializerHandlerFunc,
string jobClassName,
params object[] args)
+ where TKey : notnull
{
// TODO: IGNITE-16990 - implement partition awareness.
IgniteArgumentCheck.NotNull(tableName, nameof(tableName));
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOp.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOp.cs
index bc79c32a90..86f85c6342 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOp.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOp.cs
@@ -79,6 +79,9 @@ namespace Apache.Ignite.Internal.Proto
/** Get and delete tuple. */
TupleGetAndDelete = 32,
+ /** Contains tuple. */
+ TupleContainsKey = 33,
+
/** Begin transaction. */
TxBegin = 43,
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOpExtensions.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOpExtensions.cs
index 95120369d5..a5e8961a1f 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOpExtensions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/ClientOpExtensions.cs
@@ -51,6 +51,7 @@ namespace Apache.Ignite.Internal.Proto
ClientOp.TupleDeleteExact => ClientOperationType.TupleDeleteExact,
ClientOp.TupleDeleteAllExact => ClientOperationType.TupleDeleteAllExact,
ClientOp.TupleGetAndDelete => ClientOperationType.TupleGetAndDelete,
+ ClientOp.TupleContainsKey => ClientOperationType.TupleContainsKey,
ClientOp.ComputeExecute => ClientOperationType.ComputeExecute,
ClientOp.ComputeExecuteColocated => ClientOperationType.ComputeExecute,
ClientOp.SqlExec => ClientOperationType.SqlExecute,
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/KeyValueView.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/KeyValueView.cs
new file mode 100644
index 0000000000..d45fc615d4
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/KeyValueView.cs
@@ -0,0 +1,166 @@
+/*
+ * 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.Table;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Apache.Ignite.Transactions;
+using Common;
+using Ignite.Table;
+using Serialization;
+
+/// <summary>
+/// Generic key-value view.
+/// </summary>
+/// <typeparam name="TK">Key type.</typeparam>
+/// <typeparam name="TV">Value type.</typeparam>
+internal sealed class KeyValueView<TK, TV> : IKeyValueView<TK, TV>
+ where TK : notnull
+ where TV : notnull
+{
+ /** Record view. */
+ private readonly RecordView<KvPair<TK, TV>> _recordView;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="KeyValueView{TK, TV}"/> class.
+ /// </summary>
+ /// <param name="recordView">Record view.</param>
+ public KeyValueView(RecordView<KvPair<TK, TV>> recordView)
+ {
+ _recordView = recordView;
+ }
+
+ /// <inheritdoc/>
+ public async Task<Option<TV>> GetAsync(ITransaction? transaction, TK key) =>
+ (await _recordView.GetAsync(transaction, ToKv(key))).Select(static x => x.Val);
+
+ /// <inheritdoc/>
+ public async Task<IDictionary<TK, TV>> GetAllAsync(ITransaction? transaction, IEnumerable<TK> keys) =>
+ await _recordView.GetAllAsync(
+ transaction,
+ keys.Select(static k => ToKv(k)),
+ count => new Dictionary<TK, TV>(count),
+ (dict, item) =>
+ {
+ var ((key, val), hasVal) = item;
+
+ if (hasVal)
+ {
+ dict[key] = val;
+ }
+ });
+
+ /// <inheritdoc/>
+ public async Task<bool> ContainsAsync(ITransaction? transaction, TK key) =>
+ await _recordView.ContainsKey(transaction, ToKv(key));
+
+ /// <inheritdoc/>
+ public async Task PutAsync(ITransaction? transaction, TK key, TV val) =>
+ await _recordView.UpsertAsync(transaction, ToKv(key, val));
+
+ /// <inheritdoc/>
+ public async Task PutAllAsync(ITransaction? transaction, IEnumerable<KeyValuePair<TK, TV>> pairs) =>
+ await _recordView.UpsertAllAsync(transaction, pairs.Select(static x => ToKv(x)));
+
+ /// <inheritdoc/>
+ public async Task<Option<TV>> GetAndPutAsync(ITransaction? transaction, TK key, TV val) =>
+ (await _recordView.GetAndUpsertAsync(transaction, new KvPair<TK, TV>(key, val))).Select(static x => x.Val);
+
+ /// <inheritdoc/>
+ public async Task<bool> PutIfAbsentAsync(ITransaction? transaction, TK key, TV val) =>
+ await _recordView.InsertAsync(transaction, ToKv(key, val));
+
+ /// <inheritdoc/>
+ public async Task<bool> RemoveAsync(ITransaction? transaction, TK key) =>
+ await _recordView.DeleteAsync(transaction, ToKv(key));
+
+ /// <inheritdoc/>
+ public async Task<bool> RemoveAsync(ITransaction? transaction, TK key, TV val) =>
+ await _recordView.DeleteExactAsync(transaction, ToKv(key, val));
+
+ /// <inheritdoc/>
+ public async Task<IList<TK>> RemoveAllAsync(ITransaction? transaction, IEnumerable<TK> keys)
+ {
+ IgniteArgumentCheck.NotNull(keys, nameof(keys));
+
+ return await _recordView.DeleteAllAsync(
+ transaction,
+ keys.Select(static k => ToKv(k)),
+ resultFactory: static count => count == 0
+ ? (IList<TK>)Array.Empty<TK>()
+ : new List<TK>(count),
+ addAction: static (res, item) => res.Add(item.Key),
+ exact: false);
+ }
+
+ /// <inheritdoc/>
+ public async Task<IList<TK>> RemoveAllAsync(ITransaction? transaction, IEnumerable<KeyValuePair<TK, TV>> pairs)
+ {
+ IgniteArgumentCheck.NotNull(pairs, nameof(pairs));
+
+ return await _recordView.DeleteAllAsync(
+ transaction,
+ pairs.Select(static k => ToKv(k)),
+ resultFactory: static count => count == 0
+ ? (IList<TK>)Array.Empty<TK>()
+ : new List<TK>(count),
+ addAction: static (res, item) => res.Add(item.Key),
+ exact: true);
+ }
+
+ /// <inheritdoc/>
+ public async Task<Option<TV>> GetAndRemoveAsync(ITransaction? transaction, TK key) =>
+ (await _recordView.GetAndDeleteAsync(transaction, ToKv(key))).Select(static x => x.Val);
+
+ /// <inheritdoc/>
+ public async Task<bool> ReplaceAsync(ITransaction? transaction, TK key, TV val) =>
+ await _recordView.ReplaceAsync(transaction, ToKv(key, val));
+
+ /// <inheritdoc/>
+ public async Task<bool> ReplaceAsync(ITransaction? transaction, TK key, TV oldVal, TV newVal) =>
+ await _recordView.ReplaceAsync(transaction, ToKv(key, oldVal), ToKv(key, newVal));
+
+ /// <inheritdoc/>
+ public async Task<Option<TV>> GetAndReplaceAsync(ITransaction? transaction, TK key, TV val) =>
+ (await _recordView.GetAndReplaceAsync(transaction, ToKv(key, val))).Select(static x => x.Val);
+
+ private static KvPair<TK, TV> ToKv(KeyValuePair<TK, TV> x)
+ {
+ IgniteArgumentCheck.NotNull(x.Key, "key");
+ IgniteArgumentCheck.NotNull(x.Value, "val");
+
+ return new(x.Key, x.Value);
+ }
+
+ private static KvPair<TK, TV> ToKv(TK k)
+ {
+ IgniteArgumentCheck.NotNull(k, "key");
+
+ return new(k);
+ }
+
+ private static KvPair<TK, TV> ToKv(TK k, TV v)
+ {
+ IgniteArgumentCheck.NotNull(k, "key");
+ IgniteArgumentCheck.NotNull(v, "val");
+
+ return new(k, v);
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/RecordView.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/RecordView.cs
index f0bd09aab9..6b3b40e9bf 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/RecordView.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/RecordView.cs
@@ -33,6 +33,7 @@ namespace Apache.Ignite.Internal.Table
/// </summary>
/// <typeparam name="T">Record type.</typeparam>
internal sealed class RecordView<T> : IRecordView<T>
+ where T : notnull
{
/** Table. */
private readonly Table _table;
@@ -68,7 +69,34 @@ namespace Apache.Ignite.Internal.Table
}
/// <inheritdoc/>
- public async Task<IList<Option<T>>> GetAllAsync(ITransaction? transaction, IEnumerable<T> keys)
+ public async Task<IList<Option<T>>> GetAllAsync(ITransaction? transaction, IEnumerable<T> keys) =>
+ await GetAllAsync(
+ transaction: transaction,
+ keys: keys,
+ resultFactory: static count => count == 0
+ ? (IList<Option<T>>)Array.Empty<Option<T>>()
+ : new List<Option<T>>(count),
+ addAction: static (res, item) => res.Add(item));
+
+ /// <summary>
+ /// Gets multiple records by keys.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="keys">Collection of records with key columns set.</param>
+ /// <param name="resultFactory">Result factory.</param>
+ /// <param name="addAction">Add action.</param>
+ /// <typeparam name="TRes">Result type.</typeparam>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains matching records with all columns filled from the table. The order of collection
+ /// elements is guaranteed to be the same as the order of <paramref name="keys"/>. If a record does not exist,
+ /// the element at the corresponding index of the resulting collection will be empty <see cref="Option{T}"/>.
+ /// </returns>
+ public async Task<TRes> GetAllAsync<TRes>(
+ ITransaction? transaction,
+ IEnumerable<T> keys,
+ Func<int, TRes> resultFactory,
+ Action<TRes, Option<T>> addAction)
{
IgniteArgumentCheck.NotNull(keys, nameof(keys));
@@ -76,7 +104,7 @@ namespace Apache.Ignite.Internal.Table
if (!iterator.MoveNext())
{
- return Array.Empty<Option<T>>();
+ return resultFactory(0);
}
var schema = await _table.GetLatestSchemaAsync().ConfigureAwait(false);
@@ -89,7 +117,7 @@ namespace Apache.Ignite.Internal.Table
var resSchema = await _table.ReadSchemaAsync(resBuf).ConfigureAwait(false);
// TODO: Read value parts only (IGNITE-16022).
- return _ser.ReadMultipleNullable(resBuf, resSchema);
+ return _ser.ReadMultipleNullable(resBuf, resSchema, resultFactory, addAction);
}
/// <inheritdoc/>
@@ -163,7 +191,14 @@ namespace Apache.Ignite.Internal.Table
var resSchema = await _table.ReadSchemaAsync(resBuf).ConfigureAwait(false);
// TODO: Read value parts only (IGNITE-16022).
- return _ser.ReadMultiple(resBuf, resSchema);
+ return _ser.ReadMultiple(
+ buf: resBuf,
+ schema: resSchema,
+ keyOnly: false,
+ resultFactory: static count => count == 0
+ ? (IList<T>)Array.Empty<T>()
+ : new List<T>(count),
+ addAction: static (res, item) => res.Add(item));
}
/// <inheritdoc/>
@@ -231,32 +266,52 @@ namespace Apache.Ignite.Internal.Table
}
/// <inheritdoc/>
- public async Task<IList<T>> DeleteAllAsync(ITransaction? transaction, IEnumerable<T> keys)
- {
- IgniteArgumentCheck.NotNull(keys, nameof(keys));
-
- using var iterator = keys.GetEnumerator();
-
- if (!iterator.MoveNext())
- {
- return Array.Empty<T>();
- }
-
- var schema = await _table.GetLatestSchemaAsync().ConfigureAwait(false);
- var tx = transaction.ToInternal();
+ public async Task<IList<T>> DeleteAllAsync(ITransaction? transaction, IEnumerable<T> keys) =>
+ await DeleteAllAsync(transaction, keys, exact: false);
- using var writer = ProtoCommon.GetMessageWriter();
- _ser.WriteMultiple(writer, tx, schema, iterator, keyOnly: true);
-
- using var resBuf = await DoOutInOpAsync(ClientOp.TupleDeleteAll, tx, writer).ConfigureAwait(false);
- var resSchema = await _table.ReadSchemaAsync(resBuf).ConfigureAwait(false);
+ /// <inheritdoc/>
+ public async Task<IList<T>> DeleteAllExactAsync(ITransaction? transaction, IEnumerable<T> records) =>
+ await DeleteAllAsync(transaction, records, exact: true);
- // TODO: Read value parts only (IGNITE-16022).
- return _ser.ReadMultiple(resBuf, resSchema, keyOnly: true);
- }
+ /// <summary>
+ /// Deletes multiple records. If one or more keys do not exist, other records are still deleted.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="records">Record keys to delete.</param>
+ /// <param name="exact">Whether to match on both key and value.</param>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains records from <paramref name="records"/> that did not exist.
+ /// </returns>
+ public async Task<IList<T>> DeleteAllAsync(ITransaction? transaction, IEnumerable<T> records, bool exact) =>
+ await DeleteAllAsync(
+ transaction,
+ records,
+ resultFactory: static count => count == 0
+ ? (IList<T>)Array.Empty<T>()
+ : new List<T>(count),
+ addAction: static (res, item) => res.Add(item),
+ exact: exact);
- /// <inheritdoc/>
- public async Task<IList<T>> DeleteAllExactAsync(ITransaction? transaction, IEnumerable<T> records)
+ /// <summary>
+ /// Deletes multiple records. If one or more keys do not exist, other records are still deleted.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="records">Record keys to delete.</param>
+ /// <param name="resultFactory">Result factory.</param>
+ /// <param name="addAction">Add action.</param>
+ /// <param name="exact">Whether to match on both key and value.</param>
+ /// <typeparam name="TRes">Result type.</typeparam>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains records from <paramref name="records"/> that did not exist.
+ /// </returns>
+ public async Task<TRes> DeleteAllAsync<TRes>(
+ ITransaction? transaction,
+ IEnumerable<T> records,
+ Func<int, TRes> resultFactory,
+ Action<TRes, T> addAction,
+ bool exact)
{
IgniteArgumentCheck.NotNull(records, nameof(records));
@@ -264,19 +319,43 @@ namespace Apache.Ignite.Internal.Table
if (!iterator.MoveNext())
{
- return Array.Empty<T>();
+ return resultFactory(0);
}
var schema = await _table.GetLatestSchemaAsync().ConfigureAwait(false);
var tx = transaction.ToInternal();
using var writer = ProtoCommon.GetMessageWriter();
- _ser.WriteMultiple(writer, tx, schema, iterator);
+ _ser.WriteMultiple(writer, tx, schema, iterator, keyOnly: !exact);
- using var resBuf = await DoOutInOpAsync(ClientOp.TupleDeleteAllExact, tx, writer).ConfigureAwait(false);
+ var clientOp = exact ? ClientOp.TupleDeleteAllExact : ClientOp.TupleDeleteAll;
+ using var resBuf = await DoOutInOpAsync(clientOp, tx, writer).ConfigureAwait(false);
var resSchema = await _table.ReadSchemaAsync(resBuf).ConfigureAwait(false);
- return _ser.ReadMultiple(resBuf, resSchema);
+ // TODO: Read value parts only (IGNITE-16022).
+ return _ser.ReadMultiple(
+ buf: resBuf,
+ schema: resSchema,
+ keyOnly: !exact,
+ resultFactory: resultFactory,
+ addAction: addAction);
+ }
+
+ /// <summary>
+ /// Determines if the table contains an entry for the specified key.
+ /// </summary>
+ /// <param name="transaction">Transaction.</param>
+ /// <param name="key">Key.</param>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains a value indicating whether a record with the specified key exists in the table.
+ /// </returns>
+ internal async Task<bool> ContainsKey(ITransaction? transaction, T key)
+ {
+ IgniteArgumentCheck.NotNull(key, nameof(key));
+
+ using var resBuf = await DoRecordOutOpAsync(ClientOp.TupleContainsKey, transaction, key, keyOnly: true).ConfigureAwait(false);
+ return resBuf.GetReader().ReadBoolean();
}
private async Task<PooledBuffer> DoOutInOpAsync(
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Schema.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Schema.cs
index f9af706204..42aae781e2 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Schema.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Schema.cs
@@ -28,5 +28,11 @@ namespace Apache.Ignite.Internal.Table
internal record Schema(
int Version,
int KeyColumnCount,
- IReadOnlyList<Column> Columns);
+ IReadOnlyList<Column> Columns)
+ {
+ /// <summary>
+ /// Gets the value column count.
+ /// </summary>
+ public int ValueColumnCount => Columns.Count - KeyColumnCount;
+ }
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Schema.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/KvPair.cs
similarity index 57%
copy from modules/platforms/dotnet/Apache.Ignite/Internal/Table/Schema.cs
copy to modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/KvPair.cs
index f9af706204..83a38b842c 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Schema.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/KvPair.cs
@@ -15,18 +15,18 @@
* limitations under the License.
*/
-namespace Apache.Ignite.Internal.Table
-{
- using System.Collections.Generic;
+namespace Apache.Ignite.Internal.Table.Serialization;
- /// <summary>
- /// Schema.
- /// </summary>
- /// <param name="Version">Version.</param>
- /// <param name="KeyColumnCount">Key column count.</param>
- /// <param name="Columns">Columns in schema order.</param>
- internal record Schema(
- int Version,
- int KeyColumnCount,
- IReadOnlyList<Column> Columns);
-}
+using System.Collections.Generic;
+using Ignite.Table;
+
+/// <summary>
+/// Key + Value wrapper for serializing data from <see cref="IKeyValueView{TK,TV}"/>.
+/// <para />
+/// We can't use built-in <see cref="KeyValuePair{TKey,TValue}"/>, because it can come from the user code.
+/// </summary>
+/// <param name="Key">Key.</param>
+/// <param name="Val">Value.</param>
+/// <typeparam name="TK">Key type.</typeparam>
+/// <typeparam name="TV">Value type.</typeparam>
+internal readonly record struct KvPair<TK, TV>(TK Key, TV Val = default!);
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
index ba9b923739..becfff017e 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
@@ -68,7 +68,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
? w
: _valuePartReaders.GetOrAdd(schema.Version, EmitValuePartReader(schema));
- var binaryTupleReader = new BinaryTupleReader(reader.ReadBytesAsMemory(), schema.Columns.Count - schema.KeyColumnCount);
+ var binaryTupleReader = new BinaryTupleReader(reader.ReadBytesAsMemory(), schema.ValueColumnCount);
return readDelegate(ref binaryTupleReader, key);
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/RecordSerializer.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/RecordSerializer.cs
index 29412babf5..e8a24c0576 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/RecordSerializer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/RecordSerializer.cs
@@ -72,7 +72,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
var r = buf.GetReader();
r.Skip();
- return _handler.ReadValuePart(ref r, schema, key);
+ return Option.Some(_handler.ReadValuePart(ref r, schema, key));
}
/// <summary>
@@ -81,13 +81,21 @@ namespace Apache.Ignite.Internal.Table.Serialization
/// <param name="buf">Buffer.</param>
/// <param name="schema">Schema or null when there is no value.</param>
/// <param name="keyOnly">Key only mode.</param>
+ /// <param name="resultFactory">Result factory.</param>
+ /// <param name="addAction">Adds items to the result.</param>
+ /// <typeparam name="TRes">Result type.</typeparam>
/// <returns>List of records.</returns>
- public IList<T> ReadMultiple(PooledBuffer buf, Schema? schema, bool keyOnly = false)
+ public TRes ReadMultiple<TRes>(
+ PooledBuffer buf,
+ Schema? schema,
+ bool keyOnly,
+ Func<int, TRes> resultFactory,
+ Action<TRes, T> addAction)
{
if (schema == null)
{
// Null schema means empty collection.
- return Array.Empty<T>();
+ return resultFactory(0);
}
// Skip schema version.
@@ -95,11 +103,11 @@ namespace Apache.Ignite.Internal.Table.Serialization
r.Skip();
var count = r.ReadInt32();
- var res = new List<T>(count);
+ var res = resultFactory(count);
for (var i = 0; i < count; i++)
{
- res.Add(_handler.Read(ref r, schema, keyOnly));
+ addAction(res, _handler.Read(ref r, schema, keyOnly));
}
return res;
@@ -110,14 +118,20 @@ namespace Apache.Ignite.Internal.Table.Serialization
/// </summary>
/// <param name="buf">Buffer.</param>
/// <param name="schema">Schema or null when there is no value.</param>
- /// <param name="keyOnly">Key only mode.</param>
+ /// <param name="resultFactory">Result factory.</param>
+ /// <param name="addAction">Adds items to the result.</param>
+ /// <typeparam name="TRes">Result type.</typeparam>
/// <returns>List of records.</returns>
- public IList<Option<T>> ReadMultipleNullable(PooledBuffer buf, Schema? schema, bool keyOnly = false)
+ public TRes ReadMultipleNullable<TRes>(
+ PooledBuffer buf,
+ Schema? schema,
+ Func<int, TRes> resultFactory,
+ Action<TRes, Option<T>> addAction)
{
if (schema == null)
{
// Null schema means empty collection.
- return Array.Empty<Option<T>>();
+ return resultFactory(0);
}
// Skip schema version.
@@ -125,15 +139,15 @@ namespace Apache.Ignite.Internal.Table.Serialization
r.Skip();
var count = r.ReadInt32();
- var res = new List<Option<T>>(count);
+ var res = resultFactory(count);
for (var i = 0; i < count; i++)
{
- Option<T> option = r.ReadBoolean()
- ? _handler.Read(ref r, schema, keyOnly)
- : default(Option<T>);
+ var option = r.ReadBoolean()
+ ? Option.Some(_handler.Read(ref r, schema))
+ : default;
- res.Add(option);
+ addAction(res, option);
}
return res;
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs
new file mode 100644
index 0000000000..8d585685fb
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TuplePairSerializerHandler.cs
@@ -0,0 +1,114 @@
+/*
+ * 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.Table.Serialization;
+
+using Ignite.Table;
+using MessagePack;
+using Proto;
+using Proto.BinaryTuple;
+
+/// <summary>
+/// Serializer handler for <see cref="IIgniteTuple"/>.
+/// </summary>
+internal class TuplePairSerializerHandler : IRecordSerializerHandler<KvPair<IIgniteTuple, IIgniteTuple>>
+{
+ /// <summary>
+ /// Singleton instance.
+ /// </summary>
+ public static readonly TuplePairSerializerHandler Instance = new();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TuplePairSerializerHandler"/> class.
+ /// </summary>
+ private TuplePairSerializerHandler()
+ {
+ // No-op.
+ }
+
+ /// <inheritdoc/>
+ public KvPair<IIgniteTuple, IIgniteTuple> Read(ref MessagePackReader reader, Schema schema, bool keyOnly = false)
+ {
+ var columns = schema.Columns;
+ var count = keyOnly ? schema.KeyColumnCount : columns.Count;
+ var keyTuple = new IgniteTuple(count);
+ var valTuple = keyOnly ? null! : new IgniteTuple(schema.ValueColumnCount);
+ var tupleReader = new BinaryTupleReader(reader.ReadBytesAsMemory(), count);
+
+ for (var index = 0; index < count; index++)
+ {
+ var column = columns[index];
+
+ var tuple = index < schema.KeyColumnCount ? keyTuple : valTuple;
+ tuple[column.Name] = tupleReader.GetObject(index, column.Type, column.Scale);
+ }
+
+ return new(keyTuple, valTuple);
+ }
+
+ /// <inheritdoc/>
+ public KvPair<IIgniteTuple, IIgniteTuple> ReadValuePart(ref MessagePackReader reader, Schema schema, KvPair<IIgniteTuple, IIgniteTuple> key)
+ {
+ var columns = schema.Columns;
+ var tuple = new IgniteTuple(columns.Count);
+ var tupleReader = new BinaryTupleReader(reader.ReadBytesAsMemory(), schema.ValueColumnCount);
+
+ for (var i = schema.KeyColumnCount; i < columns.Count; i++)
+ {
+ var column = columns[i];
+ tuple[column.Name] = tupleReader.GetObject(i - schema.KeyColumnCount, column.Type, column.Scale);
+ }
+
+ return key with { Val = tuple };
+ }
+
+ /// <inheritdoc/>
+ public void Write(ref MessagePackWriter writer, Schema schema, KvPair<IIgniteTuple, IIgniteTuple> record, bool keyOnly = false)
+ {
+ var columns = schema.Columns;
+ var count = keyOnly ? schema.KeyColumnCount : columns.Count;
+ var noValueSet = writer.WriteBitSet(count);
+
+ var tupleBuilder = new BinaryTupleBuilder(count);
+
+ try
+ {
+ for (var index = 0; index < count; index++)
+ {
+ var col = columns[index];
+ var rec = index < schema.KeyColumnCount ? record.Key : record.Val;
+ var colIdx = rec.GetOrdinal(col.Name);
+
+ if (colIdx >= 0)
+ {
+ tupleBuilder.AppendObject(rec[colIdx], col.Type, col.Scale);
+ }
+ else
+ {
+ tupleBuilder.AppendNoValue(noValueSet);
+ }
+ }
+
+ var binaryTupleMemory = tupleBuilder.Build();
+ writer.Write(binaryTupleMemory.Span);
+ }
+ finally
+ {
+ tupleBuilder.Dispose();
+ }
+ }
+}
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 8c274a4126..087fc04efc 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/TupleSerializerHandler.cs
@@ -62,7 +62,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
{
var columns = schema.Columns;
var tuple = new IgniteTuple(columns.Count);
- var tupleReader = new BinaryTupleReader(reader.ReadBytesAsMemory(), schema.Columns.Count - schema.KeyColumnCount);
+ var tupleReader = new BinaryTupleReader(reader.ReadBytesAsMemory(), schema.ValueColumnCount);
for (var i = 0; i < columns.Count; i++)
{
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
index 1f64ae8f45..0098f7b986 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
@@ -62,6 +62,15 @@ namespace Apache.Ignite.Internal.Table
RecordBinaryView = new RecordView<IIgniteTuple>(
this,
new RecordSerializer<IIgniteTuple>(this, TupleSerializerHandler.Instance));
+
+ // RecordView and KeyValueView are symmetric and perform the same operations on the protocol level.
+ // Only serialization is different - KeyValueView splits records into two parts.
+ // Therefore, KeyValueView below simply delegates to RecordView<KvPair>,
+ // and SerializerHandler writes KV pair as a single record and reads back record as two parts.
+ var pairSerializer = new RecordSerializer<KvPair<IIgniteTuple, IIgniteTuple>>(this, TuplePairSerializerHandler.Instance);
+
+ KeyValueBinaryView = new KeyValueView<IIgniteTuple, IIgniteTuple>(
+ new RecordView<KvPair<IIgniteTuple, IIgniteTuple>>(this, pairSerializer));
}
/// <inheritdoc/>
@@ -70,6 +79,9 @@ namespace Apache.Ignite.Internal.Table
/// <inheritdoc/>
public IRecordView<IIgniteTuple> RecordBinaryView { get; }
+ /// <inheritdoc/>
+ public IKeyValueView<IIgniteTuple, IIgniteTuple> KeyValueBinaryView { get; }
+
/// <summary>
/// Gets the associated socket.
/// </summary>
@@ -81,7 +93,8 @@ namespace Apache.Ignite.Internal.Table
internal Guid Id { get; }
/// <inheritdoc/>
- public IRecordView<T> GetRecordView<T>() => GetRecordViewInternal<T>();
+ public IRecordView<T> GetRecordView<T>()
+ where T : notnull => GetRecordViewInternal<T>();
/// <summary>
/// Gets the record view for the specified type.
@@ -89,6 +102,7 @@ namespace Apache.Ignite.Internal.Table
/// <typeparam name="T">Record type.</typeparam>
/// <returns>Record view.</returns>
internal RecordView<T> GetRecordViewInternal<T>()
+ where T : notnull
{
// ReSharper disable once HeapView.CanAvoidClosure (generics prevent this)
return (RecordView<T>)_recordViews.GetOrAdd(
diff --git a/modules/platforms/dotnet/Apache.Ignite/Option.cs b/modules/platforms/dotnet/Apache.Ignite/Option.cs
index 1ed1fb03fd..c021001d2f 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Option.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Option.cs
@@ -38,7 +38,7 @@ public readonly record struct Option<T>
"StyleCop.CSharp.DocumentationRules",
"SA1642:ConstructorSummaryDocumentationMustBeginWithStandardText",
Justification = "False positive.")]
- private Option(T value, bool hasValue)
+ internal Option(T value, bool hasValue)
{
_value = value;
HasValue = hasValue;
@@ -56,13 +56,6 @@ public readonly record struct Option<T>
/// </summary>
public bool HasValue { get; }
- /// <summary>
- /// Wraps a value into an option.
- /// </summary>
- /// <param name="value">Value.</param>
- /// <returns>Wrapped value.</returns>
- public static implicit operator Option<T>(T value) => new(value, true);
-
/// <summary>
/// Deconstructs this instance.
/// </summary>
@@ -74,6 +67,14 @@ public readonly record struct Option<T>
hasValue = HasValue;
}
+ /// <summary>
+ /// Maps this instance to another type.
+ /// </summary>
+ /// <param name="selector">Selector.</param>
+ /// <typeparam name="TRes">Result type.</typeparam>
+ /// <returns>Resulting option.</returns>
+ public Option<TRes> Select<TRes>(Func<T, TRes> selector) => HasValue ? Option.Some(selector(_value)) : default!;
+
private bool PrintMembers(StringBuilder builder)
{
builder.Append("HasValue = ");
@@ -100,7 +101,7 @@ public static class Option
/// <param name="val">Value.</param>
/// <typeparam name="T">value type.</typeparam>
/// <returns>Option of T.</returns>
- public static Option<T> Some<T>(T val) => val;
+ public static Option<T> Some<T>(T val) => new(val, true);
/// <summary>
/// Returns an option without a value.
@@ -108,4 +109,4 @@ public static class Option
/// <typeparam name="T">value type.</typeparam>
/// <returns>Option of T.</returns>
public static Option<T> None<T>() => default;
-}
\ No newline at end of file
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite/RetryReadPolicy.cs b/modules/platforms/dotnet/Apache.Ignite/RetryReadPolicy.cs
index 09b2e7142b..ee656a8795 100644
--- a/modules/platforms/dotnet/Apache.Ignite/RetryReadPolicy.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/RetryReadPolicy.cs
@@ -54,6 +54,7 @@ namespace Apache.Ignite
ClientOperationType.TupleDeleteExact => false,
ClientOperationType.TupleDeleteAllExact => false,
ClientOperationType.TupleGetAndDelete => false,
+ ClientOperationType.TupleContainsKey => false,
ClientOperationType.ComputeExecute => false,
ClientOperationType.SqlExecute => false,
var unsupported => throw new NotSupportedException("Unsupported operation type: " + unsupported)
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/IKeyValueView.cs b/modules/platforms/dotnet/Apache.Ignite/Table/IKeyValueView.cs
new file mode 100644
index 0000000000..552c1d280f
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/IKeyValueView.cs
@@ -0,0 +1,203 @@
+/*
+ * 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.Table;
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Transactions;
+
+/// <summary>
+/// Key-value view provides access to table records in form of separate key and value parts.
+/// </summary>
+/// <typeparam name="TK">Key type.</typeparam>
+/// <typeparam name="TV">Value type.</typeparam>
+public interface IKeyValueView<TK, TV>
+ where TK : notnull
+ where TV : notnull
+{
+ /// <summary>
+ /// Gets a value associated with the given key.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains the value for the specified key, or an <see cref="Option{T}"/> instance without a value
+ /// when specified key is not present in the table.
+ /// </returns>
+ Task<Option<TV>> GetAsync(ITransaction? transaction, TK key);
+
+ /// <summary>
+ /// Gets multiple records by keys.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="keys">Keys.</param>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains a dictionary with specified keys and their values. If a record for a particular key does not exist,
+ /// it will not be present in the resulting dictionary.
+ /// </returns>
+ Task<IDictionary<TK, TV>> GetAllAsync(ITransaction? transaction, IEnumerable<TK> keys);
+
+ /// <summary>
+ /// Determines if the table contains an entry for the specified key.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Keys.</param>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result is <c>true</c> if a value exists for the specified key, and <c>false</c> otherwise.
+ /// </returns>
+ Task<bool> ContainsAsync(ITransaction? transaction, TK key);
+
+ /// <summary>
+ /// Puts a value with a given key.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="val">Value.</param>
+ /// <returns>A <see cref="Task{TResult}"/> representing the asynchronous operation.</returns>
+ Task PutAsync(ITransaction? transaction, TK key, TV val);
+
+ /// <summary>
+ /// Puts multiple key-value pairs.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="pairs">Pairs.</param>
+ /// <returns>A <see cref="Task{TResult}"/> representing the asynchronous operation.</returns>
+ Task PutAllAsync(ITransaction? transaction, IEnumerable<KeyValuePair<TK, TV>> pairs);
+
+ /// <summary>
+ /// Puts a value with a given key and returns previous value for that key.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="val">Value.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains the value for the specified key, or an <see cref="Option{T}"/> instance without a value
+ /// when specified key is not present in the table.
+ /// </returns>
+ Task<Option<TV>> GetAndPutAsync(ITransaction? transaction, TK key, TV val);
+
+ /// <summary>
+ /// Puts a value with a given key if the specified key is not present in the table.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="val">Value.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains a value indicating whether the value was added to the table.
+ /// </returns>
+ Task<bool> PutIfAbsentAsync(ITransaction? transaction, TK key, TV val);
+
+ /// <summary>
+ /// Removes a value with a given key from the table.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains a value indicating whether the key was removed from the table.
+ /// </returns>
+ Task<bool> RemoveAsync(ITransaction? transaction, TK key);
+
+ /// <summary>
+ /// Removes a value with a given key from the table only if it is equal to the specified value.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="val">Val.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains a value indicating whether the key was removed from the table.
+ /// </returns>
+ Task<bool> RemoveAsync(ITransaction? transaction, TK key, TV val);
+
+ /// <summary>
+ /// Removes values with given keys from the table.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="keys">Keys.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains skipped keys.
+ /// </returns>
+ Task<IList<TK>> RemoveAllAsync(ITransaction? transaction, IEnumerable<TK> keys);
+
+ /// <summary>
+ /// Removes records with given keys and values from the table.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="pairs">Keys.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains skipped keys.
+ /// </returns>
+ Task<IList<TK>> RemoveAllAsync(ITransaction? transaction, IEnumerable<KeyValuePair<TK, TV>> pairs);
+
+ /// <summary>
+ /// Gets and removes a value associated with the given key.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <returns>
+ /// A <see cref="Task{TResult}"/> representing the asynchronous operation.
+ /// The task result contains a value indicating whether the key was removed from the table.
+ /// </returns>
+ Task<Option<TV>> GetAndRemoveAsync(ITransaction? transaction, TK key);
+
+ /// <summary>
+ /// Replaces a record with the same key columns if it exists, otherwise does nothing.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="val">Value.</param>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains a value indicating whether a record with the specified key was replaced.
+ /// </returns>
+ Task<bool> ReplaceAsync(ITransaction? transaction, TK key, TV val);
+
+ /// <summary>
+ /// Replaces a record with a new one only if all existing columns have the same values
+ /// as the specified <paramref name="oldVal"/>.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="oldVal">Old value.</param>
+ /// <param name="newVal">New value.</param>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains a value indicating whether a record was replaced.
+ /// </returns>
+ Task<bool> ReplaceAsync(ITransaction? transaction, TK key, TV oldVal, TV newVal);
+
+ /// <summary>
+ /// Replaces a record with the same key columns if it exists.
+ /// </summary>
+ /// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
+ /// <param name="key">Key.</param>
+ /// <param name="val">Value.</param>
+ /// <returns>
+ /// A <see cref="Task"/> representing the asynchronous operation.
+ /// The task result contains the previous value for the given key, or empty <see cref="Option{T}"/> if it did not exist.
+ /// </returns>
+ Task<Option<TV>> GetAndReplaceAsync(ITransaction? transaction, TK key, TV val);
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/IRecordView.cs b/modules/platforms/dotnet/Apache.Ignite/Table/IRecordView.cs
index b13c5295b0..6d06822718 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/IRecordView.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/IRecordView.cs
@@ -26,6 +26,7 @@ namespace Apache.Ignite.Table
/// </summary>
/// <typeparam name="T">Record type.</typeparam>
public interface IRecordView<T>
+ where T : notnull
{
/// <summary>
/// Gets a record by key.
@@ -47,7 +48,7 @@ namespace Apache.Ignite.Table
/// A <see cref="Task"/> representing the asynchronous operation.
/// The task result contains matching records with all columns filled from the table. The order of collection
/// elements is guaranteed to be the same as the order of <paramref name="keys"/>. If a record does not exist,
- /// the element at the corresponding index of the resulting collection will be <c>null</c>.
+ /// the element at the corresponding index of the resulting collection will be empty <see cref="Option{T}"/>.
/// </returns>
Task<IList<Option<T>>> GetAllAsync(ITransaction? transaction, IEnumerable<T> keys);
@@ -68,7 +69,7 @@ namespace Apache.Ignite.Table
Task UpsertAllAsync(ITransaction? transaction, IEnumerable<T> records);
/// <summary>
- /// Inserts a record into the table if it does not exist or replaces the existing one.
+ /// Inserts a record into the table and returns previous record.
/// </summary>
/// <param name="transaction">The transaction or <c>null</c> to auto commit.</param>
/// <param name="record">Record to upsert.</param>
@@ -132,7 +133,7 @@ namespace Apache.Ignite.Table
/// <param name="record">Record to insert.</param>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation.
- /// The task result contains the previous value for the given key, or <c>null</c> if it did not exist.
+ /// The task result contains the previous value for the given key, or empty <see cref="Option{T}"/> if it did not exist.
/// </returns>
Task<Option<T>> GetAndReplaceAsync(ITransaction? transaction, T record);
@@ -165,7 +166,7 @@ namespace Apache.Ignite.Table
/// <param name="key">A record with key columns set.</param>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation.
- /// The task result contains deleted record or <c>null</c> if it did not exist.
+ /// The task result contains deleted record or empty <see cref="Option{T}"/> if it did not exist.
/// </returns>
Task<Option<T>> GetAndDeleteAsync(ITransaction? transaction, T key);
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs b/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs
index e6c49a30ad..300f15243b 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/ITable.cs
@@ -32,6 +32,11 @@ namespace Apache.Ignite.Table
/// </summary>
public IRecordView<IIgniteTuple> RecordBinaryView { get; }
+ /// <summary>
+ /// Gets the key-value binary view.
+ /// </summary>
+ public IKeyValueView<IIgniteTuple, IIgniteTuple> KeyValueBinaryView { get; }
+
/// <summary>
/// Gets the record view mapped to specified type <typeparamref name="T"/>.
/// <para />
@@ -40,6 +45,7 @@ namespace Apache.Ignite.Table
/// </summary>
/// <typeparam name="T">Record type.</typeparam>
/// <returns>Record view.</returns>
- public IRecordView<T> GetRecordView<T>(); // TODO: Custom mapping (IGNITE-16356)
+ public IRecordView<T> GetRecordView<T>() // TODO: Custom mapping (IGNITE-16356)
+ where T : notnull;
}
}