This is an automated email from the ASF dual-hosted git repository. jorgebg pushed a commit to branch TINKERPOP-2070 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 57db815df5609e2d4f7a75ecd2cbbe2aa7271d98 Author: Jorge Bay Gondra <jorgebaygon...@gmail.com> AuthorDate: Tue Oct 23 12:13:11 2018 +0200 Introduce ResultSet abstraction - Client and Connection submit() method returns a ResultSet instance - DriverRemoteConnection returns an instance of RemoteTraversal - Include gremlin script submission via gremlin-javascript in reference docs - ResultSet tests and other test fixes --- docs/src/reference/gremlin-variants.asciidoc | 23 ++++++ docs/src/upgrade/release-3.2.x-incubating.asciidoc | 29 +++---- gremlin-javascript/pom.xml | 62 +++++++-------- .../main/javascript/gremlin-javascript/index.js | 14 ++-- .../gremlin-javascript/lib/driver/client.js | 2 +- .../gremlin-javascript/lib/driver/connection.js | 5 +- .../lib/driver/driver-remote-connection.js | 6 +- .../lib/driver/remote-connection.js | 3 + .../gremlin-javascript/lib/driver/result-set.js | 92 ++++++++++++++++++++++ .../javascript/gremlin-javascript/lib/utils.js | 6 +- .../javascript/gremlin-javascript/test/helper.js | 15 ++-- .../test/integration/client-tests.js | 31 ++++---- .../gremlin-javascript/test/unit/exports-test.js | 18 +++-- .../test/unit/result-set-test.js | 86 ++++++++++++++++++++ 14 files changed, 305 insertions(+), 87 deletions(-) diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index 074c436..3852591 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -562,3 +562,26 @@ const __ = gremlin.process.statics; g.V().repeat(__.out()).times(2).values("name").fold().toList(); ---- + +=== Submit Gremlin Scripts + +Additionally, you can also send parametrized Gremlin scripts to the server as strings, using the +`Client` class in Gremlin-JavaScript. + + +[source,javascript] +---- +const gremlin = require('gremlin'); +const client = new gremlin.driver.Client('ws://localhost:8182/gremlin', { traversalSource: 'g' }); + +const result1 = await client.submit('g.V(vid)', { vid: 1 }); +const vertex = result1.first(); + +const result2 = await client.submit('g.V().hasLabel(label).tail(n)', { label: 'person', n: 3 }); + +// ResultSet is an iterable +for (const vertex of result2) { + console.log(vertex.id); +} + +---- \ No newline at end of file diff --git a/docs/src/upgrade/release-3.2.x-incubating.asciidoc b/docs/src/upgrade/release-3.2.x-incubating.asciidoc index 49e222a..9fc3002 100644 --- a/docs/src/upgrade/release-3.2.x-incubating.asciidoc +++ b/docs/src/upgrade/release-3.2.x-incubating.asciidoc @@ -112,18 +112,19 @@ Gremlin Javascript can now submit script, with optional bindings, using the `Cli [source,javascript] ---- const gremlin = require('gremlin'); -const connection = new gremlin.driver.Client('ws://localhost:8182/gremlin', { traversalSource: 'g' }); +const client = new gremlin.driver.Client('ws://localhost:8182/gremlin', { traversalSource: 'g' }); -connection.submit('g.V().tail()') - .then(response => { - console.log(response.traversers.length); - console.log(response.traversers[0]); +client.submit('g.V().tail()') + .then(result => { + console.log(result.length); + console.log(result.toArray()[0]); }); -connection.submit('g.V(vid)', {vid: 1}) - .then(response => { - console.log(response.traversers.length); - console.log(response.traversers[0]); +client.submit('g.V(vid)', { vid: 1 }) + .then(result => { + console.log(result.length); + // Get the first item + console.log(result.first()); }); ---- @@ -133,16 +134,16 @@ and also allows translation of bytecode steps into script: ---- const gremlin = require('gremlin'); const graph = new gremlin.process.Graph(); -const connection = new gremlin.driver.Client('ws://localhost:8182/gremlin', { traversalSource: 'g' }); +const client = new gremlin.driver.Client('ws://localhost:8182/gremlin', { traversalSource: 'g' }); const translator = new gremlin.process.Translator('g'); const g = graph.traversal(); const script = translator.translate(g.V().tail().getBytecode()); -connection.submit(script) - .then(response => { - console.log(response.traversers.length); - console.log(response.traversers[0]); +client.submit(script) + .then(result => { + console.log(result.length); + console.log(result.first()); }); ---- diff --git a/gremlin-javascript/pom.xml b/gremlin-javascript/pom.xml index fad3fde..6102a7d 100644 --- a/gremlin-javascript/pom.xml +++ b/gremlin-javascript/pom.xml @@ -179,37 +179,37 @@ limitations under the License. </goals> </execution> <execution> - <id>npm install</id> - <phase>generate-test-resources</phase> - <goals> - <goal>npm</goal> - </goals> - <configuration> - <arguments>install</arguments> - </configuration> - </execution> - <execution> - <id>npm test</id> - <phase>integration-test</phase> - <goals> - <goal>npm</goal> - </goals> - <configuration> - <arguments>test --exit</arguments> - <failOnError>true</failOnError> - </configuration> - </execution> - <execution> - <id>npm test gherkin features</id> - <phase>integration-test</phase> - <goals> - <goal>npm</goal> - </goals> - <configuration> - <arguments>run-script features</arguments> - <failOnError>true</failOnError> - </configuration> - </execution> + <id>npm install</id> + <phase>generate-test-resources</phase> + <goals> + <goal>npm</goal> + </goals> + <configuration> + <arguments>install</arguments> + </configuration> + </execution> + <execution> + <id>npm test</id> + <phase>integration-test</phase> + <goals> + <goal>npm</goal> + </goals> + <configuration> + <arguments>test --exit</arguments> + <failOnError>true</failOnError> + </configuration> + </execution> + <execution> + <id>npm test gherkin features</id> + <phase>integration-test</phase> + <goals> + <goal>npm</goal> + </goals> + <configuration> + <arguments>run-script features</arguments> + <failOnError>true</failOnError> + </configuration> + </execution> </executions> <configuration> <skip>${skipTests}</skip> diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js index ffc7f0c..7f8c80f 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js @@ -33,6 +33,7 @@ const Translator = require('./lib/process/translator'); const utils = require('./lib/utils'); const DriverRemoteConnection = require('./lib/driver/driver-remote-connection'); const Client = require('./lib/driver/client'); +const ResultSet = require('./lib/driver/result-set'); const Authenticator = require('./lib/driver/auth/authenticator'); const PlainTextSaslAuthenticator = require('./lib/driver/auth/plain-text-sasl-authenticator'); @@ -41,15 +42,16 @@ module.exports = { RemoteConnection: rc.RemoteConnection, RemoteStrategy: rc.RemoteStrategy, RemoteTraversal: rc.RemoteTraversal, - DriverRemoteConnection: DriverRemoteConnection, - Client: Client, + DriverRemoteConnection, + Client, + ResultSet, auth: { - Authenticator: Authenticator, - PlainTextSaslAuthenticator: PlainTextSaslAuthenticator + Authenticator, + PlainTextSaslAuthenticator } }, process: { - Bytecode: Bytecode, + Bytecode, EnumValue: t.EnumValue, P: t.P, Traversal: t.Traversal, @@ -70,7 +72,7 @@ module.exports = { GraphTraversal: gt.GraphTraversal, GraphTraversalSource: gt.GraphTraversalSource, statics: gt.statics, - Translator: Translator + Translator }, structure: { io: { 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 ccb7b8b..79fa91e 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 @@ -61,7 +61,7 @@ class Client { * @returns {Promise} */ submit(message, bindings) { - if (typeof message === 'string' || message instanceof String) { + if (typeof message === 'string') { const args = { 'gremlin': message, 'bindings': bindings, 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 0f1e732..cc03449 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 @@ -26,6 +26,7 @@ const WebSocket = require('ws'); const util = require('util'); const utils = require('../utils'); const serializer = require('../structure/io/graph-serializer'); +const ResultSet = require('./result-set'); const responseStatusCode = { success: 200, @@ -192,7 +193,7 @@ class Connection { switch (response.status.code) { case responseStatusCode.noContent: this._clearHandler(response.requestId); - return handler.callback(null, { traversers: []}); + return handler.callback(null, new ResultSet(utils.emptyArray)); case responseStatusCode.partialContent: handler.result = handler.result || []; handler.result.push.apply(handler.result, response.result.data); @@ -205,7 +206,7 @@ class Connection { handler.result = response.result.data; } this._clearHandler(response.requestId); - return handler.callback(null, { traversers: handler.result }); + return handler.callback(null, new ResultSet(handler.result)); } } 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 f4021d4..2ed7ba5 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 @@ -22,7 +22,9 @@ */ 'use strict'; -const RemoteConnection = require('./remote-connection').RemoteConnection; +const rcModule = require('./remote-connection'); +const RemoteConnection = rcModule.RemoteConnection; +const RemoteTraversal = rcModule.RemoteTraversal; const Client = require('./client'); /** @@ -58,7 +60,7 @@ class DriverRemoteConnection extends RemoteConnection { /** @override */ submit(bytecode) { - return this._client.submit(bytecode); + return this._client.submit(bytecode).then(result => new RemoteTraversal(result.toArray())); } /** @override */ 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 9f70679..cc4c4d2 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 @@ -61,6 +61,9 @@ class RemoteConnection { } } +/** + * Represents a traversal as a result of a {@link RemoteConnection} submission. + */ class RemoteTraversal extends t.Traversal { constructor(traversers, sideEffects) { super(null, null, null); diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/result-set.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/result-set.js new file mode 100644 index 0000000..1860aea --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/result-set.js @@ -0,0 +1,92 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +const util = require('util'); +const inspect = util.inspect.custom || 'inspect'; + +/** + * Represents the response returned from the execution of a Gremlin traversal or script. + */ +class ResultSet { + + /** + * Creates a new instance of {@link ResultSet}. + * @param {Array} items + */ + constructor(items) { + if (!Array.isArray(items)) { + throw new TypeError('items must be an Array instance'); + } + + this._items = items; + + /** + * Gets the amount of items in the result. + * @type {Number} + */ + this.length = items.length; + + /** + * Access the raw result items via a property. + * @deprecated It will be removed in Apache TinkerPop version 3.4. + * Use <code>toArray()</code> or iterate directly from the <code>ResultSet</code>. + * @type {Array} + */ + this.traversers = items; + } + + /** + * Gets the iterator associated with this instance. + * @returns {Iterator} + */ + [Symbol.iterator]() { + return this._items[Symbol.iterator](); + } + + /** + * Provides a representation useful for debug and tracing. + */ + [inspect]() { + return this._items; + } + + /** + * Gets an array of result items. + * @returns {Array} + */ + toArray() { + return this._items; + } + + /** + * Returns the first item. + * @returns {Object|null} + */ + first() { + const item = this._items[0]; + return item !== undefined ? item : null; + } +} + +module.exports = ResultSet; \ No newline at end of file diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js index 7abc5fe..c6b091f 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/utils.js @@ -31,7 +31,7 @@ exports.toLong = function toLong(value) { const Long = exports.Long = function Long(value) { if (typeof value !== 'string' && typeof value !== 'number') { - throw new TypeError('Ty') + throw new TypeError('The value must be a string or a number'); } this.value = value.toString(); }; @@ -53,4 +53,6 @@ exports.getUuid = function getUuid() { hex.substr(12, 4) + '-' + hex.substr(16, 4) + '-' + hex.substr(20, 12)); -} \ No newline at end of file +}; + +exports.emptyArray = Object.freeze([]); \ No newline at end of file diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js index 53358c3..f819ca3 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/helper.js @@ -26,20 +26,23 @@ const DriverRemoteConnection = require('../lib/driver/driver-remote-connection') const Client = require('../lib/driver/client'); const PlainTextSaslAuthenticator = require('../lib/driver/auth/plain-text-sasl-authenticator'); +const serverUrl = 'ws://localhost:45940/gremlin'; +const serverAuthUrl = 'ws://localhost:45941/gremlin'; + /** @returns {DriverRemoteConnection} */ exports.getConnection = function getConnection(traversalSource) { - return new DriverRemoteConnection('ws://localhost:45940/gremlin', { traversalSource: traversalSource }); + return new DriverRemoteConnection(serverUrl, { traversalSource }); }; exports.getSecureConnectionWithPlainTextSaslAuthenticator = (traversalSource, username, password) => { const authenticator = new PlainTextSaslAuthenticator(username, password); - return new DriverRemoteConnection('ws://localhost:45941/gremlin', { - traversalSource: traversalSource, - authenticator: authenticator, - rejectUnauthorized: false + return new DriverRemoteConnection(serverAuthUrl, { + traversalSource, + authenticator, + rejectUnauthorized: false }); }; exports.getClient = function getClient(traversalSource) { - return new Client('ws://localhost:45940/gremlin', { traversalSource: traversalSource }); + return new Client(serverUrl, { traversalSource }); }; \ No newline at end of file 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 ffad6d6..92e709c 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 @@ -38,35 +38,32 @@ describe('Client', function () { describe('#submit()', function () { it('should send bytecode', function () { return client.submit(new Bytecode().addStep('V', []).addStep('tail', [])) - .then(function (response) { - assert.ok(response); - assert.ok(response.traversers); - assert.strictEqual(response.traversers.length, 1); - assert.ok(response.traversers[0].object instanceof graphModule.Vertex); + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.length, 1); + assert.ok(result.first().object instanceof graphModule.Vertex); }); }); it('should send and parse a script', function () { return client.submit('g.V().tail()') - .then(function (response) { - assert.ok(response); - assert.ok(response.traversers); - assert.strictEqual(response.traversers.length, 1); - assert.ok(response.traversers[0] instanceof graphModule.Vertex); + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.length, 1); + assert.ok(result.first() instanceof graphModule.Vertex); }); }); it('should send and parse a script with bindings', function () { return client.submit('x + x', { x: 3 }) - .then(function (response) { - assert.ok(response); - assert.ok(response.traversers); - assert.strictEqual(response.traversers[0], 6); + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.first(), 6); }); }); it('should send and parse a script with non-native javascript bindings', function () { return client.submit('card.class.simpleName + ":" + card', { card: t.cardinality.set } ) - .then(function (response) { - assert.ok(response); - assert.strictEqual(response.traversers[0], 'Cardinality:set'); + .then(function (result) { + assert.ok(result); + assert.strictEqual(result.first(), 'Cardinality:set'); }); }); }); 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 e0dfb12..b12bfed 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 @@ -66,10 +66,16 @@ describe('API', function () { }); it('should expose fields under driver', function () { assert.ok(glvModule.driver); - assert.strictEqual(typeof glvModule.driver.RemoteConnection, 'function'); - assert.strictEqual(typeof glvModule.driver.RemoteStrategy, 'function'); - assert.strictEqual(typeof glvModule.driver.RemoteTraversal, 'function'); - assert.strictEqual(typeof glvModule.driver.DriverRemoteConnection, 'function'); - assert.strictEqual(glvModule.driver.DriverRemoteConnection.name, 'DriverRemoteConnection'); + validateConstructor(glvModule.driver, 'RemoteConnection'); + validateConstructor(glvModule.driver, 'RemoteStrategy'); + validateConstructor(glvModule.driver, 'RemoteTraversal'); + validateConstructor(glvModule.driver, 'DriverRemoteConnection'); + validateConstructor(glvModule.driver, 'Client'); + validateConstructor(glvModule.driver, 'ResultSet'); }); -}); \ No newline at end of file +}); + +function validateConstructor(parent, name) { + assert.strictEqual(typeof parent[name], 'function'); + assert.strictEqual(parent[name].name, name); +} \ No newline at end of file diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/result-set-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/result-set-test.js new file mode 100644 index 0000000..9eb99af --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/result-set-test.js @@ -0,0 +1,86 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +const assert = require('assert'); +const util = require('util'); +const ResultSet = require('../../lib/driver/result-set'); + +describe('ResultSet', function () { + + describe('#toArray()', () => { + it('should return an array of items', () => { + const items = [ 'a', 'b' ]; + const result = new ResultSet(items); + assert.ok(Array.isArray(result.toArray())); + assert.deepStrictEqual(result.toArray(), items); + }); + }); + + describe('#length', () => { + it('should return the length of the items', () => { + const items = [ 'a', 'b', 1, 0 ]; + const result = new ResultSet(items); + assert.strictEqual(result.length, items.length); + }); + }); + + describe('#first()', () => { + it('should return the first item when there are one or more than one item', () => { + assert.strictEqual(new ResultSet(['a', 'b']).first(), 'a'); + assert.strictEqual(new ResultSet(['z']).first(), 'z'); + }); + + it('should return null when there are no items', () => { + assert.strictEqual(new ResultSet([]).first(), null); + }); + }); + + describe('#[Symbol.iterator]()', () => { + it('should support be iterable', () => { + const items = [ 1, 2, 3 ]; + const result = new ResultSet(items); + const obtained = []; + for (let item of result) { + obtained.push(item); + } + + assert.deepStrictEqual(obtained, items); + assert.deepStrictEqual(Array.from(result), items); + }); + }); + + describe('#[util.inspect.custom]()', () => { + it('should return the Array representation', () => { + assert.strictEqual(util.inspect(new ResultSet([ 1, 2, 3 ])), '[ 1, 2, 3 ]'); + }); + }); + + describe('#traversers', () => { + it('should expose deprecated property', () => { + const items = [ 'a', 'b' ]; + // Traversers property is going to be removed in upcoming versions + assert.strictEqual(new ResultSet(items).traversers, items); + }); + }); +}); \ No newline at end of file