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 2018/10/08 19:02:05 UTC

[tinkerpop] 01/12: Initial Commit for TINKERPOP-1959 solution.

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

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

commit ef34c12c7bf84cc812db8fe55bb09d594c84cc35
Author: Matthew Allen <ma...@runbox.com>
AuthorDate: Thu Aug 23 15:48:36 2018 +0100

    Initial Commit for TINKERPOP-1959 solution.
---
 .../glv/GraphTraversalSource.template              |  13 +++
 gremlin-javascript/glv/TraversalSource.template    |  16 +++-
 .../lib/driver/driver-remote-connection.js         |  29 ++++--
 .../lib/driver/remote-connection.js                |  28 +++++-
 .../gremlin-javascript/lib/process/bytecode.js     |  69 +++++++++++++-
 .../lib/process/graph-traversal.js                 |  13 +++
 .../gremlin-javascript/lib/process/traversal.js    |  16 +++-
 .../test/integration/traversal-test.js             |  18 ++++
 .../gremlin-javascript/test/unit/eval-test.js      | 104 +++++++++++++++++++++
 .../gremlin/server/gremlin-server-integration.yaml |   1 +
 10 files changed, 291 insertions(+), 16 deletions(-)

diff --git a/gremlin-javascript/glv/GraphTraversalSource.template b/gremlin-javascript/glv/GraphTraversalSource.template
index 58be16c..3f8e715 100644
--- a/gremlin-javascript/glv/GraphTraversalSource.template
+++ b/gremlin-javascript/glv/GraphTraversalSource.template
@@ -104,6 +104,19 @@ class GraphTraversal extends Traversal {
     return this;
   }
   <% } %>
+
+
+  /**
+   * Send a Gremlin-Groovy script to the server. If a script is not passed in 
+   * then the bytecode instructions will be converted to a script and sent.
+   * @param {string} gremlinScript The script to send to server
+   * @param {array} bindings Map of bindings
+   * @param {*} options Options to configure the script sending
+   */
+  eval(script, bindings) {
+    this.bytecode.addStep('eval', [ script, bindings ]);
+    return this._applyStrategies().then(() => this._getNext());
+  }
 }
 
 function callOnEmptyTraversal(fnName, args) {
diff --git a/gremlin-javascript/glv/TraversalSource.template b/gremlin-javascript/glv/TraversalSource.template
index 6965110..6afe868 100644
--- a/gremlin-javascript/glv/TraversalSource.template
+++ b/gremlin-javascript/glv/TraversalSource.template
@@ -79,15 +79,29 @@ class Traversal {
   }
 
   /**
+   * Send a Gremlin-Groovy script to the server. If a script is not passed in 
+   * then the bytecode instructions will be converted to a script and sent.
+   * @param {string} gremlinScript The script to send to server
+   * @param {array} bindings Map of bindings
+   * @param {*} options Options to configure the script sending
+   */
+  eval(script, bindings) {
+    this.bytecode.addStep('eval', [ script, bindings ]);
+    return this._applyStrategies().then(() => this._getNext());
+  }
+
+  /**
    * Synchronous iterator of traversers including
    * @private
    */
   _getNext() {
     while (this.traversers && this._traversersIteratorIndex < this.traversers.length) {
       let traverser = this.traversers[this._traversersIteratorIndex];
-      if (traverser.bulk > 0) {
+      if (traverser.bulk && traverser.bulk > 0) {
         traverser.bulk--;
         return { value: traverser.object, done: false };
+      } else if (traverser.bulk === undefined) {
+        return { value: traverser, done: true }
       }
       this._traversersIteratorIndex++;
     }
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 ef0242c..fb66aae 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
@@ -107,7 +107,7 @@ class DriverRemoteConnection extends RemoteConnection {
   }
 
   /** @override */
-  submit(bytecode, op, args, requestId) {
+  submit(bytecode, op, args, requestId, processor) {
     return this.open().then(() => new Promise((resolve, reject) => {
       if (requestId === null || requestId === undefined) {
         requestId = utils.getUuid();
@@ -116,27 +116,38 @@ class DriverRemoteConnection extends RemoteConnection {
           result: null
         };
       }
-      const message = bufferFromString(this._header + JSON.stringify(this._getRequest(requestId, bytecode, op, args)));
+
+      const message = bufferFromString(this._header + JSON.stringify(this._getRequest(requestId, bytecode, op, args, processor)));
       this._ws.send(message);
     }));
   }
 
-  _getRequest(id, bytecode, op, args) {
+  _getRequest(id, bytecode, op, args, processor) {
     if (args) {
       args = this._adaptArgs(args);
     }
-
+    
     return ({
       'requestId': { '@type': 'g:UUID', '@value': id },
       'op': op || 'bytecode',
-      'processor': 'traversal',
-      'args': args || {
-        'gremlin': this._writer.adaptObject(bytecode),
-        'aliases': { 'g': this.traversalSource }
-      }
+      // if using op eval need to ensure processor stays unset if caller didn't set it.
+      'processor': (!processor && op !== 'eval') ? 'traversal' : processor,
+      'args': this._getArgs(args || {
+          'gremlin': this._writer.adaptObject(bytecode)
+        },
+        op
+      )
     });
   }
 
+  _getArgs(args, op) {
+    if (args.aliases === undefined) {
+      args.aliases = { 'g': this.traversalSource };
+    }
+
+    return args;
+  }
+
   _handleMessage(data) {
     const response = this._reader.read(JSON.parse(data.toString()));
     if (response.requestId === null || response.requestId === undefined) {
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 be6f962..d93d89b 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
@@ -34,11 +34,12 @@ class RemoteConnection {
    * @abstract
    * @param {Bytecode} bytecode
    * @param {String} op Operation to perform, defaults to bytecode.
-   * @param {Object} args The arguments for the operation. Defaults to an associative array containing values for "aliases" and "gremlin" keyss.
+   * @param {Object} args The arguments for the operation. Defaults to an associative array containing values for "aliases" and "gremlin" keys.
    * @param {String} requestId A requestId for the current request. If none provided then a requestId is generated internally.
+   * @param {String} processor The processor to use on the connection.
    * @returns {Promise}
    */
-  submit(bytecode, op, args, requestId) {
+  submit(bytecode, op, args, requestId, processor) {
     throw new Error('submit() was not implemented');
   };
 }
@@ -66,7 +67,28 @@ class RemoteStrategy extends TraversalStrategy {
     if (traversal.traversers) {
       return Promise.resolve();
     }
-    return this.connection.submit(traversal.getBytecode()).then(function (remoteTraversal) {
+
+    let instructions = traversal.getBytecode();
+    let op = 'bytecode';
+    let processor = 'traversal';
+    let args = null;
+
+    // check if the last instruction is an eval statement
+    const bytecode = traversal.getBytecode();
+    if (bytecode.stepInstructions.length && bytecode.stepInstructions[bytecode.stepInstructions.length-1][0] === 'eval') {
+      const script = traversal.getBytecode().toScript();
+      op = 'eval';
+      processor = '';
+      args = {
+        'gremlin': script.script,
+        'bindings': script.bindings,
+        'language': 'gremlin-groovy',
+        'accept': 'application/json',
+      };
+      instructions = null;
+    }
+
+    return this.connection.submit(instructions, op, args, null, processor).then(function (remoteTraversal) {
       traversal.sideEffects = remoteTraversal.sideEffects;
       traversal.traversers = remoteTraversal.traversers;
     });
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 0f5ba15..fe96568 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
@@ -91,8 +91,73 @@ class Bytecode {
       (this.stepInstructions.length   > 0 ? JSON.stringify(this.stepInstructions) : '')
     );
   }
-}
 
+  /**
+   * Returns a script representations of the step instructions that can be used by standard eval operation.
+   * @returns {Object} An object containing a script string and bindings map.
+   */
+  toScript() {
+    let bindings = {};
+    let script = 'g';
+    let length = this.stepInstructions.length;
+
+    // if eval was passed a script then simply execute the given script.
+    if (this.stepInstructions[length - 1][0] === 'eval'
+      && this.stepInstructions[length - 1][1] !== undefined
+      && this.stepInstructions[length - 1][1] !== null
+    ) {
+      return {
+        script: this.stepInstructions[length - 1][1],
+        bindings: this.stepInstructions[length - 1][2]
+      }
+    }
+
+    if (this.stepInstructions[length - 1][0] === 'eval') {
+      this.stepInstructions.pop();
+      length = this.stepInstructions.length;
+    }
 
+    // build the script from the glv instructions.
+    let paramIdx = 1;
+    for (let i = 0; i < length; i++) {
+      const params = this.stepInstructions[i].slice(1);
+      script = script + '.' + this.stepInstructions[i][0] + '(';
+
+      if (params.length) {
+        for (let k = 0; k < params.length; k++) {
+          if (k > 0) {
+            script = script + ', ';
+          }
+
+          if (Object(params[k]) === params[k]) {
+            script = script + params[k].toString();
+          } else {
+            const prop = `p${paramIdx++}`;
+            script = script + prop;
+            
+            if (typeof params[k] === 'number') {
+              if (Number.isInteger(params[k])) {
+                bindings[prop] = Number.parseInt(params[k]);
+              } else {
+                bindings[prop] = Number.parseFloat(params[k]);
+              }
+            } else if (params[k] === undefined) {
+              bindings[prop] = null;
+            } else {
+              bindings[prop] = params[k];
+            }
+          }
+        }
+      }
+
+      script = script + ')';
+    }
+    
+    return {
+      script,
+      bindings
+    };
+  }
+}
 
-module.exports = Bytecode;
\ No newline at end of file
+module.exports = Bytecode;
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 edeb2cb..aa0259b 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
@@ -1132,6 +1132,19 @@ class GraphTraversal extends Traversal {
     return this;
   }
   
+
+
+  /**
+   * Send a Gremlin-Groovy script to the server. If a script is not passed in 
+   * then the bytecode instructions will be converted to a script and sent.
+   * @param {string} gremlinScript The script to send to server
+   * @param {array} bindings Map of bindings
+   * @param {*} options Options to configure the script sending
+   */
+  eval(script, bindings) {
+    this.bytecode.addStep('eval', [ script, bindings ]);
+    return this._applyStrategies().then(() => this._getNext());
+  }
 }
 
 function callOnEmptyTraversal(fnName, args) {
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
index d39ccf0..d8a0761 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js
@@ -79,15 +79,29 @@ class Traversal {
   }
 
   /**
+   * Send a Gremlin-Groovy script to the server. If a script is not passed in 
+   * then the bytecode instructions will be converted to a script and sent.
+   * @param {string} gremlinScript The script to send to server
+   * @param {array} bindings Map of bindings
+   * @param {*} options Options to configure the script sending
+   */
+  eval(script, bindings) {
+    this.bytecode.addStep('eval', [ script, bindings ]);
+    return this._applyStrategies().then(() => this._getNext());
+  }
+
+  /**
    * Synchronous iterator of traversers including
    * @private
    */
   _getNext() {
     while (this.traversers && this._traversersIteratorIndex < this.traversers.length) {
       let traverser = this.traversers[this._traversersIteratorIndex];
-      if (traverser.bulk > 0) {
+      if (traverser.bulk && traverser.bulk > 0) {
         traverser.bulk--;
         return { value: traverser.object, done: false };
+      } else if (traverser.bulk === undefined) {
+        return { value: traverser, done: true }
       }
       this._traversersIteratorIndex++;
     }
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 920d998..9d5af16 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
@@ -66,4 +66,22 @@ describe('Traversal', function () {
         });
     });
   });
+  describe('#eval()', function() {
+    it('should submit the traversal as a script and return a result', function() {
+      var g = new Graph().traversal().withRemote(connection);
+      return g.V().count().eval().then(function (item) {
+        assert.ok(item);
+        assert.strictEqual(item.done, true);
+        assert.strictEqual(typeof item.value, 'number');
+      });
+    });
+
+    it('should submit a script and bindings and return a result', function() {
+      var g = new Graph().traversal().withRemote(connection);
+      return g.V().eval('g.V().has(\'name\', name)', { name: 'marko' }).then(function (item) {
+        assert.ok(item);
+        assert.ok(item.value instanceof Vertex);
+      });
+    });
+  });
 });
\ No newline at end of file
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/eval-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/eval-test.js
new file mode 100644
index 0000000..c3c87fb
--- /dev/null
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/eval-test.js
@@ -0,0 +1,104 @@
+/*
+ *  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 expect = require('chai').expect;
+const graph = require('../../lib/structure/graph');
+const t = require('../../lib/process/traversal');
+const TraversalStrategies = require('../../lib/process/traversal-strategy').TraversalStrategies;
+const Bytecode = require('../../lib/process/bytecode');
+
+describe('Traversal', function () {
+
+  describe('#getBytecode#toScript()', function () {
+    it('should add steps and produce valid script representation', function () {
+      const g = new graph.Graph().traversal();
+      const script = g.V().out('created').getBytecode().toScript();
+      assert.ok(script);
+      assert.strictEqual(script.script, 'g.V().out(p1)');
+    });
+
+    it('should add steps and produce valid script representation with parameter bindings', function () {
+      const g = new graph.Graph().traversal();
+      const script = g.addV('name', 'Lilac').getBytecode().toScript();
+      assert.ok(script);
+      assert.strictEqual(script.script, 'g.addV(p1, p2)');
+      assert.ok(script.bindings);
+      assert.deepStrictEqual(script.bindings, { p1: 'name', p2: 'Lilac' });
+    });
+
+    it('should add steps containing enum and produce valid script representation', function () {
+      const g = new graph.Graph().traversal();
+      const script = g.V().order().by('age', t.order.decr).getBytecode().toScript();
+      assert.ok(script);
+      assert.strictEqual(script.script, 'g.V().order().by(p1, decr)');
+    });
+
+    it('should add steps containing a predicate and produce valid script representation', function () {
+      const g = new graph.Graph().traversal();
+      const script = g.V().hasLabel('person').has('age', t.P.gt(30)).getBytecode().toScript();
+      assert.ok(script);
+      assert.strictEqual(script.script, 'g.V().hasLabel(p1).has(p2, gt(30))');
+    });
+
+    it('should take a script and return that script along with passed in bindings', function () {
+      const g = new graph.Graph().traversal();
+      const bytecode = g.addV('name', 'Lilac').getBytecode();
+      const script = bytecode.addStep('eval', [ 
+        'g.addV(\'name\', name).property(\'created\', date)',
+        { name: 'Lilac', created: '2018-01-01T00:00:00.000z' }
+      ]).toScript();
+      assert.ok(script);
+      assert.strictEqual(script.script, 'g.addV(\'name\', name).property(\'created\', date)');
+      assert.deepStrictEqual(script.bindings, { name: 'Lilac', created: '2018-01-01T00:00:00.000z' });
+    });
+  });
+
+  describe('#eval()', function () {
+    it('should apply the strategies and return a Promise with the iterator item', function () {
+      const strategyMock = {
+        apply: function (traversal) {
+          traversal.traversers = [ new t.Traverser(1, 1), new t.Traverser(2, 1) ];
+          return Promise.resolve();
+        }
+      };
+      const strategies = new TraversalStrategies();
+      strategies.addStrategy(strategyMock);
+      const traversal = new t.Traversal(null, strategies, new Bytecode());
+      return traversal.eval(null, null, null)
+        .then(function (item) {
+          assert.strictEqual(item.value, 1);
+          assert.strictEqual(item.done, false);
+          return traversal.eval(null, null, null);
+        })
+        .then(function (item) {
+          assert.strictEqual(item.value, 2);
+          assert.strictEqual(item.done, false);
+          return traversal.eval(null, null, null);
+        })
+        .then(function (item) {
+          assert.strictEqual(item.value, null);
+          assert.strictEqual(item.done, true);
+          return traversal.eval(null, null, null);
+        });
+    });
+  });
+});
diff --git a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml
index 4e6cf9a..f4d508a 100644
--- a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml
+++ b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml
@@ -42,6 +42,7 @@ serializers:
   - { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV3d0, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3d0] }}
 processors:
   - { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }}
+  - { className: org.apache.tinkerpop.gremlin.server.op.standard.StandardOpProcessor, config: { maxParameters: 64 }}
 metrics: {
   slf4jReporter: {enabled: true, interval: 180000}}
 strictTransactionManagement: false