public void InMemoryKeyValueStore_AllOperations_SingleTransaction()
        {
            var kvs = new InMemoryKeyValueStore();

            var subtable = kvs.GetTable("MyTable");

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                table.Add("A", new byte[] { 1 });

                var one = table["A"];

                CollectionAssert.AreEqual(new byte[] { 1 }, one);

                table.Update("A", new byte[] { 2 });

                Assert.IsTrue(table.Contains("A"));

                var two = table["A"];

                CollectionAssert.AreEqual(new byte[] { 2 }, two);

                var contents = table.ToList();

                Assert.AreEqual(contents.Count, 1);
                Assert.AreEqual(contents[0].Key, "A");
                CollectionAssert.AreEquivalent(contents[0].Value, new byte[] { 2 });

                table.Remove("A");

                Assert.IsFalse(table.Contains("A"));

                tx.CommitAsync().Wait();
            }

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                Assert.AreEqual(table.Count(), 0);
            }
        }
        public void InMemoryKeyValueStore_Extensions()
        {
            Assert.ThrowsException <ArgumentNullException>(() => TransactedKeyValueTable.Clear <string, byte[]>(null));
            Assert.ThrowsException <ArgumentNullException>(() => TransactedKeyValueTable.TryRemove <string, byte[]>(null, "aa"));
            Assert.ThrowsException <ArgumentNullException>(() => TransactedKeyValueTable.TryGet <string, byte[]>(null, "aa", out _));

            var kvs = new InMemoryKeyValueStore();

            var subtable = kvs.GetTable("MyTable");

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                // Clear

                table.Add("A", new byte[] { 1 });

                table.Clear();

                Assert.IsFalse(table.Contains("A"));

                // TryRemove

                Assert.IsFalse(table.TryRemove("A"));

                table.Add("A", new byte[] { 1 });

                Assert.IsTrue(table.TryRemove("A"));
                Assert.IsFalse(table.Contains("A"));

                // TryGet

                Assert.IsFalse(table.TryGet("A", out var value));
                table.Add("A", new byte[] { 1 });
                Assert.IsTrue(table.TryGet("A", out value));
                CollectionAssert.AreEquivalent(value, new byte[] { 1 });
            }

            Assert.ThrowsException <ArgumentNullException>(() => Transaction.CommitAsync(null));
        }
        public void InMemoryKeyValueStore_ConsistencyViolations()
        {
            var kvs = new InMemoryKeyValueStore();

            var subtable1 = kvs.GetTable("MyTable1");

            // Contains

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var reader = subtable1.Enter(tx1);
                    var writer = subtable1.Enter(tx2);

                    reader.Contains("A");
                    writer.Add("A", Array.Empty <byte>());

                    tx2.CommitAsync().Wait();

                    // Throw because contains was false before and true now
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var reader = subtable1.Enter(tx1);
                    var writer = subtable1.Enter(tx2);

                    reader.Contains("A");
                    writer.Remove("A");

                    tx2.CommitAsync().Wait();

                    // Throw because contains was true before and false now
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            // Get

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var reader = subtable1.Enter(tx1);
                    var writer = subtable1.Enter(tx2);

                    try
                    {
                        var CS0201 = reader["A"];
                        Assert.Fail();
                    }
                    catch
                    {
                        // We leak the fact that the table doesn't contain "A"
                    }

                    writer.Add("A", Array.Empty <byte>());

                    tx2.CommitAsync().Wait();

                    // Throw because Get threw before and won't now (since the writer has added the key)
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var reader = subtable1.Enter(tx1);
                    var writer = subtable1.Enter(tx2);

                    var CS0201 = reader["A"];
                    writer.Update("A", new byte[1]);

                    tx2.CommitAsync().Wait();

                    // Throw because Get returned the original version before but will return the updated version now
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var reader = subtable1.Enter(tx1);
                    var writer = subtable1.Enter(tx2);

                    var CS0201 = reader["A"];
                    writer.Remove("A");

                    tx2.CommitAsync().Wait();

                    // Throw because Get didn't throw before but will now (since the writer has added the key)
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            // Update

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var writer1 = subtable1.Enter(tx1);
                    var writer2 = subtable1.Enter(tx2);

                    try
                    {
                        writer1.Update("A", Array.Empty <byte>());
                        Assert.Fail();
                    }
                    catch
                    {
                        // We leaked that "A" is not in the kvs
                    }

                    writer2.Add("A", Array.Empty <byte>());

                    tx2.CommitAsync().Wait();

                    // Throw because Updaete threw before and won't now (since the writer has added the key)
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            // Add

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var writer1 = subtable1.Enter(tx1);
                    var writer2 = subtable1.Enter(tx2);

                    try
                    {
                        writer1.Add("A", Array.Empty <byte>());
                        Assert.Fail();
                    }
                    catch
                    {
                        // We leaked that "A" is already in the kvs
                    }

                    writer2.Remove("A");

                    tx2.CommitAsync().Wait();

                    // Throw because Add threw before and won't now (since the writer has removed the key)
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            // Remove

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var writer1 = subtable1.Enter(tx1);
                    var writer2 = subtable1.Enter(tx2);

                    try
                    {
                        writer1.Remove("A");
                        Assert.Fail();
                    }
                    catch
                    {
                        // We leaked that "A" is already in the kvs
                    }

                    writer2.Add("A", Array.Empty <byte>());

                    tx2.CommitAsync().Wait();

                    // Throw because Remove threw before and won't now (since the writer has added the key)
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }

            // Enumerate

            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var writer1 = subtable1.Enter(tx1);
                    var writer2 = subtable1.Enter(tx2);

                    foreach (var item in writer1)
                    {
                        ;
                    }

                    writer2.Update("A", new byte[2]);

                    tx2.CommitAsync().Wait();

                    // Throw because iteration is not consistent with before
                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx1.CommitAsync()).Wait();
                }
        }
        public void InMemoryKeyValueStoreMultipleWriters()
        {
            var kvs = new InMemoryKeyValueStore();

            var subtable1 = kvs.GetTable("MyTable1");
            var subtable2 = kvs.GetTable("MyTable2");

            // Same table, different keys
            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var writer1 = subtable1.Enter(tx1);
                    var writer2 = subtable1.Enter(tx2);

                    writer1.Add("A", new byte[] { 1 });
                    writer2.Add("B", new byte[] { 1 });

                    tx1.CommitAsync().Wait();
                    tx2.CommitAsync().Wait();
                }

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable1.Enter(tx);

                Assert.IsTrue(table.Contains("A"));
                Assert.IsTrue(table.Contains("B"));
            }

            // Different table, same keys
            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var writer1 = subtable1.Enter(tx1);
                    var writer2 = subtable2.Enter(tx2);

                    writer1.Add("C", new byte[] { 1 });
                    writer2.Add("C", new byte[] { 2 });

                    tx1.CommitAsync().Wait();
                    tx2.CommitAsync().Wait();
                }

            using (var tx = kvs.CreateTransaction())
            {
                var table1 = subtable1.Enter(tx);
                var table2 = subtable2.Enter(tx);

                CollectionAssert.AreEquivalent(table1["C"], new byte[] { 1 });
                CollectionAssert.AreEquivalent(table2["C"], new byte[] { 2 });
            }

            // Same table, same keys
            using (var tx1 = kvs.CreateTransaction())
                using (var tx2 = kvs.CreateTransaction())
                {
                    var writer1 = subtable1.Enter(tx1);
                    var writer2 = subtable1.Enter(tx2);

                    writer1.Add("D", new byte[] { 1 });
                    writer2.Add("D", new byte[] { 2 });

                    tx1.CommitAsync().Wait();

                    Assert.ThrowsExceptionAsync <WriteConflictException>(() => tx2.CommitAsync()).Wait();
                }
        }
        private static void InMemoryKeyValueStore_AllOperations_MultipleTransaction_Core(bool saveAndLoad)
        {
            var kvs = new InMemoryKeyValueStore();

            void SaveAndLoad()
            {
                if (!saveAndLoad)
                {
                    return;
                }

                using var ms = new MemoryStream();

                kvs.Save(ms);

                ms.Position = 0;

                kvs = InMemoryKeyValueStore.Load(ms);
            }

            var subtable = kvs.GetTable("MyTable");

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                table.Add("A", new byte[] { 1 });

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                var one = table["A"];

                CollectionAssert.AreEqual(new byte[] { 1 }, one);

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                table.Update("A", new byte[] { 2 });

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                Assert.IsTrue(table.Contains("A"));

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                var two = table["A"];

                CollectionAssert.AreEqual(new byte[] { 2 }, two);

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                var contents = table.ToList();

                Assert.AreEqual(contents.Count, 1);
                Assert.AreEqual(contents[0].Key, "A");
                CollectionAssert.AreEquivalent(contents[0].Value, new byte[] { 2 });

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                table.Remove("A");

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                Assert.IsFalse(table.Contains("A"));

                tx.CommitAsync().Wait();
            }

            SaveAndLoad();

            using (var tx = kvs.CreateTransaction())
            {
                var table = subtable.Enter(tx);

                Assert.AreEqual(table.Count(), 0);
            }
        }