From fa711ef23bf2695cba1dfb99e025406c959796d2 Mon Sep 17 00:00:00 2001 From: Ali Ince Date: Mon, 3 Jul 2017 17:18:11 +0100 Subject: [PATCH 1/2] Improve tests & result builder classes 1. Added tests for Session#RunAsync, Session#BeginTransactionAsync and Session#Dispose after async calls, 2. Added tests for Transaction#RunAsync, 3. Improved ResultBuilder & ResultReaderBuilder with a new base class including common members from both classes, 4. Added tests for ResultReaderBuilder & StatementResultReader, 5. Added Session#BeginTransactionAsync(string bookmark) method. --- .../Neo4j.Driver.Tests.csproj | 2 + .../Result/ResultReaderBuilderTests.cs | 153 ++++++ .../Result/StatementResultReaderTests.cs | 443 ++++++++++++++++++ .../Neo4j.Driver.Tests/SessionTests.cs | 217 ++++++++- .../Neo4j.Driver.Tests/TransactionTests.cs | 60 +++ .../Internal/Result/BuilderBase.cs | 253 ++++++++++ .../Internal/Result/ResultBuilder.cs | 228 +-------- .../Internal/Result/ResultReaderBuilder.cs | 235 +--------- .../Internal/Result/StatementResultReader.cs | 11 +- Neo4j.Driver/Neo4j.Driver/Internal/Session.cs | 7 + 10 files changed, 1168 insertions(+), 441 deletions(-) create mode 100644 Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs create mode 100644 Neo4j.Driver/Neo4j.Driver.Tests/Result/StatementResultReaderTests.cs create mode 100644 Neo4j.Driver/Neo4j.Driver/Internal/Result/BuilderBase.cs diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj b/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj index da9980b5e..d800e715b 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Neo4j.Driver.Tests.csproj @@ -114,6 +114,8 @@ + + diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs new file mode 100644 index 000000000..a93a65cae --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs @@ -0,0 +1,153 @@ +// Copyright (c) 2002-2017 "Neo Technology," +// Network Engine for Objects in Lund AB [http://neotechnology.com] +// +// This file is part of Neo4j. +// +// Licensed 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. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Neo4j.Driver.Internal.Result; +using Neo4j.Driver.V1; +using Xunit; + +namespace Neo4j.Driver.Tests +{ + public class ResultReaderBuilderTests + { + private static ResultReaderBuilder GenerateBuilder(IDictionary meta = null) + { + var builder = new ResultReaderBuilder(); + builder.CollectFields(meta ?? new Dictionary { { "fields", new List { "x" } } }); + return builder; + } + + private static Task AssertGetExpectResults(IStatementResultReader reader, int numberExpected, List exspectedRecordsValues = null) + { + int count = 0; + var t = Task.Factory.StartNew(async () => + { + // ReSharper disable once LoopCanBeConvertedToQuery + while (await reader.ReadAsync()) + { + if (exspectedRecordsValues != null) + { + reader.Current().Values.First().Value.Should().Be(exspectedRecordsValues[count]); + } + + count++; + } + + count.Should().Be(numberExpected); + }); + return t; + } + + public class CollectRecordMethod + { + [Fact] + public void ShouldStreamResults() + { + var builder = GenerateBuilder(); + var i = 0; + builder.SetReceiveOneAction(() => + { + if (i++ >= 3) + { + builder.CollectSummary(null); + } + else + { + builder.CollectRecord(new object[] {123 + i}); + } + + return Task.CompletedTask; + }); + var result = builder.PreBuild(); + + var t = AssertGetExpectResults(result, 3, new List {124, 125, 126}); + + t.Wait(); + } + + [Fact] + public void ShouldReturnNoResultsWhenNoneRecieved() + { + var builder = GenerateBuilder(); + builder.SetReceiveOneAction(() => + { + builder.CollectSummary(null); + + return Task.CompletedTask; + }); + var result = builder.PreBuild(); + + var t = AssertGetExpectResults(result, 0); + + t.Wait(); + } + + [Fact] + public void ShouldReturnQueuedResultsWithExspectedValue() + { + var builder = GenerateBuilder(); + List recordValues = new List + { + 1, + "Hello", + false, + 10 + }; + for (int i = 0; i < recordValues.Count; i++) + { + builder.CollectRecord(new[] { recordValues[i] }); + } + builder.CollectSummary(null); + + var result = builder.PreBuild(); + + var task = AssertGetExpectResults(result, recordValues.Count, recordValues); + task.Wait(); + } + } + + public class InvalidateResultMethod + { + [Fact] + public void ShouldStopStreamingWhenResultIsInvalid() + { + var builder = GenerateBuilder(); + var i = 0; + builder.SetReceiveOneAction(() => + { + if (i++ >= 3) + { + builder.DoneFailure(); + } + else + { + builder.CollectRecord(new object[] {123 + i}); + } + + return Task.CompletedTask; + }); + var result = builder.PreBuild(); + + var t = AssertGetExpectResults(result, 3, new List { 124, 125, 126 }); + t.Wait(); + } + } + } +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Result/StatementResultReaderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Result/StatementResultReaderTests.cs new file mode 100644 index 000000000..ee449b71b --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Result/StatementResultReaderTests.cs @@ -0,0 +1,443 @@ +// Copyright (c) 2002-2017 "Neo Technology," +// Network Engine for Objects in Lund AB [http://neotechnology.com] +// +// This file is part of Neo4j. +// +// Licensed 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. +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; +using Neo4j.Driver.Internal.Result; +using Neo4j.Driver.V1; +using Record = Neo4j.Driver.Internal.Result.Record; + +namespace Neo4j.Driver.Tests +{ + public class StatementResultReaderTests + { + + private static Task NextRecordFromEnum(IEnumerator resultEnum) + { + if (resultEnum.MoveNext()) + { + return Task.FromResult(resultEnum.Current); + } + else + { + return Task.FromResult((IRecord)null); + } + } + + private static class ResultReaderCreator + { + public static StatementResultReader CreateResultReader(int keySize, int recordSize = 1, + Func> getSummaryFunc = null) + { + var keys = RecordCreator.CreateKeys(keySize); + var records = RecordCreator.CreateRecords(recordSize, keys); + var recordsEnum = records.GetEnumerator(); + + return new StatementResultReader(keys, () => NextRecordFromEnum(recordsEnum), getSummaryFunc); + } + + } + + public class Constructor + { + [Fact] + public void ShouldThrowArgumentNullExceptionIfRecordsIsNull() + { + var ex = Xunit.Record.Exception(() => new StatementResult(new List{"test"}, null)); + ex.Should().NotBeNull(); + ex.Should().BeOfType(); + } + + [Fact] + public void ShouldThrowArgumentNullExceptionIfKeysIsNull() + { + var ex = Xunit.Record.Exception(() => new StatementResult(null, new ListBasedRecordSet(new List()))); + ex.Should().NotBeNull(); + ex.Should().BeOfType(); + } + + [Fact] + public void ShouldSetKeysProperlyIfKeysNotNull() + { + var result = new StatementResult(new List{"test"}, new ListBasedRecordSet(new List())); + result.Keys.Should().HaveCount(1); + result.Keys.Should().Contain("test"); + } + } + + public class Keys + { + + [Fact] + public void KeysShouldReturnTheSameGivenInConstructor() + { + var result = ResultReaderCreator.CreateResultReader(1); + + result.Keys.Count.Should().Be(1); + result.Keys.Should().Contain("key0"); + } + + } + + public class ConsumeAsyncMethod + { + // INFO: Rewritten because StatementResult no longers takes IPeekingEnumerator in constructor + [Fact] + public async void ShouldConsumeAllRecords() + { + var result = ResultReaderCreator.CreateResultReader(0, 3); + await result.ConsumeAsync(); + var rec = await result.PeekAsync(); + rec.Should().BeNull(); + var read = await result.ReadAsync(); + read.Should().BeFalse(); + } + + [Fact] + public async void ShouldConsumeSummaryCorrectly() + { + int getSummaryCalled = 0; + var result = ResultReaderCreator.CreateResultReader(1, 0, () => { getSummaryCalled++; return Task.FromResult((IResultSummary)new FakeSummary()); }); + + + await result.ConsumeAsync(); + getSummaryCalled.Should().Be(1); + + // the same if we call it multiple times + await result.ConsumeAsync(); + getSummaryCalled.Should().Be(1); + } + + [Fact] + public async void ShouldThrowNoExceptionWhenCallingMultipleTimes() + { + + var result = ResultReaderCreator.CreateResultReader(1); + + await result.ConsumeAsync(); + var ex = await Xunit.Record.ExceptionAsync(() => result.ConsumeAsync()); + ex.Should().BeNull(); + } + + [Fact] + public async void ShouldConsumeRecordCorrectly() + { + var result = ResultReaderCreator.CreateResultReader(1, 3); + + await result.ConsumeAsync(); + + var read = await result.ReadAsync(); + read.Should().BeFalse(); + result.Current().Should().BeNull(); + } + } + + public class StreamingRecords + { + private readonly ITestOutputHelper _output; + + private class TestRecordYielder + { + private readonly IList _records = new List(); + private readonly int _total = 0; + + private readonly ITestOutputHelper _output; + public static List Keys => new List{"Test", "Keys"}; + + public TestRecordYielder(int count, int total, ITestOutputHelper output) + { + Add(count); + _total = total; + _output = output; + } + + public void AddNew(int count) + { + Add(count); + } + + private void Add(int count) + { + for (int i = 0; i < count; i++) + { + _records.Add(new Record(Keys, new object[] { "Test", 123 })); + } + } + + public IEnumerable Records + { + get + { + int i = 0; + while (i < _total) + { + while (i == _records.Count) + { + _output.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} -> Waiting for more Records"); + Thread.Sleep(50); + } + + yield return _records[i]; + i++; + } + } + } + + public IEnumerable RecordsWithAutoLoad + { + get + { + int i = 0; + while (i < _total) + { + while (i == _records.Count) + { + _output.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} -> Waiting for more Records"); + Thread.Sleep(500); + AddNew(1); + _output.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} -> Record arrived"); + } + + yield return _records[i]; + i++; + } + } + } + } + + public StreamingRecords(ITestOutputHelper output) + { + _output = output; + } + + + + [Fact] + public async void ShouldReturnRecords() + { + var recordYielder = new TestRecordYielder(5, 10, _output); + var recordYielderEnum = recordYielder.RecordsWithAutoLoad.GetEnumerator(); + var cursor = new StatementResultReader( TestRecordYielder.Keys, () => NextRecordFromEnum(recordYielderEnum)); + var records = new List(); + while (await cursor.ReadAsync()) + { + records.Add(cursor.Current()); + } + + records.Count.Should().Be(10); + } + + [Fact] + public async void ShouldWaitForAllRecordsToArrive() + { + var recordYielder = new TestRecordYielder(5, 10, _output); + var recordYielderEnum = recordYielder.Records.GetEnumerator(); + + int count = 0; + var cursor = new StatementResultReader(TestRecordYielder.Keys, () => NextRecordFromEnum(recordYielderEnum)); + var t = Task.Factory.StartNew(async () => + { + // ReSharper disable once LoopCanBeConvertedToQuery + while (await cursor.ReadAsync()) + { + count++; + } + + count.Should().Be(10); + }); + + while (count < 5) + { + Thread.Sleep(10); + } + + recordYielder.AddNew(5); + + await t; + } + + [Fact] + public async void ShouldReturnRecordsImmediatelyWhenReady() + { + var recordYielder = new TestRecordYielder(5, 10, _output); + var recordYielderEnum = recordYielder.Records.GetEnumerator(); + var result = new StatementResultReader(TestRecordYielder.Keys, () => NextRecordFromEnum(recordYielderEnum)); + var records = new List(); + var count = 5; + while (count > 0 && await result.ReadAsync()) + { + records.Add(result.Current()); + count--; + } + records.Count.Should().Be(5); + } + } + + public class ResultNavigation + { + [Fact] + public async void ShouldGetTheFirstRecordAndMoveToNextPosition() + { + var result = ResultReaderCreator.CreateResultReader(1, 3); + var read = await result.ReadAsync(); + read.Should().BeTrue(); + var record = result.Current(); + record[0].ValueAs().Should().Be("record0:key0"); + + read = await result.ReadAsync(); + read.Should().BeTrue(); + record = result.Current(); + record[0].ValueAs().Should().Be("record1:key0"); + } + + //[Fact] + //public void ShouldAlwaysAdvanceRecordPosition() + //{ + // var result = ResultReaderCreator.CreateResultReader(1, 3); + // var enumerable = result.Take(1); + // var records = result.Take(2).ToList(); + + // records[0][0].ValueAs().Should().Be("record0:key0"); + // records[1][0].ValueAs().Should().Be("record1:key0"); + + // records = enumerable.ToList(); + // records[0][0].ValueAs().Should().Be("record2:key0"); + //} + } + + public class SummaryAsyncMethod + { + [Fact] + public async void ShouldCallGetSummaryWhenGetSummaryIsNotNull() + { + bool getSummaryCalled = false; + var result = ResultReaderCreator.CreateResultReader(1, 0, () => { getSummaryCalled = true; return Task.FromResult((IResultSummary)null); }); + + // ReSharper disable once UnusedVariable + var summary = await result.SummaryAsync(); + + getSummaryCalled.Should().BeTrue(); + } + + [Fact] + public async void ShouldReturnNullWhenGetSummaryIsNull() + { + var result = ResultReaderCreator.CreateResultReader(1, 0); + var summary = await result.SummaryAsync(); + + summary.Should().BeNull(); + } + + [Fact] + public async void ShouldReturnExistingSummaryWhenSummaryHasBeenRetrieved() + { + int getSummaryCalled = 0; + var result = ResultReaderCreator.CreateResultReader(1, 0, () => { getSummaryCalled++; return Task.FromResult((IResultSummary)new FakeSummary()); }); + + // ReSharper disable once NotAccessedVariable + var summary = await result.SummaryAsync(); + // ReSharper disable once RedundantAssignment + summary = await result.SummaryAsync(); + getSummaryCalled.Should().Be(1); + } + } + + public class PeekAsyncMethod + { + [Fact] + public async void ShouldReturnNextRecordWithoutMovingCurrentRecord() + { + var result = ResultReaderCreator.CreateResultReader(1); + var record = await result.PeekAsync(); + record.Should().NotBeNull(); + + result.Current().Should().BeNull(); + } + + [Fact] + public async void ShouldReturnNullJustBeforeAtEnd() + { + var result = ResultReaderCreator.CreateResultReader(1); + var read = await result.ReadAsync(); + read.Should().BeTrue(); + var record = await result.PeekAsync(); + record.Should().BeNull(); + } + + [Fact] + public async void ShouldReturnNullIfAtEnd() + { + var result = ResultReaderCreator.CreateResultReader(1); + var read = await result.ReadAsync(); + read.Should().BeTrue(); + read = await result.ReadAsync(); + read.Should().BeFalse(); + var record = await result.PeekAsync(); + record.Should().BeNull(); + } + + [Fact] + public async void ShouldReturnSameRecordIfPeekedTwice() + { + var result = ResultReaderCreator.CreateResultReader(1); + var peeked1 = await result.PeekAsync(); + peeked1.Should().NotBeNull(); + var peeked2 = await result.PeekAsync(); + peeked2.Should().NotBeNull(); + peeked2.Should().Be(peeked1); + } + } + + public class ReadAsyncMethod + { + + [Fact] + public async void ReadAsyncAndCurrentWillReturnPeekedAfterPeek() + { + var result = ResultReaderCreator.CreateResultReader(1); + var peeked = await result.PeekAsync(); + peeked.Should().NotBeNull(); + var read = await result.ReadAsync(); + read.Should().BeTrue(); + var record = result.Current(); + record.Should().NotBeNull(); + record.Should().Be(peeked); + } + + } + + private class FakeSummary : IResultSummary + { + public Statement Statement { get; } + public ICounters Counters { get; } + public StatementType StatementType { get; } + public bool HasPlan { get; } + public bool HasProfile { get; } + public IPlan Plan { get; } + public IProfiledPlan Profile { get; } + public IList Notifications { get; } + public TimeSpan ResultAvailableAfter { get; } + public TimeSpan ResultConsumedAfter { get; } + public IServerInfo Server { get; } + } + } +} diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs index f4a5fbebb..2218e67bf 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs @@ -67,6 +67,32 @@ public void ResultBuilderShouldObtainServerInfoFromConnection() } } + public class RunAsyncMethod + { + [Fact] + public async void ShouldSendOnRun() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + await session.RunAsync("lalalal"); + + mockConn.Verify(x => x.Run("lalalal", new Dictionary(), It.IsAny(), true), Times.Once); + mockConn.Verify(x => x.SendAsync()); + } + + [Fact] + public async void ResultBuilderShouldObtainServerInfoFromConnection() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + await session.RunAsync("lalalal"); + + mockConn.Verify(x => x.Server, Times.Once); + } + } + public class BeginTransactionMethod { [Fact] @@ -216,6 +242,150 @@ public void ShouldDisposeConnectionOnNewBeginTxIfBeginTxFailed() } } + public class BeginTransactionAsyncMethod + { + + [Fact] + public async void ShouldIgnoreNullBookmark() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object, bookmark: FakeABookmark(123)); + session.LastBookmark.Should().EndWith("123"); + await session.BeginTransactionAsync(null); + session.LastBookmark.Should().EndWith("123"); + } + + [Fact] + public async void ShouldSetNewBookmark() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object, bookmark: FakeABookmark(123)); + session.LastBookmark.Should().EndWith("123"); + // begin tx will directly override the bookmark that was originally set before + await session.BeginTransactionAsync(FakeABookmark(12)); + session.LastBookmark.Should().EndWith("12"); + } + + [Fact] + public async void ShouldNotAllowNewTxWhileOneIsRunning() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + await session.BeginTransactionAsync(); + var error = await Record.ExceptionAsync(() => session.BeginTransactionAsync()); + error.Should().BeOfType(); + } + + [Fact] + public async void ShouldBeAbleToOpenTxAfterPreviousIsClosed() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + var tx = await session.BeginTransactionAsync(); + await tx.RollbackAsync(); + tx = await session.BeginTransactionAsync(); + } + + [Fact] + public async void ShouldNotBeAbleToUseSessionWhileOngoingTransaction() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + var tx = await session.BeginTransactionAsync(); + + var error = await Record.ExceptionAsync(() => session.RunAsync("lalal")); + error.Should().BeOfType(); + } + + [Fact] + public async void ShouldBeAbleToUseSessionAgainWhenTransactionIsClosed() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + var tx = await session.BeginTransactionAsync(); + await tx.RollbackAsync(); + + await session.RunAsync("lalal"); + } + + [Fact] + public async void ShouldClosePreviousRunConnectionWhenRunMoreStatements() + { + var mockConn = new Mock(); + var session = NewSession(mockConn.Object); + await session.RunAsync("lalal"); + + await session.RunAsync("bibib"); + mockConn.Verify(c => c.CloseAsync(), Times.Once); + } + + [Fact] + public async void ShouldClosePreviousRunConnectionWhenRunMoreTransactions() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(false); + var session = NewSession(mockConn.Object); + await session.RunAsync("lala"); + + await session.BeginTransactionAsync(); + mockConn.Verify(c => c.CloseAsync(), Times.Once); + } + + [Fact] + public async void ShouldDisposeConnectionOnRunIfBeginTxFailed() + { + // Given + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + mockConn.Setup(x => x.Run("BEGIN", null, null, true)) + .Throws(new IOException("Triggered an error when beginTx")); + var session = NewSession(mockConn.Object); + var exc = await Record.ExceptionAsync(() => session.BeginTransactionAsync()); + exc.Should().BeOfType(); + + // When + await session.RunAsync("lala"); + + // Then + mockConn.Verify(x => x.CloseAsync(), Times.Once); + } + + [Fact] + public async void ShouldDisposeConnectionOnNewBeginTxIfBeginTxFailed() + { + // Given + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var calls = 0; + mockConn.Setup(x => x.Run("BEGIN", null, null, true)) + .Callback(() => + { + // only throw exception on the first beginTx call + calls++; + if (calls == 1) + { + throw new IOException("Triggered an error when beginTx"); + } + }); + var session = NewSession(mockConn.Object); + var exc = await Record.ExceptionAsync(() => session.BeginTransactionAsync()); + exc.Should().BeOfType(); + + // When + await session.BeginTransactionAsync(); + + // Then + mockConn.Verify(x => x.CloseAsync(), Times.Once); + } + } + + public class DisposeMethod { [Fact] @@ -275,6 +445,51 @@ public void ShouldThrowExceptionWhenDisposingSessionMoreThanOnce() } } + public class DisposeMethodOnAsync + { + [Fact] + public async void ShouldDisposeConnectionIfBeginTxFailed() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + mockConn.Setup(x => x.Run("BEGIN", null, null, true)) + .Throws(new IOException("Triggered an error when beginTx")); + var session = NewSession(mockConn.Object); + var error = await Record.ExceptionAsync(() => session.BeginTransactionAsync()); + error.Should().BeOfType(); + session.Dispose(); + + mockConn.Verify(x => x.Close(), Times.Once); + } + + [Fact] + public async void ShouldDisposeTxOnDispose() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + var tx = await session.BeginTransactionAsync(); + session.Dispose(); + + mockConn.Verify(x => x.Run("ROLLBACK", null, null, false), Times.Once); + mockConn.Verify(x => x.Close(), Times.Once); + } + + [Fact] + public async void ShouldDisposeConnectinOnDispose() + { + var mockConn = new Mock(); + mockConn.Setup(x => x.IsOpen).Returns(true); + var session = NewSession(mockConn.Object); + await session.RunAsync("lalal"); + session.Dispose(); + + mockConn.Verify(x => x.Sync(), Times.Once); + mockConn.Verify(x => x.Close(), Times.Once); + } + + } + private class TestConnectionProvider : IConnectionProvider { private IConnection Connection { get; set; } @@ -297,7 +512,7 @@ public IConnection Acquire(AccessMode mode) public Task AcquireAsync(AccessMode mode) { - throw new NotSupportedException(); + return Task.FromResult(Connection); } } } diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs index d5d7f4b66..a9d1a9757 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/TransactionTests.cs @@ -173,6 +173,66 @@ public void ResultBuilderShouldObtainServerInfoFromConnection() } } + public class RunAsyncMethod + { + [Fact] + public async void ShouldRunPullAllSyncRun() + { + var mockConn = new Mock(); + var tx = new Transaction(mockConn.Object); + + await tx.RunAsync("lalala"); + + mockConn.Verify(x => x.Run("lalala", new Dictionary(), It.IsAny(), true), Times.Once); + mockConn.Verify(x => x.SendAsync(), Times.Once); + } + + [Fact] + public async void ShouldThrowExceptionIfPreviousTxFailed() + { + var mockConn = new Mock(); + var tx = new Transaction(mockConn.Object); + + try + { + mockConn.Setup(x => x.Run(It.IsAny(), new Dictionary(), It.IsAny(), true)) + .Throws(); + await tx.RunAsync("lalala"); + } + catch (Neo4jException) + { + // Fine, the state is set to failed now. + } + + var error = await Xunit.Record.ExceptionAsync(() => tx.RunAsync("ttt")); + error.Should().BeOfType(); + } + + [Fact] + public async void ShouldThrowExceptionIfFailedToRunAndFetchResult() + { + var mockConn = new Mock(); + var tx = new Transaction(mockConn.Object); + + mockConn.Setup(x => x.Run(It.IsAny(), new Dictionary(), It.IsAny(), true)) + .Throws(); + + var error = await Xunit.Record.ExceptionAsync(() => tx.RunAsync("ttt")); + error.Should().BeOfType(); + } + + [Fact] + public async void ResultBuilderShouldObtainServerInfoFromConnection() + { + var mockConn = new Mock(); + var tx = new Transaction(mockConn.Object); + + await tx.RunAsync("lalala"); + + mockConn.Verify(x => x.Server, Times.Once); + } + } + public class DisposeMethod { [Fact] diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/BuilderBase.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/BuilderBase.cs new file mode 100644 index 000000000..036f1df12 --- /dev/null +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/BuilderBase.cs @@ -0,0 +1,253 @@ +// Copyright (c) 2002-2017 "Neo Technology," +// Network Engine for Objects in Lund AB [http://neotechnology.com] +// +// This file is part of Neo4j. +// +// Licensed 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. +using System.Collections.Generic; +using System.Linq; +using static Neo4j.Driver.V1.StatementType; +using System; +using Neo4j.Driver.V1; + +namespace Neo4j.Driver.Internal.Result +{ + internal abstract class BuilderBase : IMessageResponseCollector + { + protected readonly List _keys = new List(); + protected readonly SummaryBuilder _summaryBuilder; + protected readonly IResultResourceHandler _resourceHandler; + + protected BuilderBase(Statement statement, IServerInfo server, IResultResourceHandler resourceHandler = null) + { + _summaryBuilder = new SummaryBuilder(statement, server); + _resourceHandler = resourceHandler; + } + + public abstract void CollectRecord(object[] fields); + + public void CollectFields(IDictionary meta) + { + if (meta == null) + { + return; + } + CollectKeys(meta, "fields"); + CollectResultAvailableAfter(meta, "result_available_after"); + } + + public void CollectBookmark(IDictionary meta) + { + throw new NotSupportedException( + $"Should not get a bookmark on a result. bookmark = {meta[Bookmark.BookmarkKey].As()}"); + } + + public virtual void CollectSummary(IDictionary meta) + { + if (meta == null) + { + return; + } + CollectType(meta, "type"); + CollectCounters(meta, "stats"); + CollectPlan(meta, "plan"); + CollectProfile(meta, "profile"); + CollectNotifications(meta, "notifications"); + CollectResultConsumedAfter(meta, "result_consumed_after"); + } + + public abstract void DoneSuccess(); + + public abstract void DoneFailure(); + + public abstract void DoneIgnored(); + + private void CollectKeys(IDictionary meta, string name) + { + if (!meta.ContainsKey(name)) + { + return; + } + + if (meta.ContainsKey(name)) + { + foreach (var key in meta[name].As>()) + { + _keys.Add(key); + } + } + } + + private void CollectType(IDictionary meta, string name) + { + if (!meta.ContainsKey(name)) + { + return; + } + var type = meta[name] as string; + _summaryBuilder.StatementType = FromCode(type); + } + + private void CollectCounters(IDictionary meta, string name) + { + if (!meta.ContainsKey(name)) + { + return; + } + var stats = meta[name] as IDictionary; + + _summaryBuilder.Counters = new Counters( + CountersValue(stats, "nodes-created"), + CountersValue(stats, "nodes-deleted"), + CountersValue(stats, "relationships-created"), + CountersValue(stats, "relationships-deleted"), + CountersValue(stats, "properties-set"), + CountersValue(stats, "labels-added"), + CountersValue(stats, "labels-removed"), + CountersValue(stats, "indexes-added"), + CountersValue(stats, "indexes-removed"), + CountersValue(stats, "constraints-added"), + CountersValue(stats, "constraints-removed")); + } + + private void CollectPlan(IDictionary meta, string name) + { + if (meta == null || !meta.ContainsKey(name)) + { + return; + } + var planDictionary = meta[name] as IDictionary; + _summaryBuilder.Plan = CollectPlan(planDictionary); + } + + + private static IPlan CollectPlan(IDictionary planDictionary) + { + if (planDictionary == null || planDictionary.Count == 0) + { + return null; + } + + var operationType = planDictionary.GetMandatoryValue("operatorType"); + var args = planDictionary.GetValue("args", new Dictionary()); + var identifiers = planDictionary.GetValue("identifiers", new List()).Cast(); + var children = planDictionary.GetValue("children", new List()); + + var childPlans = children + .Select(child => child as IDictionary) + .Select(CollectPlan) + .Where(childPlan => childPlan != null) + .ToList(); + return new Plan(operationType, args, identifiers.ToList(), childPlans); + } + + private static IProfiledPlan CollectProfile(IDictionary profileDictionary) + { + if (profileDictionary == null || profileDictionary.Count == 0) + { + return null; + } + var operationType = profileDictionary.GetMandatoryValue("operatorType"); + var args = profileDictionary.GetValue("args", new Dictionary()); + var identifiers = profileDictionary.GetValue("identifiers", new List()).Cast(); + var dbHits = profileDictionary.GetMandatoryValue("dbHits"); + var rows = profileDictionary.GetMandatoryValue("rows"); + var children = profileDictionary.GetValue("children", new List()); + + var childPlans = children + .Select(child => child as IDictionary) + .Select(CollectProfile) + .Where(childProfile => childProfile != null) + .ToList(); + return new ProfiledPlan(operationType, args, identifiers.ToList(), childPlans, dbHits, rows); + } + + + private void CollectProfile(IDictionary meta, string name) + { + if (!meta.ContainsKey(name)) + { + return; + } + var profiledPlan = meta[name] as IDictionary; + _summaryBuilder.Profile = CollectProfile(profiledPlan); + } + + private void CollectNotifications(IDictionary meta, string name) + { + if (!meta.ContainsKey(name)) + { + return; + } + var list = (meta[name] as List).Cast>(); + var notifications = new List(); + foreach (var value in list) + { + var code = value.GetValue("code", string.Empty); + var title = value.GetValue("title", string.Empty); + var description = value.GetValue("description", string.Empty); + + var posValue = value.GetValue("position", new Dictionary()); + + var position = new InputPosition( + (int)posValue.GetValue("offset", 0L), + (int)posValue.GetValue("line", 0L), + (int)posValue.GetValue("column", 0L)); + var severity = value.GetValue("severity", string.Empty); + notifications.Add(new Notification(code, title, description, position, severity)); + } + _summaryBuilder.Notifications = notifications; + } + + private void CollectResultAvailableAfter(IDictionary meta, string name) + { + if (!meta.ContainsKey(name)) + { + return; + } + _summaryBuilder.ResultAvailableAfter = meta[name].As(); + } + + private void CollectResultConsumedAfter(IDictionary meta, string name) + { + if (!meta.ContainsKey(name)) + { + return; + } + _summaryBuilder.ResultConsumedAfter = meta[name].As(); + } + + private static int CountersValue(IDictionary counters, string name) + { + return (int)counters.GetValue(name, 0L); + } + + private static StatementType FromCode(string type) + { + switch (type.ToLowerInvariant()) + { + case "r": + return ReadOnly; + case "rw": + return ReadWrite; + case "w": + return WriteOnly; + case "s": + return SchemaWrite; + default: + throw new ClientException("Unknown statement type: `" + type + "`."); + } + } + + } +} \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultBuilder.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultBuilder.cs index 5281f17e2..f40e3cfa7 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultBuilder.cs @@ -22,26 +22,20 @@ namespace Neo4j.Driver.Internal.Result { - internal class ResultBuilder : IMessageResponseCollector + internal class ResultBuilder : BuilderBase { - private readonly List _keys = new List(); - private readonly SummaryBuilder _summaryBuilder; - private Action _receiveOneAction; private readonly Queue _records = new Queue(); private bool _hasMoreRecords = true; - private readonly IResultResourceHandler _resourceHandler; - public ResultBuilder() : this(null, null, null, null, null) { } public ResultBuilder(Statement statement, Action receiveOneAction, IServerInfo server, IResultResourceHandler resourceHandler = null) + : base(statement, server, resourceHandler) { - _summaryBuilder = new SummaryBuilder(statement, server); - _resourceHandler = resourceHandler; SetReceiveOneAction(receiveOneAction); } @@ -102,55 +96,30 @@ internal void SetReceiveOneAction(Action receiveOneAction) }; } - public void CollectRecord(object[] fields) + public override void CollectRecord(object[] fields) { var record = new Record(_keys, fields); _records.Enqueue(record); } - - public void CollectFields(IDictionary meta) - { - if (meta == null) - { - return; - } - CollectKeys(meta, "fields"); - CollectResultAvailableAfter(meta, "result_available_after"); - } - - public void CollectBookmark(IDictionary meta) - { - throw new NotSupportedException( - $"Should not get a bookmark on a result. bookmark = {meta[Bookmark.BookmarkKey].As()}"); - } - - public void CollectSummary(IDictionary meta) + + public override void CollectSummary(IDictionary meta) { NoMoreRecords(); - if (meta == null) - { - return; - } - CollectType(meta, "type"); - CollectCounters(meta, "stats"); - CollectPlan(meta, "plan"); - CollectProfile(meta, "profile"); - CollectNotifications(meta, "notifications"); - CollectResultConsumedAfter(meta, "result_consumed_after"); + base.CollectSummary(meta); } - public void DoneSuccess() + public override void DoneSuccess() { // do nothing } - public void DoneFailure() + public override void DoneFailure() { NoMoreRecords();// an error received, so the result is broken } - public void DoneIgnored() + public override void DoneIgnored() { NoMoreRecords();// the result is ignored } @@ -159,183 +128,6 @@ private void NoMoreRecords() { _hasMoreRecords = false; } - - private void CollectKeys(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - - if (meta.ContainsKey(name)) - { - foreach (var key in meta[name].As>()) - { - _keys.Add(key); - } - } - } - - private void CollectType(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var type = meta[name] as string; - _summaryBuilder.StatementType = FromCode(type); - } - - private void CollectCounters(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var stats = meta[name] as IDictionary; - - _summaryBuilder.Counters = new Counters( - CountersValue(stats, "nodes-created"), - CountersValue(stats, "nodes-deleted"), - CountersValue(stats, "relationships-created"), - CountersValue(stats, "relationships-deleted"), - CountersValue(stats, "properties-set"), - CountersValue(stats, "labels-added"), - CountersValue(stats, "labels-removed"), - CountersValue(stats, "indexes-added"), - CountersValue(stats, "indexes-removed"), - CountersValue(stats, "constraints-added"), - CountersValue(stats, "constraints-removed")); - } - - private void CollectPlan(IDictionary meta, string name) - { - if (meta == null || !meta.ContainsKey(name)) - { - return; - } - var planDictionary = meta[name] as IDictionary; - _summaryBuilder.Plan = CollectPlan(planDictionary); - } - - - private static IPlan CollectPlan(IDictionary planDictionary) - { - if (planDictionary == null || planDictionary.Count == 0) - { - return null; - } - - var operationType = planDictionary.GetMandatoryValue("operatorType"); - var args = planDictionary.GetValue("args", new Dictionary()); - var identifiers = planDictionary.GetValue("identifiers", new List()).Cast(); - var children = planDictionary.GetValue("children", new List()); - - var childPlans = children - .Select(child => child as IDictionary) - .Select(CollectPlan) - .Where(childPlan => childPlan != null) - .ToList(); - return new Plan(operationType, args, identifiers.ToList(), childPlans); - } - - private static IProfiledPlan CollectProfile(IDictionary profileDictionary) - { - if (profileDictionary == null || profileDictionary.Count == 0) - { - return null; - } - var operationType = profileDictionary.GetMandatoryValue("operatorType"); - var args = profileDictionary.GetValue("args", new Dictionary()); - var identifiers = profileDictionary.GetValue("identifiers", new List()).Cast(); - var dbHits = profileDictionary.GetMandatoryValue("dbHits"); - var rows = profileDictionary.GetMandatoryValue("rows"); - var children = profileDictionary.GetValue("children", new List()); - - var childPlans = children - .Select(child => child as IDictionary) - .Select(CollectProfile) - .Where(childProfile => childProfile != null) - .ToList(); - return new ProfiledPlan(operationType, args, identifiers.ToList(), childPlans, dbHits, rows); - } - - - private void CollectProfile(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var profiledPlan = meta[name] as IDictionary; - _summaryBuilder.Profile = CollectProfile(profiledPlan); - } - - private void CollectNotifications(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var list = (meta[name] as List).Cast>(); - var notifications = new List(); - foreach (var value in list) - { - var code = value.GetValue("code", string.Empty); - var title = value.GetValue("title", string.Empty); - var description = value.GetValue("description", string.Empty); - - var posValue = value.GetValue("position", new Dictionary()); - - var position = new InputPosition( - (int)posValue.GetValue("offset", 0L), - (int)posValue.GetValue("line", 0L), - (int)posValue.GetValue("column", 0L)); - var severity = value.GetValue("severity", string.Empty); - notifications.Add(new Notification(code, title, description, position, severity)); - } - _summaryBuilder.Notifications = notifications; - } - - private void CollectResultAvailableAfter(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - _summaryBuilder.ResultAvailableAfter = meta[name].As(); - } - - private void CollectResultConsumedAfter(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - _summaryBuilder.ResultConsumedAfter = meta[name].As(); - } - - private static int CountersValue(IDictionary counters, string name) - { - return (int)counters.GetValue(name, 0L); - } - - private static StatementType FromCode(string type) - { - switch (type.ToLowerInvariant()) - { - case "r": - return ReadOnly; - case "rw": - return ReadWrite; - case "w": - return WriteOnly; - case "s": - return SchemaWrite; - default: - throw new ClientException("Unknown statement type: `" + type + "`."); - } - } - + } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs index bb6662006..c672d1648 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs @@ -23,23 +23,20 @@ namespace Neo4j.Driver.Internal.Result { - internal class ResultReaderBuilder : IMessageResponseCollector + internal class ResultReaderBuilder : BuilderBase { - private readonly List _keys = new List(); - private readonly SummaryBuilder _summaryBuilder; - private Func _receiveOneFunc; private readonly Queue _records = new Queue(); private bool _hasMoreRecords = true; + public ResultReaderBuilder() : this(null, null, null, null, null) + { + } - private readonly IResultResourceHandler _resourceHandler; - - public ResultReaderBuilder(Statement statement, Func receiveOneFunc, IServerInfo server, IResultResourceHandler resourceHandler = null) + public ResultReaderBuilder(Statement statement, Func receiveOneAction, IServerInfo server, IResultResourceHandler resourceHandler = null) + : base(statement, server, resourceHandler) { - _summaryBuilder = new SummaryBuilder(statement, server); - _resourceHandler = resourceHandler; - SetReceiveOneFunc(receiveOneFunc); + SetReceiveOneAction(receiveOneAction); } public ResultReaderBuilder(string statement, IDictionary parameters, @@ -85,7 +82,7 @@ private async Task NextRecord() return _records.Count > 0 ? _records.Dequeue() : null; } - private void SetReceiveOneFunc(Func receiveOneAction) + internal void SetReceiveOneAction(Func receiveOneAction) { _receiveOneFunc = async () => { @@ -99,55 +96,30 @@ private void SetReceiveOneFunc(Func receiveOneAction) }; } - public void CollectRecord(object[] fields) + public override void CollectRecord(object[] fields) { var record = new Record(_keys, fields); _records.Enqueue(record); } - public void CollectFields(IDictionary meta) - { - if (meta == null) - { - return; - } - CollectKeys(meta, "fields"); - CollectResultAvailableAfter(meta, "result_available_after"); - } - - public void CollectBookmark(IDictionary meta) - { - throw new NotSupportedException( - $"Should not get a bookmark on a result. bookmark = {meta[Bookmark.BookmarkKey].As()}"); - } - - public void CollectSummary(IDictionary meta) + public override void CollectSummary(IDictionary meta) { NoMoreRecords(); - if (meta == null) - { - return; - } - CollectType(meta, "type"); - CollectCounters(meta, "stats"); - CollectPlan(meta, "plan"); - CollectProfile(meta, "profile"); - CollectNotifications(meta, "notifications"); - CollectResultConsumedAfter(meta, "result_consumed_after"); + base.CollectSummary(meta); } - public void DoneSuccess() + public override void DoneSuccess() { // do nothing } - public void DoneFailure() + public override void DoneFailure() { NoMoreRecords();// an error received, so the result is broken } - public void DoneIgnored() + public override void DoneIgnored() { NoMoreRecords();// the result is ignored } @@ -156,183 +128,6 @@ private void NoMoreRecords() { _hasMoreRecords = false; } - - private void CollectKeys(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - - if (meta.ContainsKey(name)) - { - foreach (var key in meta[name].As>()) - { - _keys.Add(key); - } - } - } - - private void CollectType(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var type = meta[name] as string; - _summaryBuilder.StatementType = FromCode(type); - } - - private void CollectCounters(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var stats = meta[name] as IDictionary; - - _summaryBuilder.Counters = new Counters( - CountersValue(stats, "nodes-created"), - CountersValue(stats, "nodes-deleted"), - CountersValue(stats, "relationships-created"), - CountersValue(stats, "relationships-deleted"), - CountersValue(stats, "properties-set"), - CountersValue(stats, "labels-added"), - CountersValue(stats, "labels-removed"), - CountersValue(stats, "indexes-added"), - CountersValue(stats, "indexes-removed"), - CountersValue(stats, "constraints-added"), - CountersValue(stats, "constraints-removed")); - } - - private void CollectPlan(IDictionary meta, string name) - { - if (meta == null || !meta.ContainsKey(name)) - { - return; - } - var planDictionary = meta[name] as IDictionary; - _summaryBuilder.Plan = CollectPlan(planDictionary); - } - - - private static IPlan CollectPlan(IDictionary planDictionary) - { - if (planDictionary == null || planDictionary.Count == 0) - { - return null; - } - - var operationType = planDictionary.GetMandatoryValue("operatorType"); - var args = planDictionary.GetValue("args", new Dictionary()); - var identifiers = planDictionary.GetValue("identifiers", new List()).Cast(); - var children = planDictionary.GetValue("children", new List()); - - var childPlans = children - .Select(child => child as IDictionary) - .Select(CollectPlan) - .Where(childPlan => childPlan != null) - .ToList(); - return new Plan(operationType, args, identifiers.ToList(), childPlans); - } - - private static IProfiledPlan CollectProfile(IDictionary profileDictionary) - { - if (profileDictionary == null || profileDictionary.Count == 0) - { - return null; - } - var operationType = profileDictionary.GetMandatoryValue("operatorType"); - var args = profileDictionary.GetValue("args", new Dictionary()); - var identifiers = profileDictionary.GetValue("identifiers", new List()).Cast(); - var dbHits = profileDictionary.GetMandatoryValue("dbHits"); - var rows = profileDictionary.GetMandatoryValue("rows"); - var children = profileDictionary.GetValue("children", new List()); - - var childPlans = children - .Select(child => child as IDictionary) - .Select(CollectProfile) - .Where(childProfile => childProfile != null) - .ToList(); - return new ProfiledPlan(operationType, args, identifiers.ToList(), childPlans, dbHits, rows); - } - - - private void CollectProfile(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var profiledPlan = meta[name] as IDictionary; - _summaryBuilder.Profile = CollectProfile(profiledPlan); - } - - private void CollectNotifications(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - var list = (meta[name] as List).Cast>(); - var notifications = new List(); - foreach (var value in list) - { - var code = value.GetValue("code", string.Empty); - var title = value.GetValue("title", string.Empty); - var description = value.GetValue("description", string.Empty); - - var posValue = value.GetValue("position", new Dictionary()); - - var position = new InputPosition( - (int)posValue.GetValue("offset", 0L), - (int)posValue.GetValue("line", 0L), - (int)posValue.GetValue("column", 0L)); - var severity = value.GetValue("severity", string.Empty); - notifications.Add(new Notification(code, title, description, position, severity)); - } - _summaryBuilder.Notifications = notifications; - } - - private void CollectResultAvailableAfter(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - _summaryBuilder.ResultAvailableAfter = meta[name].As(); - } - - private void CollectResultConsumedAfter(IDictionary meta, string name) - { - if (!meta.ContainsKey(name)) - { - return; - } - _summaryBuilder.ResultConsumedAfter = meta[name].As(); - } - - private static int CountersValue(IDictionary counters, string name) - { - return (int)counters.GetValue(name, 0L); - } - - private static StatementType FromCode(string type) - { - switch (type.ToLowerInvariant()) - { - case "r": - return ReadOnly; - case "rw": - return ReadWrite; - case "w": - return WriteOnly; - case "s": - return SchemaWrite; - default: - throw new ClientException("Unknown statement type: `" + type + "`."); - } - } - + } } \ No newline at end of file diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/StatementResultReader.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/StatementResultReader.cs index cd9e0a2e7..3bb01aff1 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/StatementResultReader.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/StatementResultReader.cs @@ -32,9 +32,16 @@ public StatementResultReader(List keys, Func> nextRecordFu public Task SummaryAsync() { - if (_summary == null && _summaryFunc != null) + if (_summary == null) { - _summary = _summaryFunc(); + if (_summaryFunc != null) + { + _summary = _summaryFunc(); + } + else + { + _summary = Task.FromResult((IResultSummary)null); + } } return _summary; diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs index e4a4fa975..31e284d62 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs @@ -427,6 +427,13 @@ public Task BeginTransactionAsync() return TryExecuteAsync(async()=> await BeginTransactionWithoutLoggingAsync(_defaultMode)); } + public Task BeginTransactionAsync(string bookmark) + { + UpdateBookmark(Bookmark.From(bookmark, _logger)); + + return BeginTransactionAsync(); + } + private Task RunTransactionAsync(AccessMode mode, Func work) { return RunTransactionAsync(mode, async tx => From e88daf657171c3525ae0f7b3422281882e7d5961 Mon Sep 17 00:00:00 2001 From: Zhen Li Date: Tue, 4 Jul 2017 11:38:26 +0200 Subject: [PATCH 2/2] Fixing some minor naming --- .../Result/ResultReaderBuilderTests.cs | 6 ++--- .../Neo4j.Driver.Tests/SessionTests.cs | 24 ------------------- .../Internal/Result/ResultReaderBuilder.cs | 10 ++++---- Neo4j.Driver/Neo4j.Driver/Internal/Session.cs | 8 ------- 4 files changed, 8 insertions(+), 40 deletions(-) diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs index a93a65cae..0fd950899 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/Result/ResultReaderBuilderTests.cs @@ -62,7 +62,7 @@ public void ShouldStreamResults() { var builder = GenerateBuilder(); var i = 0; - builder.SetReceiveOneAction(() => + builder.SetReceiveOneFunc(() => { if (i++ >= 3) { @@ -86,7 +86,7 @@ public void ShouldStreamResults() public void ShouldReturnNoResultsWhenNoneRecieved() { var builder = GenerateBuilder(); - builder.SetReceiveOneAction(() => + builder.SetReceiveOneFunc(() => { builder.CollectSummary(null); @@ -130,7 +130,7 @@ public void ShouldStopStreamingWhenResultIsInvalid() { var builder = GenerateBuilder(); var i = 0; - builder.SetReceiveOneAction(() => + builder.SetReceiveOneFunc(() => { if (i++ >= 3) { diff --git a/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs b/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs index 2218e67bf..9fb6f236f 100644 --- a/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs +++ b/Neo4j.Driver/Neo4j.Driver.Tests/SessionTests.cs @@ -244,30 +244,6 @@ public void ShouldDisposeConnectionOnNewBeginTxIfBeginTxFailed() public class BeginTransactionAsyncMethod { - - [Fact] - public async void ShouldIgnoreNullBookmark() - { - var mockConn = new Mock(); - mockConn.Setup(x => x.IsOpen).Returns(true); - var session = NewSession(mockConn.Object, bookmark: FakeABookmark(123)); - session.LastBookmark.Should().EndWith("123"); - await session.BeginTransactionAsync(null); - session.LastBookmark.Should().EndWith("123"); - } - - [Fact] - public async void ShouldSetNewBookmark() - { - var mockConn = new Mock(); - mockConn.Setup(x => x.IsOpen).Returns(true); - var session = NewSession(mockConn.Object, bookmark: FakeABookmark(123)); - session.LastBookmark.Should().EndWith("123"); - // begin tx will directly override the bookmark that was originally set before - await session.BeginTransactionAsync(FakeABookmark(12)); - session.LastBookmark.Should().EndWith("12"); - } - [Fact] public async void ShouldNotAllowNewTxWhileOneIsRunning() { diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs index c672d1648..ba25e9173 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Result/ResultReaderBuilder.cs @@ -33,10 +33,10 @@ public ResultReaderBuilder() : this(null, null, null, null, null) { } - public ResultReaderBuilder(Statement statement, Func receiveOneAction, IServerInfo server, IResultResourceHandler resourceHandler = null) + public ResultReaderBuilder(Statement statement, Func receiveOneFunc, IServerInfo server, IResultResourceHandler resourceHandler = null) : base(statement, server, resourceHandler) { - SetReceiveOneAction(receiveOneAction); + SetReceiveOneFunc(receiveOneFunc); } public ResultReaderBuilder(string statement, IDictionary parameters, @@ -47,7 +47,7 @@ public ResultReaderBuilder(Statement statement, Func receiveOneAction, ISe public IStatementResultReader PreBuild() { - return new StatementResultReader(_keys, NextRecord, SummaryAsync); + return new StatementResultReader(_keys, NextRecordAsync, SummaryAsync); } /// @@ -69,7 +69,7 @@ private async Task SummaryAsync() /// Return next record in the record stream if any, otherwise return null /// /// Next record in the record stream if any, otherwise return null - private async Task NextRecord() + private async Task NextRecordAsync() { if (_records.Count > 0) { @@ -82,7 +82,7 @@ private async Task NextRecord() return _records.Count > 0 ? _records.Dequeue() : null; } - internal void SetReceiveOneAction(Func receiveOneAction) + internal void SetReceiveOneFunc(Func receiveOneAction) { _receiveOneFunc = async () => { diff --git a/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs b/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs index 31e284d62..36f8f4267 100644 --- a/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs +++ b/Neo4j.Driver/Neo4j.Driver/Internal/Session.cs @@ -15,7 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. using System; -using System.Collections.Generic; using System.Threading.Tasks; using Neo4j.Driver.Internal.Connector; using Neo4j.Driver.Internal.Result; @@ -427,13 +426,6 @@ public Task BeginTransactionAsync() return TryExecuteAsync(async()=> await BeginTransactionWithoutLoggingAsync(_defaultMode)); } - public Task BeginTransactionAsync(string bookmark) - { - UpdateBookmark(Bookmark.From(bookmark, _logger)); - - return BeginTransactionAsync(); - } - private Task RunTransactionAsync(AccessMode mode, Func work) { return RunTransactionAsync(mode, async tx =>