You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2021/06/23 22:13:45 UTC

[tinkerpop] branch TINKERPOP-2557 created (now 2088179)

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a change to branch TINKERPOP-2557
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


      at 2088179  TINKERPOP-2557 wip

This branch includes the following new commits:

     new 2088179  TINKERPOP-2557 wip

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[tinkerpop] 01/01: TINKERPOP-2557 wip

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2557
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 2088179e83ecc2ac42d98ee39cb1ba2f844f4597
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Wed Jun 23 18:13:01 2021 -0400

    TINKERPOP-2557 wip
---
 .../gremlin/process/traversal/Bytecode.java        |   2 +-
 gremlin-javascript/pom.xml                         | 171 +++++++++++++++++++++
 .../gremlin-javascript/lib/driver/client.js        |  18 ++-
 .../gremlin-javascript/lib/driver/connection.js    |   2 +-
 .../lib/driver/driver-remote-connection.js         |  26 +++-
 .../lib/driver/remote-connection.js                |  33 +++-
 .../lib/process/anonymous-traversal.js             |   2 +-
 .../gremlin-javascript/lib/process/bytecode.js     |  22 ++-
 .../lib/process/graph-traversal.js                 |  20 ++-
 .../gremlin-javascript/lib/process/transaction.js  |  70 +++++++++
 .../gremlin-javascript/lib/process/translator.js   |   2 +-
 .../lib/process/traversal-strategy.js              |  10 ++
 .../lib/structure/io/type-serializers.js           |   2 +-
 .../gremlin-javascript/package-lock.json           |  10 +-
 .../test/integration/client-tests.js               |   2 +-
 .../test/integration/remote-connection-tests.js    |   2 +-
 .../test/integration/sasl-authentication-tests.js  |   2 +-
 .../test/integration/session-client-tests.js       |   4 +-
 .../test/integration/traversal-test.js             |  83 +++++++++-
 .../gremlin-javascript/test/unit/exports-test.js   |   3 +-
 .../test/unit/traversal-strategy-test.js           |  51 ++++++
 .../gremlin-javascript/test/unit/traversal-test.js |   2 +-
 .../src/test/scripts/generate-all.groovy           |   8 +
 .../src/test/scripts/test-server-start.groovy      |  13 ++
 pom.xml                                            |   3 +-
 25 files changed, 527 insertions(+), 36 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
index 56422c7..dc8eca3 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java
@@ -171,7 +171,7 @@ public final class Bytecode implements Cloneable, Serializable {
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
-        Bytecode bytecode = (Bytecode) o;
+        final Bytecode bytecode = (Bytecode) o;
         return Objects.equals(sourceInstructions, bytecode.sourceInstructions) &&
                 Objects.equals(stepInstructions, bytecode.stepInstructions);
     }
diff --git a/gremlin-javascript/pom.xml b/gremlin-javascript/pom.xml
index 5eb2f06..07e4122 100644
--- a/gremlin-javascript/pom.xml
+++ b/gremlin-javascript/pom.xml
@@ -65,6 +65,16 @@ 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>commons-io</groupId>
+                        <artifactId>commons-io</artifactId>
+                        <version>${commons.io.version}</version>
+                    </dependency>
+                    <dependency>
                         <groupId>log4j</groupId>
                         <artifactId>log4j</artifactId>
                         <version>${log4j.version}</version>
@@ -243,6 +253,10 @@ file.write(file.getText("UTF-8").replaceFirst(/"version": "(.*)",/, "\"version\"
                     <workingDirectory>src/main/javascript/gremlin-javascript</workingDirectory>
                     <nodeVersion>${node.version}</nodeVersion>
                     <npmVersion>${npm.version}</npmVersion>
+
+                    <environmentVariables>
+                        <TEST_TRANSACTIONS>false</TEST_TRANSACTIONS>
+                    </environmentVariables>
                 </configuration>
             </plugin>
             <!--
@@ -280,6 +294,163 @@ file.write(file.getText("UTF-8").replaceFirst(/"version": "(.*)",/, "\"version\"
     </build>
     <profiles>
         <!--
+          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>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>com.github.eirslett</groupId>
+                        <artifactId>frontend-maven-plugin</artifactId>
+                        <configuration>
+                            <environmentVariables combine.children="override">
+                                <TEST_TRANSACTIONS>true</TEST_TRANSACTIONS>
+                            </environmentVariables>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.codehaus.gmavenplus</groupId>
+                        <artifactId>gmavenplus-plugin</artifactId>
+                        <dependencies>
+                            <dependency>
+                                <groupId>org.apache.tinkerpop</groupId>
+                                <artifactId>gremlin-core</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.apache.tinkerpop</groupId>
+                                <artifactId>gremlin-server</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.apache.tinkerpop</groupId>
+                                <artifactId>gremlin-test</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.apache.tinkerpop</groupId>
+                                <artifactId>neo4j-gremlin</artifactId>
+                                <version>${project.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>commons-io</groupId>
+                                <artifactId>commons-io</artifactId>
+                                <version>${commons.io.version}</version>
+                            </dependency>
+                            <dependency>
+                                <groupId>log4j</groupId>
+                                <artifactId>log4j</artifactId>
+                                <version>${log4j.version}</version>
+                                <scope>runtime</scope>
+                            </dependency>
+                            <dependency>
+                                <groupId>org.codehaus.groovy</groupId>
+                                <artifactId>groovy-all</artifactId>
+                                <version>${groovy.version}</version>
+                                <type>pom</type>
+                                <scope>runtime</scope>
+                            </dependency>
+                            <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>
+
+        <!--
         Provides a way to deploy the gremlin-javascript GLV to npm. This cannot be part of the standard maven execution
         because npm does not have a staging environment like sonatype for releases. As soon as the release is
         published it is public. In our release workflow, deploy occurs prior to vote on the release and we can't
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/client.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/client.js
index 99fde77..3e1a1ce 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/client.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/client.js
@@ -20,7 +20,7 @@
 
 const utils = require('../utils');
 const Connection = require('./connection');
-const Bytecode = require('../process/bytecode');
+const Bytecode = require('../process/bytecode').Bytecode;
 
 /**
  * A {@link Client} contains methods to send messages to a Gremlin Server.
@@ -48,7 +48,7 @@ class Client {
     this._options = options;
     if (this._options.processor === 'session') {
       // compatibility with old 'session' processor setting
-      this._options.session = options.session || utils.getUuid()
+      this._options.session = options.session || utils.getUuid();
     }
     if (this._options.session) {
       // re-assign processor to 'session' when in session mode
@@ -93,16 +93,20 @@ class Client {
       aliases: { 'g': this._options.traversalSource || 'g' }
     }, requestOptions)
 
+    if (this._options.session && this._options.processor === 'session') {
+      args['session'] = this._options.session;
+    }
+
     if (message instanceof Bytecode) {
-      return this._connection.submit('traversal','bytecode', args, requestIdOverride);
+      if (this._options.session && this._options.processor === 'session') {
+        return this._connection.submit('session', 'bytecode', args, requestIdOverride);
+      } else {
+        return this._connection.submit('traversal', 'bytecode', args, requestIdOverride);
+      }
     } else if (typeof message === 'string') {
       args['bindings'] = bindings;
       args['language'] = 'gremlin-groovy';
       args['accept'] = this._connection.mimeType;
-
-      if (this._options.session && this._options.processor === 'session') {
-        args['session'] = this._options.session;
-      }
       return this._connection.submit(this._options.processor || '','eval', args, requestIdOverride);
     } else {
       throw new TypeError("message must be of type Bytecode or string");
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/connection.js
index cf46475..6ba2484 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/connection.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/connection.js
@@ -29,7 +29,7 @@ const utils = require('../utils');
 const serializer = require('../structure/io/graph-serializer');
 const ResultSet = require('./result-set');
 const ResponseError = require('./response-error');
-const Bytecode = require('../process/bytecode');
+const Bytecode = require('../process/bytecode').Bytecode;
 
 const responseStatusCode = {
   success: 200,
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js
index f7d626d..1ab821b 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js
@@ -25,7 +25,9 @@
 const rcModule = require('./remote-connection');
 const RemoteConnection = rcModule.RemoteConnection;
 const RemoteTraversal = rcModule.RemoteTraversal;
+const utils = require('../utils');
 const Client = require('./client');
+const graphOp = require('../process/bytecode').graphOp;
 const OptionsStrategy = require('../process/traversal-strategy').OptionsStrategy;
 
 /**
@@ -49,8 +51,8 @@ class DriverRemoteConnection extends RemoteConnection {
    * @param {Object} [options.headers] An associative array containing the additional header key/values for the initial request.
    * @constructor
    */
-  constructor(url, options) {
-    super(url);
+  constructor(url, options = {}) {
+    super(url, options);
     this._client = new Client(url, options);
   }
 
@@ -59,6 +61,7 @@ class DriverRemoteConnection extends RemoteConnection {
     return this._client.open();
   }
 
+  /** @override */
   get isOpen() {
     return this._client.isOpen;
   }
@@ -79,10 +82,29 @@ class DriverRemoteConnection extends RemoteConnection {
         }
       }
     }
+
     return this._client.submit(bytecode, null, requestOptions).then(result => new RemoteTraversal(result.toArray()));
   }
 
   /** @override */
+  begin() {
+    // make sure a fresh session is used when starting a new transaction
+    const copiedOptions = Object.assign({}, this.options);
+    copiedOptions.session = utils.getUuid();
+    return new DriverRemoteConnection(this.url, copiedOptions);
+  }
+
+  /** @override */
+  commit() {
+    return this._client.submit(graphOp.commit, null)
+  }
+
+  /** @override */
+  rollback() {
+    return this._client.submit(graphOp.rollback, null)
+  }
+
+  /** @override */
   close() {
     return this._client.close();
   }
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js
index d53f320..db8fc97 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js
@@ -30,8 +30,14 @@ const TraversalStrategy = require('../process/traversal-strategy').TraversalStra
  * returning results.
  */
 class RemoteConnection {
-  constructor(url) {
+
+  /**
+   * @param {String} url The resource uri.
+   * @param {Object} [options] The connection options.
+   */
+  constructor(url, options) {
     this.url = url;
+    this.options = options;
   }
 
   /**
@@ -61,6 +67,26 @@ class RemoteConnection {
   };
 
   /**
+   * @returns {RemoteConnection}
+   */
+  begin() {
+    throw new Error('begin() must be implemented');
+  }
+
+  /**
+   * @returns {Promise}
+   */
+  commit() {
+    throw new Error('commit() must be implemented');
+  }
+  /**
+   * @returns {Promise}
+   */
+  rollback() {
+    throw new Error('rollback() must be implemented');
+  }
+
+  /**
    * Closes the connection, if its not already opened.
    * @returns {Promise}
    */
@@ -86,7 +112,10 @@ class RemoteStrategy extends TraversalStrategy {
    * @param {RemoteConnection} connection
    */
   constructor(connection) {
-    super();
+    // gave this a fqcn that has a local "js:" prefix since this strategy isn't sent as bytecode to the server.
+    // this is a sort of local-only strategy that actually executes client side. not sure if this prefix is the
+    // right way to name this or not, but it should have a name to identify it.
+    super("js:RemoteStrategy");
     this.connection = connection;
   }
 
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/anonymous-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/anonymous-traversal.js
index 63e1e77..31757cc 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/anonymous-traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/anonymous-traversal.js
@@ -52,7 +52,7 @@ class AnonymousTraversalSource {
   /**
    * Creates a {@link GraphTraversalSource} binding a {@link RemoteConnection} to a remote {@link Graph} instances as its
    * reference so that traversals spawned from it will execute over that reference.
-   * @param {GraphTraversalSource} remoteConnection
+   * @param {RemoteConnection} remoteConnection
    * @return {GraphTraversalSource}
    */
   withRemote(remoteConnection) {
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js
index 97d369e..8f3177c 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js
@@ -96,4 +96,24 @@ class Bytecode {
   }
 }
 
-module.exports = Bytecode;
+/**
+ * Adds a new source instructions
+ * @param {String} name
+ * @param {Array} values
+ * @returns {Bytecode}
+ */
+function _createGraphOp(name, values) {
+  const bc = new Bytecode();
+  bc.addSource(name, values);
+  return bc;
+}
+
+const graphOp = {
+  commit: _createGraphOp("tx", ["commit"]),
+  rollback: _createGraphOp("tx", ["rollback"])
+}
+
+module.exports = {
+  Bytecode,
+  graphOp
+};
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
index 9421730..38d910c 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
@@ -23,12 +23,11 @@
 'use strict';
 
 const { Traversal } = require('./traversal');
+const { Transaction } = require('./transaction');
 const remote = require('../driver/remote-connection');
-const utils = require('../utils');
-const Bytecode = require('./bytecode');
+const Bytecode = require('./bytecode').Bytecode;
 const { TraversalStrategies, VertexProgramStrategy, OptionsStrategy } = require('./traversal-strategy');
 
-
 /**
  * Represents the primary DSL of the Gremlin traversal machine.
  */
@@ -48,16 +47,27 @@ class GraphTraversalSource {
     this.bytecode = bytecode || new Bytecode();
     this.graphTraversalSourceClass = graphTraversalSourceClass || GraphTraversalSource;
     this.graphTraversalClass = graphTraversalClass || GraphTraversal;
+
+    // in order to keep the constructor unchanged within 3.5.x we can try to pop the RemoteConnection out of the
+    // TraversalStrategies. keeping this unchanged will allow user DSLs to not take a break.
+    // TODO: refactor this to be nicer in 3.6.0 when we can take a breaking change
+    const strat = traversalStrategies.strategies.find(ts => ts.fqcn === "js:RemoteStrategy");
+    this.remoteConnection = strat !== undefined ? strat.connection : undefined;
   }
 
   /**
-   * @param remoteConnection
+   * @param {RemoteConnection} remoteConnection
    * @returns {GraphTraversalSource}
    */
   withRemote(remoteConnection) {
     const traversalStrategy = new TraversalStrategies(this.traversalStrategies);
     traversalStrategy.addStrategy(new remote.RemoteStrategy(remoteConnection));
-    return new this.graphTraversalSourceClass(this.graph, traversalStrategy, new Bytecode(this.bytecode), this.graphTraversalSourceClass, this.graphTraversalClass);
+    return new this.graphTraversalSourceClass(this.graph, traversalStrategy, new Bytecode(this.bytecode),
+      this.graphTraversalSourceClass, this.graphTraversalClass);
+  }
+
+  tx() {
+    return new Transaction(this);
   }
 
   /**
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/transaction.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/transaction.js
new file mode 100644
index 0000000..ec0cf8e
--- /dev/null
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/transaction.js
@@ -0,0 +1,70 @@
+/*
+ *  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.
+ */
+'use strict';
+
+const remote = require('../driver/remote-connection');
+const Bytecode = require('./bytecode').Bytecode;
+const { TraversalStrategies } = require('./traversal-strategy');
+
+class Transaction {
+  constructor(g) {
+    this._g = g;
+  }
+
+  begin() {
+    this._sessionBasedConnection = this._g.remoteConnection.begin();
+    const traversalStrategy = new TraversalStrategies();
+    traversalStrategy.addStrategy(new remote.RemoteStrategy(this._sessionBasedConnection));
+    return new this._g.graphTraversalSourceClass(this._g.graph, traversalStrategy, new Bytecode(this._g.bytecode),
+      this._g.graphTraversalSourceClass, this._g.graphTraversalClass);
+  }
+
+  /**
+   * @returns {Promise}
+   */
+  commit() {
+    return this._sessionBasedConnection.commit().then(() => this.close());
+  }
+
+  /**
+   * @returns {Promise}
+   */
+  rollback() {
+    return this._sessionBasedConnection.rollback().then(() => this.close());
+  }
+
+  /**
+   * Returns true if transaction is open.
+   * @returns {Boolean}
+   */
+  get isOpen() {
+    this._sessionBasedConnection.isOpen;
+  }
+
+  /**
+   * @returns {Promise}
+   */
+  close() {
+    this._sessionBasedConnection.close();
+  }
+}
+
+module.exports = {
+  Transaction
+};
\ No newline at end of file
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/translator.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/translator.js
index 46551a4..c1a2f77 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/translator.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/translator.js
@@ -19,7 +19,7 @@
 'use strict';
 
 const Traversal = require('./traversal').Traversal;
-const Bytecode = require('./bytecode');
+const Bytecode = require('./bytecode').Bytecode;
 
 /**
  * Class to translate glv bytecode steps into executable Gremlin-Groovy script
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal-strategy.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal-strategy.js
index 37816bd..7539a26 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal-strategy.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal-strategy.js
@@ -45,6 +45,16 @@ class TraversalStrategies {
     this.strategies.push(strategy);
   }
 
+  /** @param {TraversalStrategy} strategy */
+  removeStrategy(strategy) {
+    const idx = this.strategies.findIndex(s => s.fqcn === strategy.fqcn);
+    if (idx !== -1) {
+      return this.strategies.splice(idx, 1)[0];
+    }
+
+    return undefined;
+  }
+
   /**
    * @param {Traversal} traversal
    * @returns {Promise}
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
index bd71c5b..cab5d95 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/type-serializers.js
@@ -24,7 +24,7 @@
 
 const t = require('../../process/traversal');
 const ts = require('../../process/traversal-strategy');
-const Bytecode = require('../../process/bytecode');
+const Bytecode = require('../../process/bytecode').Bytecode;
 const g = require('../graph');
 const utils = require('../../utils');
 
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json b/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json
index fa9f9ab..35baa25 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/package-lock.json
@@ -1666,7 +1666,7 @@
     "mocha": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
-      "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=",
+      "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
       "dev": true,
       "requires": {
         "browser-stdout": "1.3.1",
@@ -1685,13 +1685,13 @@
         "commander": {
           "version": "2.15.1",
           "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
-          "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=",
+          "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
           "dev": true
         },
         "debug": {
           "version": "3.1.0",
           "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
           "dev": true,
           "requires": {
             "ms": "2.0.0"
@@ -1700,7 +1700,7 @@
         "glob": {
           "version": "7.1.2",
           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-          "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
+          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
           "dev": true,
           "requires": {
             "fs.realpath": "^1.0.0",
@@ -1723,7 +1723,7 @@
         "supports-color": {
           "version": "5.4.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
-          "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=",
+          "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
           "dev": true,
           "requires": {
             "has-flag": "^3.0.0"
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js
index 56c7c58..b6d59fd 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js
@@ -20,7 +20,7 @@
 'use strict';
 
 const assert = require('assert');
-const Bytecode = require('../../lib/process/bytecode');
+const Bytecode = require('../../lib/process/bytecode').Bytecode;
 const graphModule = require('../../lib/structure/graph');
 const helper = require('../helper');
 const t = require('../../lib/process/traversal');
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/remote-connection-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/remote-connection-tests.js
index f86e252..a4b8d15 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/remote-connection-tests.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/remote-connection-tests.js
@@ -23,7 +23,7 @@
 'use strict';
 
 const assert = require('assert');
-const Bytecode = require('../../lib/process/bytecode');
+const Bytecode = require('../../lib/process/bytecode').Bytecode;
 const graphModule = require('../../lib/structure/graph');
 const helper = require('../helper');
 
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js
index 2eaa622..e2fc800 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/sasl-authentication-tests.js
@@ -22,7 +22,7 @@
 const assert = require('assert');
 const AssertionError = require('assert');
 const { traversal } = require('../../lib/process/anonymous-traversal');
-const Bytecode = require('../../lib/process/bytecode');
+const Bytecode = require('../../lib/process/bytecode').Bytecode;
 const helper = require('../helper');
 const DriverRemoteConnection = require('../../lib/driver/driver-remote-connection');
 const PlainTextSaslAuthenticator = require('../../lib/driver/auth/plain-text-sasl-authenticator');
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/session-client-tests.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/session-client-tests.js
index 87190d0..ea91ac8 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/session-client-tests.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/session-client-tests.js
@@ -24,7 +24,7 @@
 'use strict';
 
 const assert = require('assert');
-const Bytecode = require('../../lib/process/bytecode');
+const Bytecode = require('../../lib/process/bytecode').Bytecode;
 const graphModule = require('../../lib/structure/graph');
 const helper = require('../helper');
 
@@ -48,7 +48,7 @@ describe('Client', function () {
         });
     });
 
-    it('should use golbal cache in session', function () {
+    it('should use global cache in session', function () {
       return client.submit("x = [0, 1, 2, 3, 4, 5]")
         .then(function (result) {
           assert.ok(result);
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
index 9cf62cb..359378a 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/traversal-test.js
@@ -32,11 +32,12 @@ const { traversal } = require('../../lib/process/anonymous-traversal');
 const { GraphTraversalSource, GraphTraversal, statics } = require('../../lib/process/graph-traversal');
 const { SubgraphStrategy, ReadOnlyStrategy,
         ReservedKeysVerificationStrategy, EdgeLabelVerificationStrategy } = require('../../lib/process/traversal-strategy');
-const Bytecode = require('../../lib/process/bytecode');
+const Bytecode = require('../../lib/process/bytecode').Bytecode;
 const helper = require('../helper');
 const __ = statics;
 
 let connection;
+let txConnection;
 
 class SocialTraversal extends GraphTraversal {
   constructor(graph, traversalStrategies, bytecode) {
@@ -211,4 +212,84 @@ describe('Traversal', function () {
       return g.V().repeat(__.both()).iterate().then(() => assert.fail("should have tanked"), (err) => assert.strictEqual(err.statusCode, 598));
     });
   });
+  describe("should support remote transactions - commit", function() {
+    before(function () {
+      if (process.env.TEST_TRANSACTIONS !== "true") return this.skip();
+
+      txConnection = helper.getConnection('gtx');
+      return txConnection.open();
+    });
+    after(function () {
+      if (process.env.TEST_TRANSACTIONS === "true") {
+        // neo4j gets re-used and has to be cleaned up per test that uses it
+        const g = traversal().withRemote(txConnection);
+        return g.V().drop().iterate().then(() => {
+          return txConnection.close()
+        });
+      }
+    });
+    it('should commit a simple transaction', function () {
+      const g = traversal().withRemote(txConnection);
+      const tx = g.tx();
+      const gtx = tx.begin();
+      return Promise.all([
+        gtx.addV("person").property("name", "jorge").iterate(),
+        gtx.addV("person").property("name", "josh").iterate()
+      ]).then(() => {
+        return gtx.V().count().next();
+      }).then(function (r) {
+        // assert within the transaction....
+        assert.ok(r);
+        assert.strictEqual(r.value, 2);
+
+        // now commit changes to test outside of the transaction
+        return tx.commit();
+      }).then(() => {
+        return g.V().count().next();
+      }).then(function (r) {
+        assert.ok(r);
+        assert.strictEqual(r.value, 2);
+      });
+    });
+  });
+  describe("should support remote transactions - rollback", function() {
+    before(function () {
+      if (process.env.TEST_TRANSACTIONS !== "true") return this.skip();
+
+      txConnection = helper.getConnection('gtx');
+      return txConnection.open();
+    });
+    after(function () {
+      if (process.env.TEST_TRANSACTIONS === "true") {
+        // neo4j gets re-used and has to be cleaned up per test that uses it
+        const g = traversal().withRemote(txConnection);
+        return g.V().drop().iterate().then(() => {
+          return txConnection.close()
+        });
+      }
+    });
+    it('should rollback a simple transaction', function() {
+      const g = traversal().withRemote(txConnection);
+      const tx = g.tx();
+      const gtx = tx.begin();
+      return Promise.all([
+        gtx.addV("person").property("name", "jorge").iterate(),
+        gtx.addV("person").property("name", "josh").iterate()
+      ]).then(() => {
+        return gtx.V().count().next();
+      }).then(function (r) {
+        // assert within the transaction....
+        assert.ok(r);
+        assert.strictEqual(r.value, 2);
+
+        // now rollback changes to test outside of the transaction
+        return tx.rollback();
+      }).then(() => {
+        return g.V().count().next();
+      }).then(function (r) {
+        assert.ok(r);
+        assert.strictEqual(r.value, 0);
+      });
+    });
+  });
 });
\ No newline at end of file
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/exports-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/exports-test.js
index 4273604..f56cf47 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/exports-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/exports-test.js
@@ -29,7 +29,8 @@ describe('API', function () {
   it('should export fields under process', function () {
     assert.ok(glvModule);
     assert.ok(glvModule.process);
-    assert.strictEqual(typeof glvModule.process.Bytecode, 'function');
+    assert.strictEqual(typeof glvModule.process.Bytecode.Bytecode, 'function');
+    assert.strictEqual(typeof glvModule.process.Bytecode.graphOp, 'object');
     assert.strictEqual(typeof glvModule.process.EnumValue, 'function');
     assert.strictEqual(typeof glvModule.process.P, 'function');
     assert.strictEqual(typeof glvModule.process.Traversal, 'function');
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-strategy-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-strategy-test.js
new file mode 100644
index 0000000..fbfbcf1
--- /dev/null
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-strategy-test.js
@@ -0,0 +1,51 @@
+/*
+ *  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.
+ */
+
+'use strict';
+
+const assert = require('assert');
+const { TraversalStrategies, OptionsStrategy, ConnectiveStrategy } = require('../../lib/process/traversal-strategy');
+
+describe('TraversalStrategies', function () {
+
+  describe('#removeStrategy()', function () {
+    it('should remove strategy', function () {
+      const ts = new TraversalStrategies()
+      ts.addStrategy(new ConnectiveStrategy());
+      ts.addStrategy(new OptionsStrategy({x: 123}));
+      assert.strictEqual(ts.strategies.length, 2);
+
+      const c = new OptionsStrategy({x: 123});
+      const os = ts.removeStrategy(c);
+      assert.strictEqual(os.fqcn, c.fqcn);
+      assert.strictEqual(ts.strategies.length, 1);
+
+      ts.removeStrategy(new ConnectiveStrategy());
+      assert.strictEqual(ts.strategies.length, 0);
+    });
+
+    it('should not find anything to remove', function () {
+      const ts = new TraversalStrategies()
+      ts.addStrategy(new OptionsStrategy({x: 123}));
+      assert.strictEqual(ts.strategies.length, 1);
+      ts.removeStrategy(new ConnectiveStrategy());
+      assert.strictEqual(ts.strategies.length, 1);
+    });
+  });
+});
\ No newline at end of file
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js
index 273f2c4..51b55b2 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/traversal-test.js
@@ -30,7 +30,7 @@ const t = require('../../lib/process/traversal');
 const gt = require('../../lib/process/graph-traversal');
 const V = gt.statics.V;
 const P = t.P;
-const Bytecode = require('../../lib/process/bytecode');
+const Bytecode = require('../../lib/process/bytecode').Bytecode;
 const TraversalStrategies = require('../../lib/process/traversal-strategy').TraversalStrategies;
 
 describe('Traversal', function () {
diff --git a/gremlin-server/src/test/scripts/generate-all.groovy b/gremlin-server/src/test/scripts/generate-all.groovy
index f49351b..2400908 100644
--- a/gremlin-server/src/test/scripts/generate-all.groovy
+++ b/gremlin-server/src/test/scripts/generate-all.groovy
@@ -67,3 +67,11 @@ globals << [gcrew : traversal().withEmbedded(crew).withStrategies(ReferenceEleme
 globals << [ggraph : traversal().withEmbedded(graph).withStrategies(ReferenceElementStrategy)]
 globals << [ggrateful : traversal().withEmbedded(grateful).withStrategies(ReferenceElementStrategy)]
 globals << [gsink : traversal().withEmbedded(sink).withStrategies(ReferenceElementStrategy)]
+
+// dynamically detect existence of gtx as it may or may not be present depending on the -DincludeNeo4j
+// and the configuration of the particular server instance.
+def dynamicGtx = context.getBindings(javax.script.ScriptContext.GLOBAL_SCOPE)["tx"]
+if (dynamicGtx != null)
+    globals << [gtx : traversal().withEmbedded(dynamicGtx).withStrategies(ReferenceElementStrategy)]
+
+globals
diff --git a/gremlin-server/src/test/scripts/test-server-start.groovy b/gremlin-server/src/test/scripts/test-server-start.groovy
index ca59745..98e81e5 100644
--- a/gremlin-server/src/test/scripts/test-server-start.groovy
+++ b/gremlin-server/src/test/scripts/test-server-start.groovy
@@ -34,8 +34,20 @@ import org.apache.tinkerpop.gremlin.server.Settings
 ////////////////////////////////////////////////////////////////////////////////
 
 if (Boolean.parseBoolean(skipTests)) return
+def testTransactions = System.getProperty("includeNeo4j") != null
+
+if (testTransactions) {
+    // clean up prior neo4j instances
+    def tempNeo4jDir = new java.io.File("/tmp/neo4j")
+    if (tempNeo4jDir.exists()) {
+        log.info("Cleaning up prior Neo4j test instances for ${executionName}")
+        org.apache.commons.io.FileUtils.deleteDirectory(tempNeo4jDir)
+    }
+}
 
 log.info("Starting Gremlin Server instances for native testing of ${executionName}")
+log.info("Transactions validated (enabled with -DincludeNeo4j and only available on port 45940): " + testTransactions)
+
 def settings = Settings.read("${settingsFile}")
 settings.graphs.graph = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
 settings.graphs.classic = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
@@ -43,6 +55,7 @@ settings.graphs.modern = gremlinServerDir + "/src/test/scripts/tinkergraph-empty
 settings.graphs.crew = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
 settings.graphs.grateful = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
 settings.graphs.sink = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties"
+if (testTransactions) settings.graphs.tx = gremlinServerDir + "/src/test/scripts/neo4j-empty.properties"
 settings.scriptEngines["gremlin-groovy"].plugins["org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin"].files = [gremlinServerDir + "/src/test/scripts/generate-all.groovy"]
 settings.port = 45940
 
diff --git a/pom.xml b/pom.xml
index ea378c2..f589fb7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,7 @@ limitations under the License.
     <properties>
         <commons.configuration.version>2.7</commons.configuration.version>
         <commons.lang.version>2.6</commons.lang.version>
+        <commons.io.version>2.8.0</commons.io.version>
         <commons.lang3.version>3.11</commons.lang3.version>
         <commons.text.version>1.9</commons.text.version>
         <groovy.version>2.5.14</groovy.version>
@@ -826,7 +827,7 @@ limitations under the License.
             <dependency>
                 <groupId>commons-io</groupId>
                 <artifactId>commons-io</artifactId>
-                <version>2.8.0</version>
+                <version>${commons.io.version}</version>
             </dependency>
             <dependency>
                 <groupId>commons-codec</groupId>