You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by fl...@apache.org on 2021/10/13 08:43:39 UTC
[tinkerpop] 01/01: TINKERPOP-2556 Add tx() support for .NET
This is an automated email from the ASF dual-hosted git repository.
florianhockmann pushed a commit to branch TINKERPOP-2556
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit a6ee742f857751403a7a8218549e309934ec35e7
Author: Florian Hockmann <fh...@florian-hockmann.de>
AuthorDate: Fri Oct 8 16:35:31 2021 +0200
TINKERPOP-2556 Add tx() support for .NET
---
.github/workflows/build-test.yml | 2 +-
.../Driver/Remote/DriverRemoteConnection.cs | 35 +++++-
.../Driver/Remote/DriverRemoteTransaction.cs | 62 ++++++++++
gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj | 5 +-
.../Process/Remote/IRemoteConnection.cs | 6 +
.../src/Gremlin.Net/Process/Traversal/Bytecode.cs | 2 +-
.../IRemoteConnection.cs => Traversal/GraphOp.cs} | 26 ++--
.../Process/Traversal/GraphTraversalSource.cs | 32 ++++-
.../ITransaction.cs} | 17 +--
.../GraphTraversalSourceTests.cs | 4 +-
.../DriverRemoteConnection/GraphTraversalTests.cs | 2 +-
.../GraphTraversalTransactionTests.cs | 107 ++++++++++++++++
.../Driver/Remote/DriverRemoteTransactionTests.cs | 57 +++++++++
gremlin-dotnet/test/pom.xml | 137 +++++++++++++++++++++
14 files changed, 451 insertions(+), 43 deletions(-)
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 74e912e..7e25b0f 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -185,4 +185,4 @@ jobs:
touch gremlin-dotnet/src/.glv
touch gremlin-dotnet/test/.glv
mvn clean install -pl -:gremlin-javascript,-:gremlin-python,-:gremlint -q -DskipTests -Dci
- mvn verify -pl :gremlin-dotnet,:gremlin-dotnet-tests -P gremlin-dotnet
\ No newline at end of file
+ mvn verify -pl :gremlin-dotnet,:gremlin-dotnet-tests -P gremlin-dotnet -DincludeNeo4j
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
index 006cf8a..63649a1 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteConnection.cs
@@ -25,10 +25,10 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Gremlin.Net.Driver.Messages;
-using Gremlin.Net.Driver;
using Gremlin.Net.Process.Remote;
using Gremlin.Net.Process.Traversal;
using Gremlin.Net.Process.Traversal.Strategy.Decoration;
+using Gremlin.Net.Structure;
namespace Gremlin.Net.Driver.Remote
{
@@ -49,6 +49,10 @@ namespace Gremlin.Net.Driver.Remote
{Tokens.ArgsEvalTimeout, "scriptEvaluationTimeout", Tokens.ArgsBatchSize,
Tokens.RequestId, Tokens.ArgsUserAgent};
+ private readonly string _sessionId;
+ private string Processor => IsSessionBound ? Tokens.ProcessorSession : Tokens.ProcessorTraversal;
+ public bool IsSessionBound => _sessionId != null;
+
/// <summary>
/// Initializes a new <see cref="IRemoteConnection" /> using "g" as the default remote TraversalSource name.
/// </summary>
@@ -91,6 +95,12 @@ namespace Gremlin.Net.Driver.Remote
_traversalSource = traversalSource ?? throw new ArgumentNullException(nameof(traversalSource));
}
+ private DriverRemoteConnection(IGremlinClient client, string traversalSource, Guid sessionId)
+ : this(client, traversalSource)
+ {
+ _sessionId = sessionId.ToString();
+ }
+
/// <summary>
/// Submits <see cref="Bytecode" /> for evaluation to a remote Gremlin Server.
/// </summary>
@@ -107,11 +117,16 @@ namespace Gremlin.Net.Driver.Remote
{
var requestMsg =
RequestMessage.Build(Tokens.OpsBytecode)
- .Processor(Tokens.ProcessorTraversal)
+ .Processor(Processor)
.OverrideRequestId(requestid)
.AddArgument(Tokens.ArgsGremlin, bytecode)
.AddArgument(Tokens.ArgsAliases, new Dictionary<string, string> {{"g", _traversalSource}});
+ if (IsSessionBound)
+ {
+ requestMsg.AddArgument(Tokens.ArgsSession, _sessionId);
+ }
+
var optionsStrategyInst = bytecode.SourceInstructions.Find(
s => s.OperatorName == "withStrategies" && s.Arguments[0] is OptionsStrategy);
if (optionsStrategyInst != null)
@@ -128,6 +143,22 @@ namespace Gremlin.Net.Driver.Remote
return await _client.SubmitAsync<Traverser>(requestMsg.Create()).ConfigureAwait(false);
}
+
+ public ITransaction Tx(GraphTraversalSource g)
+ {
+ var session = new DriverRemoteConnection(_client, _traversalSource, Guid.NewGuid());
+ return new DriverRemoteTransaction(session, g);
+ }
+
+ public async Task CommitAsync()
+ {
+ await SubmitAsync<object, object>(GraphOp.Commit).ConfigureAwait(false);
+ }
+
+ public async Task RollbackAsync()
+ {
+ await SubmitAsync<object, object>(GraphOp.Rollback).ConfigureAwait(false);
+ }
/// <inheritdoc />
public void Dispose()
diff --git a/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteTransaction.cs b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteTransaction.cs
new file mode 100644
index 0000000..1659bb4
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Driver/Remote/DriverRemoteTransaction.cs
@@ -0,0 +1,62 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System;
+using System.Threading.Tasks;
+using Gremlin.Net.Process.Traversal;
+using Gremlin.Net.Structure;
+
+namespace Gremlin.Net.Driver.Remote
+{
+ public class DriverRemoteTransaction : ITransaction
+ {
+ private readonly DriverRemoteConnection _sessionBasedConnection;
+ private GraphTraversalSource _g;
+
+ public DriverRemoteTransaction(DriverRemoteConnection connection, GraphTraversalSource g)
+ {
+ _sessionBasedConnection = connection;
+ _g = g;
+ }
+
+ public GraphTraversalSource Begin()
+ {
+ if (_g.IsSessionBound)
+ {
+ throw new InvalidOperationException("Transaction already started on this object");
+ }
+ _g = new GraphTraversalSource(_g.TraversalStrategies, _g.Bytecode, _sessionBasedConnection);
+ return _g;
+ }
+
+ public async Task CommitAsync()
+ {
+ await _sessionBasedConnection.CommitAsync().ConfigureAwait(false);
+ }
+
+ public async Task RollbackAsync()
+ {
+ await _sessionBasedConnection.RollbackAsync().ConfigureAwait(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
index 0f963bb..f9e3fc5 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
+++ b/gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj
@@ -19,8 +19,9 @@ limitations under the License.
<PropertyGroup Label="Build">
<TargetFramework>netstandard2.0</TargetFramework>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <GenerateDocumentationFile>true</GenerateDocumentationFile>
+<!-- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>-->
+<!-- <GenerateDocumentationFile>true</GenerateDocumentationFile>-->
+ <LangVersion>8</LangVersion>
</PropertyGroup>
<PropertyGroup Label="Package">
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
index 5393bcb..9502f5d 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
@@ -23,6 +23,7 @@
using System.Threading.Tasks;
using Gremlin.Net.Process.Traversal;
+using Gremlin.Net.Structure;
namespace Gremlin.Net.Process.Remote
{
@@ -38,5 +39,10 @@ namespace Gremlin.Net.Process.Remote
/// <param name="bytecode">The <see cref="Bytecode" /> to send.</param>
/// <returns>The <see cref="ITraversal" /> with the results and optional side-effects.</returns>
Task<ITraversal<S, E>> SubmitAsync<S, E>(Bytecode bytecode);
+
+ ITransaction Tx(GraphTraversalSource graphTraversalSource);
+ bool IsSessionBound { get; }
+ Task CommitAsync();
+ Task RollbackAsync();
}
}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
index 7149e8b..cd53a1c 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
@@ -38,7 +38,7 @@ namespace Gremlin.Net.Process.Traversal
/// </remarks>
public class Bytecode
{
- private static readonly object[] EmptyArray = new object[0];
+ private static readonly object[] EmptyArray = Array.Empty<object>();
/// <summary>
/// Initializes a new instance of the <see cref="Bytecode" /> class.
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphOp.cs
similarity index 55%
copy from gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
copy to gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphOp.cs
index 5393bcb..b938541 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphOp.cs
@@ -21,22 +21,18 @@
#endregion
-using System.Threading.Tasks;
-using Gremlin.Net.Process.Traversal;
-
-namespace Gremlin.Net.Process.Remote
+namespace Gremlin.Net.Process.Traversal
{
- /// <summary>
- /// A simple abstraction of a "connection" to a "server".
- /// </summary>
- public interface IRemoteConnection
+ public static class GraphOp
{
- /// <summary>
- /// Submits <see cref="ITraversal" /> <see cref="Bytecode" /> to a server and returns a
- /// <see cref="ITraversal" />.
- /// </summary>
- /// <param name="bytecode">The <see cref="Bytecode" /> to send.</param>
- /// <returns>The <see cref="ITraversal" /> with the results and optional side-effects.</returns>
- Task<ITraversal<S, E>> SubmitAsync<S, E>(Bytecode bytecode);
+ public static Bytecode Commit { get; } = CreateGraphOp("tx", "commit");
+ public static Bytecode Rollback { get; } = CreateGraphOp("tx", "rollback");
+
+ private static Bytecode CreateGraphOp(string name, object value)
+ {
+ var bytecode = new Bytecode();
+ bytecode.AddSource(name, value);
+ return bytecode;
+ }
}
}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
index c74e826..ba31cd7 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversalSource.cs
@@ -24,6 +24,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Gremlin.Net.Driver.Remote;
using Gremlin.Net.Process.Remote;
using Gremlin.Net.Process.Traversal.Strategy.Decoration;
using Gremlin.Net.Structure;
@@ -38,6 +39,10 @@ namespace Gremlin.Net.Process.Traversal
/// </summary>
public class GraphTraversalSource
{
+ private readonly IRemoteConnection _connection;
+
+ public bool IsSessionBound => _connection is { IsSessionBound: true };
+
/// <summary>
/// Gets or sets the traversal strategies associated with this graph traversal source.
/// </summary>
@@ -71,6 +76,15 @@ namespace Gremlin.Net.Process.Traversal
Bytecode = bytecode;
}
+ public GraphTraversalSource(ICollection<ITraversalStrategy> traversalStrategies, Bytecode bytecode,
+ IRemoteConnection connection)
+ : this(traversalStrategies.Where(strategy => strategy.GetType() != typeof(RemoteStrategy)).ToList(),
+ bytecode)
+ {
+ _connection = connection;
+ TraversalStrategies.Add(new RemoteStrategy(connection));
+ }
+
public GraphTraversalSource With(string key)
{
return With(key, true);
@@ -243,12 +257,19 @@ namespace Gremlin.Net.Process.Traversal
/// <see cref="GraphTraversal{SType, EType}" />.
/// </param>
/// <returns>A <see cref="GraphTraversalSource" /> configured to use the provided <see cref="IRemoteConnection" />.</returns>
- public GraphTraversalSource WithRemote(IRemoteConnection remoteConnection)
+ public GraphTraversalSource WithRemote(IRemoteConnection remoteConnection) =>
+ new GraphTraversalSource(new List<ITraversalStrategy>(TraversalStrategies),
+ new Bytecode(Bytecode), remoteConnection);
+
+ public ITransaction Tx()
{
- var source = new GraphTraversalSource(new List<ITraversalStrategy>(TraversalStrategies),
- new Bytecode(Bytecode));
- source.TraversalStrategies.Add(new RemoteStrategy(remoteConnection));
- return source;
+ // you can't do g.tx().begin().tx() - no child transactions
+ if (IsSessionBound)
+ {
+ throw new InvalidOperationException(
+ "This GraphTraversalSource is already bound to a transaction - child transactions are not supported");
+ }
+ return _connection.Tx(this);
}
/// <summary>
@@ -377,7 +398,6 @@ namespace Gremlin.Net.Process.Traversal
traversal.Bytecode.AddStep("io", file);
return traversal;
}
-
}
#pragma warning restore 1591
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/ITransaction.cs
similarity index 59%
copy from gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
copy to gremlin-dotnet/src/Gremlin.Net/Structure/ITransaction.cs
index 5393bcb..f3dc77e 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Remote/IRemoteConnection.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Structure/ITransaction.cs
@@ -24,19 +24,12 @@
using System.Threading.Tasks;
using Gremlin.Net.Process.Traversal;
-namespace Gremlin.Net.Process.Remote
+namespace Gremlin.Net.Structure
{
- /// <summary>
- /// A simple abstraction of a "connection" to a "server".
- /// </summary>
- public interface IRemoteConnection
+ public interface ITransaction
{
- /// <summary>
- /// Submits <see cref="ITraversal" /> <see cref="Bytecode" /> to a server and returns a
- /// <see cref="ITraversal" />.
- /// </summary>
- /// <param name="bytecode">The <see cref="Bytecode" /> to send.</param>
- /// <returns>The <see cref="ITraversal" /> with the results and optional side-effects.</returns>
- Task<ITraversal<S, E>> SubmitAsync<S, E>(Bytecode bytecode);
+ GraphTraversalSource Begin();
+ Task CommitAsync();
+ Task RollbackAsync();
}
}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs
index 1a35679..713394f 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalSourceTests.cs
@@ -1,4 +1,4 @@
-#region License
+#region License
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -21,10 +21,8 @@
#endregion
-using System;
using System.Collections.Generic;
using Gremlin.Net.Process.Traversal;
-using Gremlin.Net.Process.Traversal.Strategy.Verification;
using Xunit;
namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
index 89597a3..b0347bc 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTests.cs
@@ -247,4 +247,4 @@ namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
Assert.Equal(6, count);
}
}
-}
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs
new file mode 100644
index 0000000..fb968af
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/GraphTraversalTransactionTests.cs
@@ -0,0 +1,107 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System;
+using System.Threading.Tasks;
+using Gremlin.Net.Process.Remote;
+using Gremlin.Net.Process.Traversal;
+using Xunit;
+
+namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
+{
+ public class GraphTraversalTransactionTests : IDisposable
+ {
+ private readonly IRemoteConnection _connection = new RemoteConnectionFactory().CreateRemoteConnection("gtx");
+
+ [IgnoreIfTransactionsNotSupportedFact]
+ public async Task ShouldSupportRemoteTransactionsCommit()
+ {
+ var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+ var tx = g.Tx();
+ var gtx = tx.Begin();
+ await gtx.AddV("person").Property("name", "florian").Promise(t => t.Iterate()).ConfigureAwait(false);
+ await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate()).ConfigureAwait(false);
+
+ // Assert within the transaction
+ var count = await gtx.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+ Assert.Equal(2, count);
+
+ // Vertices should not be visible in a different transaction before commiting
+ count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+ Assert.Equal(0, count);
+
+ // Now commit changes to test outside of the transaction
+ await tx.CommitAsync().ConfigureAwait(false);
+
+ count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+ Assert.Equal(2, count);
+ }
+
+ [IgnoreIfTransactionsNotSupportedFact]
+ public async Task ShouldSupportRemoteTransactionsRollback()
+ {
+ var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+ var tx = g.Tx();
+ var gtx = tx.Begin();
+ await gtx.AddV("person").Property("name", "florian").Promise(t => t.Iterate()).ConfigureAwait(false);
+ await gtx.AddV("person").Property("name", "josh").Promise(t => t.Iterate()).ConfigureAwait(false);
+
+ // Assert within the transaction
+ var count = await gtx.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+ Assert.Equal(2, count);
+
+ // Now rollback changes to test outside of the transaction
+ await tx.RollbackAsync().ConfigureAwait(false);
+
+ count = await g.V().Count().Promise(t => t.Next()).ConfigureAwait(false);
+ Assert.Equal(0, count);
+
+ g.V().Count().Next();
+ }
+
+ public void Dispose()
+ {
+ EmptyGraph();
+ }
+
+ private void EmptyGraph()
+ {
+ var g = AnonymousTraversalSource.Traversal().WithRemote(_connection);
+ g.V().Drop().Iterate();
+ }
+ }
+
+ public sealed class IgnoreIfTransactionsNotSupportedFact : FactAttribute
+ {
+ public IgnoreIfTransactionsNotSupportedFact()
+ {
+ if (!TransactionsSupported)
+ {
+ Skip = "Transactions not supported";
+ }
+ }
+
+ private static bool TransactionsSupported =>
+ Convert.ToBoolean(Environment.GetEnvironmentVariable("TEST_TRANSACTIONS"));
+ }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/Remote/DriverRemoteTransactionTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/Remote/DriverRemoteTransactionTests.cs
new file mode 100644
index 0000000..51f2fc3
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Driver/Remote/DriverRemoteTransactionTests.cs
@@ -0,0 +1,57 @@
+#region License
+
+/*
+ * 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.
+ */
+
+#endregion
+
+using System;
+using Gremlin.Net.Driver;
+using Gremlin.Net.Driver.Remote;
+using Gremlin.Net.Process.Traversal;
+using Moq;
+using Xunit;
+
+namespace Gremlin.Net.UnitTest.Driver.Remote
+{
+ public class DriverRemoteTransactionTests
+ {
+ [Fact]
+ public void ShouldNotAllowBeginMoreThanOnce()
+ {
+ var g = AnonymousTraversalSource.Traversal()
+ .WithRemote(new DriverRemoteConnection(Mock.Of<IGremlinClient>()));
+ var tx = g.Tx();
+ tx.Begin();
+
+ Assert.Throws<InvalidOperationException>(() => tx.Begin());
+ }
+
+ [Fact]
+ public void ShouldNotSupportChildTransactions()
+ {
+ var g = AnonymousTraversalSource.Traversal()
+ .WithRemote(new DriverRemoteConnection(Mock.Of<IGremlinClient>()));
+ var tx = g.Tx();
+
+ var gtx = tx.Begin();
+ Assert.Throws<InvalidOperationException>(() => gtx.Tx());
+ }
+ }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/test/pom.xml b/gremlin-dotnet/test/pom.xml
index d110391..f1be8e2 100644
--- a/gremlin-dotnet/test/pom.xml
+++ b/gremlin-dotnet/test/pom.xml
@@ -96,6 +96,16 @@ limitations under the License.
<extensions>true</extensions>
<configuration>
<skip>${skipTests}</skip>
+ <!--
+ transaction testing is disabled unless the -DincludeNeo4j flag enables the include-neo4j
+ maven profile which is a standard profile we use to add neo4j to testing explicitly - for
+ npm we set this TEST_TRANSACTIONS environment variable that can be accessed in tests to
+ determine if we skip transaction oriented tests or not. without neo4j we can't test tx()
+ so this is disabled by default and enabled in the include-neo4j profile below
+ -->
+ <environment>
+ <TEST_TRANSACTIONS>false</TEST_TRANSACTIONS>
+ </environment>
</configuration>
</plugin>
<plugin>
@@ -113,6 +123,11 @@ limitations under the License.
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.tinkerpop</groupId>
+ <artifactId>neo4j-gremlin</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
@@ -206,5 +221,127 @@ limitations under the License.
</plugins>
</build>
</profile>
+ <!--
+ This profile will include neo4j for purposes of transactional testing within Gremlin Server.
+ Tests that require neo4j specifically will be "ignored" if this profile is not turned on.
+ -->
+ <profile>
+ <id>include-neo4j</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ <property>
+ <name>includeNeo4j</name>
+ </property>
+ </activation>
+ <properties>
+ <packaging.type>dotnet-integration-test</packaging.type>
+ </properties>
+ <build>
+ <plugins>
+ <!-- with neo4j present we can enable transaction testing -->
+ <plugin>
+ <groupId>org.eobjects.build</groupId>
+ <artifactId>dotnet-maven-plugin</artifactId>
+ <configuration>
+ <environment>
+ <TEST_TRANSACTIONS>true</TEST_TRANSACTIONS>
+ </environment>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.neo4j</groupId>
+ <artifactId>neo4j-tinkerpop-api-impl</artifactId>
+ <version>0.9-3.4.0</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.neo4j</groupId>
+ <artifactId>neo4j-kernel</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-text</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.github.ben-manes.caffeine</groupId>
+ <artifactId>caffeine</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-reflect</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-nop</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-all</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ <version>2.11.8</version>
+ </dependency>
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-reflect</artifactId>
+ <version>2.11.8</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-core</artifactId>
+ <version>5.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>4.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.neo4j</groupId>
+ <artifactId>neo4j-kernel</artifactId>
+ <version>3.4.11</version>
+ <exclusions>
+ <exclusion>
+ <groupId>io.netty</groupId>
+ <artifactId>netty-all</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
\ No newline at end of file