├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin └── gremlin-console ├── lib ├── edge-wrapper.js ├── element-wrapper.js ├── graph-wrapper.js ├── gremlin.js ├── pipeline-wrapper.js ├── query-wrapper.js └── vertex-wrapper.js ├── package.json ├── pom.xml └── test ├── test-graph-wrapper.js ├── test-gremlin.js └── test-pipeline-wrapper.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | target/ 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "eqeqeq": true, 3 | "indent": 2, 4 | "node": true, 5 | "quotmark": "single", 6 | "shadow": true, 7 | "strict": true, 8 | "trailing": true, 9 | "white": true, 10 | "globals": { 11 | "suite": true, 12 | "test": true, 13 | "suiteSetup": true, 14 | "setup": true, 15 | "suiteTeardown": true, 16 | "teardown": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - oraclejdk7 5 | env: 6 | - NODE_VERSION="0.11" 7 | - NODE_VERSION="0.10" 8 | before_install: 9 | - nvm install $NODE_VERSION 10 | before_script: 11 | - npm install 12 | script: 13 | - npm test 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 entrendipity pty ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 15 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: package lint test 2 | 3 | default: package lint test 4 | 5 | package: 6 | mvn clean package 7 | 8 | lint: 9 | find bin lib test -name "*.js" | xargs node_modules/jshint/bin/jshint 10 | 11 | test: lint 12 | node_modules/mocha/bin/mocha --reporter=spec --ui tdd 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gremlin-node 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/inolen/gremlin-node.svg)](https://travis-ci.org/inolen/gremlin-node) 5 | 6 | **NOTE: This project is no longer being maintained. The project owners instead now use [ts-tinkerpop](https://github.com/RedSeal-co/ts-tinkerpop), 7 | which takes advantage of [ts-java](https://github.com/RedSeal-co/ts-java). We suggest you take a look at those two 8 | projects.** 9 | 10 | Implementation of [Gremlin](https://github.com/tinkerpop/gremlin/wiki) for node.js. Gremlin-node is a javascript wrapper around the Gremlin API. The node-java module provides the bridge between node and Java. 11 | 12 | ```javascript 13 | var Gremlin = require('gremlin'); 14 | var gremlin = new Gremlin({ 15 | classpath: [ ... ], 16 | options: [ ... ] 17 | }); 18 | 19 | var TinkerGraphFactory = gremlin.java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraphFactory'); 20 | var graph = TinkerGraphFactory.createTinkerGraphSync(); 21 | var g = gremlin.wrap(graph); 22 | 23 | g.V('name', 'marko').next(function (err, v) { 24 | v.getProperty('name', function (err, value) { 25 | console.log(value); 26 | }); 27 | }); 28 | ``` 29 | 30 | ## Promises/A+ API 31 | 32 | Gremlin-node as of version 0.4.0 supports the use of Promises/A+ for most functions taking a callback. For example, a portion of the above code could be written as: 33 | 34 | ```javascript 35 | g.V('name', 'marko').next().then( 36 | function (v) { 37 | v.getProperty('name').then(console.log); 38 | }); 39 | ``` 40 | 41 | This snippet by itself may seem underwhelming, but consider that with some enhanecments to the gremlin-console application, promises make it possible to interact with gremlin in the console as if the promise-returning functions returned values: 42 | 43 | ``` 44 | node > var pipe = g.V('name', 'marko') 45 | node > var v = pipe.next() 46 | node > v.getProperty('name') 47 | 'marko' 48 | ``` 49 | 50 | The gremlin-console application contained in this package does not yet have these enhancements. For early access to a console with these enhancements, use the repository [gremlin-repl](https://github.com/jimlloyd/gremlin-repl). 51 | 52 | ## Dependencies 53 | 54 | [__node-java__](https://github.com/joeferner/node-java) 55 | 56 | Bridge API to connect with existing Java APIs. Please read the [__node-java__](https://github.com/joeferner/node-java) installation notes, as it outlines how to install the node-java module on specific platforms and its dependencies. 57 | 58 | [__maven__](http://maven.apache.org/index.html) 59 | 60 | Maven enables the installation of the base jar files. 61 | 62 | ## Installation 63 | 64 | ```bash 65 | $ npm install gremlin 66 | ``` 67 | 68 | Gremlin-node includes the required .jar files for Gremlin and the TinkerPop stack. It doesn't include any backend specific jars for databases such as Titan or OrientDB. 69 | 70 | ## Configuration 71 | 72 | The `Gremlin` constructor takes in an object with two properties; `classpath` which allows you to load in jar files from your own project and `options` which allows you to supply parameters to the Java runtime. 73 | 74 | ```javascript 75 | var Gremlin = require('gremlin'); 76 | var gremlin = new Gremlin({ 77 | classpath: [ 78 | path.join(__dirname, '..', 'target', '**', '*.jar') 79 | ], 80 | options: [ 81 | '-XX:+UseThreadPriorities', 82 | '-XX:ThreadPriorityPolicy=42', 83 | '-XX:+UseParNewGC', 84 | '-XX:+UseConcMarkSweepGC', 85 | '-XX:+CMSParallelRemarkEnabled', 86 | '-XX:SurvivorRatio=8', 87 | '-XX:MaxTenuringThreshold=1', 88 | '-XX:CMSInitiatingOccupancyFraction=75', 89 | '-XX:+UseCMSInitiatingOccupancyOnly', 90 | '-XX:+UseTLAB', 91 | '-XX:+UseCondCardMark' 92 | ] 93 | }); 94 | ``` 95 | 96 | ## Connecting to a Graph 97 | 98 | As mentioned above, gremlin-node only includes jars for the reference Blueprints implementation, TinkerGraph. 99 | 100 | To use another database implementing the Blueprints property graph interfaces (e.g. Titan or OrientDB), the Gremlin constructor must point to a location with the databases compiled jars. A quickstart project for using Titan with gremlin-node is up at [titan-node](https://github.com/inolen/titan-node). 101 | 102 | Once the dependent jars are properly loaded into the Java runtime, a graph instance must be created and passed to `gremlin.wrap`. 103 | 104 | ### TinkerGraph 105 | 106 | ```javascript 107 | var TinkerGraphFactory = gremlin.java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraphFactory'); 108 | var graph = TinkerGraphFactory.createTinkerGraphSync(); 109 | var g = gremlin.wrap(graph); 110 | ``` 111 | 112 | ### Titan 113 | 114 | ```javascript 115 | var TitanFactory = gremlin.java.import('com.thinkaurelius.titan.core.TitanFactory'); 116 | var graph = TitanFactory.openSync('local:/path/to/config'); 117 | var g = gremlin.wrap(graph); 118 | ``` 119 | 120 | ### OrientGraph 121 | 122 | ```javascript 123 | var OrientGraph = g.java.import('com.tinkerpop.blueprints.impls.orient.OrientGraph'); 124 | var graph = new OrientGraph('local:/path/to/database/files', 'admin', 'admin'); 125 | var g = gremlin.wrap(graph); 126 | ``` 127 | 128 | ## Working with the Database 129 | 130 | Once you have connected to the database, you are able to call all implementation specific database methods. For example here's how you would add two Vertices and an Edge and associate them in an OrientDB graph. 131 | 132 | ```javascript 133 | var luca = graph.addVertexSync(null); 134 | luca.setPropertySync( 'name', 'Luca' ); 135 | 136 | var marko = graph.addVertexSync(null); 137 | marko.setPropertySync( 'name', 'Marko' ); 138 | 139 | var lucaKnowsMarko = graph.addEdgeSync(null, luca, marko, 'knows'); 140 | 141 | graph.commitSync(); 142 | ``` 143 | 144 | ## Examples 145 | 146 | A good resource to understand the Gremlin API (for TinkerPop2) is [GremlinDocs](http://gremlindocs.com/). Most of the examples given at GremlinDocs have been translated to work in a node REPL, and encoded to run as unit tests, but in a separate repository. See [gremlin-repl](https://github.com/jimlloyd/gremlin-repl), and in particular these expected output files: 147 | 148 | 1. [gremlindocs-transform](https://github.com/jimlloyd/gremlin-repl/blob/master/test/data/gremlindocs-transform.expected) 149 | 2. [gremlindocs-filter](https://github.com/jimlloyd/gremlin-repl/blob/master/test/data/gremlindocs-filter.expected) 150 | 3. [gremlindocs-sideeffects](https://github.com/jimlloyd/gremlin-repl/blob/master/test/data/gremlindocs-side-effects.expected) 151 | 4. [gremlindocs-branch](https://github.com/jimlloyd/gremlin-repl/blob/master/test/data/gremlindocs-branch.expected) 152 | 5. [gremlindocs-methods](https://github.com/jimlloyd/gremlin-repl/blob/master/test/data/gremlindocs-methods.expected) 153 | 154 | ## Authors 155 | 156 | Frank Panetta - [Follow @entrendipity](https://twitter.com/intent/follow?screen_name=entrendipity) 157 | 158 | Anthony Pesch - [inolen](https://github.com/inolen) 159 | 160 | Jim Lloyd - [jimlloyd](https://github.com/jimlloyd) 161 | 162 | ## Contributors 163 | 164 | Jared Camins-Esakov 165 | 166 | ## License 167 | ### The MIT License (MIT) 168 | 169 | Copyright (c) 2013 entrendipity pty ltd 170 | Parts copyright (c) 2013 C & P Bibliography Services, LLC 171 | 172 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 173 | 174 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 175 | 176 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 177 | -------------------------------------------------------------------------------- /bin/gremlin-console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var repl = require('repl'); 6 | var optimist = require('optimist'); 7 | var util = require('util'); 8 | var vm = require('vm'); 9 | var Gremlin = require('../lib/gremlin'); 10 | var _ = require('underscore'); 11 | 12 | var argv = optimist.alias('c', 'classpath').argv; 13 | argv.classpath = argv.classpath || '.'; 14 | 15 | var gremlin = new Gremlin({ 16 | classpath: util.isArray(argv.classpath) ? argv.classpath : [ argv.classpath ] 17 | }); 18 | 19 | // inject a default graph 20 | var TinkerGraphFactory = gremlin.java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraphFactory'); 21 | var graph = TinkerGraphFactory.createTinkerGraphSync(); 22 | var g = gremlin.wrap(graph); 23 | 24 | console.log(); 25 | console.log(' \\,,,/'); 26 | console.log(' (o o)'); 27 | console.log('-----oOOo-(_)-oOOo-----'); 28 | 29 | var r = repl.start({ 30 | prompt: 'gremlin> ', 31 | input: process.stdin, 32 | output: process.stdout, 33 | terminal: true, 34 | ignoreUndefined: true, 35 | writer: outFunc, 36 | eval: evalFunc 37 | }); 38 | 39 | function outFunc(it) { 40 | if (_.isObject(it) && _.isFunction(it.toJSONSync)) { 41 | it = it.toJSONSync(); 42 | } 43 | return '==> ' + util.inspect(it); 44 | } 45 | 46 | function evalFunc(code, context, file, cb) { 47 | // g.print is a helper method for async functions. 48 | // in the case that it is used, we must defer calling 49 | // the callback until print itself has been called 50 | var async = false; 51 | var print = function (err, data) { 52 | cb(null, gremlin.toJSONSync(data)); 53 | }; 54 | 55 | Object.defineProperty(g, 'print', { 56 | get: function () { 57 | async = true; 58 | return print; 59 | }, 60 | configurable: true 61 | }); 62 | 63 | try { 64 | var result = vm.runInContext(code, context, file); 65 | if (!async) { 66 | cb(null, result); 67 | } 68 | } catch (e) { 69 | return cb(e); 70 | } 71 | }; 72 | 73 | r.context.gremlin = gremlin; 74 | r.context.T = gremlin.Tokens; 75 | r.context.java = gremlin.java; 76 | r.context.TinkerGraphFactory = TinkerGraphFactory; 77 | r.context.graph = graph; 78 | r.context.g = g; 79 | 80 | r.on('exit', function () { 81 | console.log('Good-bye from Gremlin!'); 82 | process.exit(); 83 | }); 84 | -------------------------------------------------------------------------------- /lib/edge-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var ElementWrapper = require('./element-wrapper'); 5 | 6 | var EdgeWrapper = module.exports = function (gremlin, el) { 7 | ElementWrapper.call(this, gremlin, el); 8 | }; 9 | 10 | util.inherits(EdgeWrapper, ElementWrapper); 11 | 12 | // public Vertex getVertex(Direction direction) throws IllegalArgumentException; 13 | 14 | EdgeWrapper.prototype.getLabel = function () { 15 | return this.el.getLabelSync(); 16 | }; 17 | -------------------------------------------------------------------------------- /lib/element-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var Q = require('q'); 5 | 6 | var ElementWrapper = module.exports = function (gremlin, el) { 7 | this.gremlin = gremlin; 8 | this.el = el; 9 | }; 10 | 11 | ElementWrapper.prototype.unwrap = function () { 12 | return this.el; 13 | }; 14 | 15 | // each database seems to want to return a different data type for 16 | // the id (Java object, long, string, etc.). in TinkerGraph and Titan 17 | // all of the possible returned types serialize to a string, and the 18 | // Graph object's getVertex and getEdge work correctly with these 19 | // serialized strings. for this reason, we're standardizing on getId 20 | // always returning a string (at least currently) 21 | ElementWrapper.prototype.getId = function () { 22 | var id = this.el.getIdSync(); 23 | 24 | if (_.isString(id)) { 25 | return id; 26 | } else if (id.longValue) { 27 | return id.longValue; 28 | } 29 | 30 | return id.toStringSync(); 31 | }; 32 | 33 | ElementWrapper.prototype.getProperty = function (key, callback) { 34 | return Q.nbind(this.el.getProperty, this.el)(key).nodeify(callback); 35 | }; 36 | 37 | ElementWrapper.prototype.getProperties = function (props, callback) { 38 | var self = this; 39 | var res = {}; 40 | var propPromises = props.map( 41 | function (prop) { 42 | return self.getProperty(prop) 43 | .then(function (value) { res[prop] = value; }); 44 | } 45 | ); 46 | 47 | // Q.all() can be dangerous for operations that modify the database, 48 | // but should be fine here since this is read-only. 49 | return Q.all(propPromises) 50 | .then(function () { return new Q(res); }) 51 | .nodeify(callback); 52 | }; 53 | 54 | ElementWrapper.prototype.setProperty = function (key, value, callback) { 55 | return Q.nbind(this.el.setProperty, this.el)(key, value).nodeify(callback); 56 | }; 57 | 58 | ElementWrapper.prototype.setProperties = function (props, callback) { 59 | var self = this; 60 | // We can't simply use Q.all like this here, because TinkerGraph doesn't handle parallel concurrent operations. 61 | // return Q.all(Object.keys(props).map(function (key) { return self.setProperty(key, props[key]); })).nodeify(callback); 62 | 63 | function setProps(keys) { 64 | if (keys.length === 0) { 65 | return new Q(); 66 | } 67 | var key = keys.pop(); 68 | return self.setProperty(key, props[key]) 69 | .then(function () { return setProps(keys); }); 70 | } 71 | 72 | return setProps(Object.keys(props)).nodeify(callback); 73 | }; 74 | 75 | ElementWrapper.prototype.removeProperty = function (key, callback) { 76 | return Q.nbind(this.el.removeProperty, this.el)(key).nodeify(callback); 77 | }; 78 | 79 | ElementWrapper.prototype.removeProperties = function (props, callback) { 80 | var self = this; 81 | 82 | function removeProps(keys) { 83 | if (keys.length === 0) { 84 | return new Q(); 85 | } 86 | var key = keys.pop(); 87 | return self.removeProperty(key) 88 | .then(function () { return removeProps(keys); }); 89 | } 90 | 91 | return removeProps(props.slice()).nodeify(callback); 92 | }; 93 | 94 | ElementWrapper.prototype.remove = function (callback) { 95 | return Q.nbind(this.el.remove, this.el)().nodeify(callback); 96 | }; 97 | 98 | ElementWrapper.prototype.toJSON = function (callback) { 99 | return Q.nbind(this.gremlin.toJSON, this.gremlin)(this.el).nodeify(callback); 100 | }; 101 | 102 | ElementWrapper.prototype.toJSONSync = function (callback) { 103 | return this.gremlin.toJSONSync(this.el); 104 | }; 105 | 106 | -------------------------------------------------------------------------------- /lib/graph-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var VertexWrapper = require('./vertex-wrapper'); 5 | var EdgeWrapper = require('./edge-wrapper'); 6 | var Q = require('q'); 7 | 8 | var GraphWrapper = module.exports = function (gremlin, graph) { 9 | this.gremlin = gremlin; 10 | this.graph = graph; 11 | 12 | // re-export some of gremlin's utility functions as 13 | // part of the graph wrapper instance 14 | this.java = gremlin.java; 15 | this.ClassTypes = gremlin.ClassTypes; 16 | this.Tokens = gremlin.Tokens; 17 | this.Compare = gremlin.Compare; 18 | this.Contains = gremlin.Contains; 19 | this.Direction = gremlin.Direction; 20 | this.ArrayList = gremlin.ArrayList; 21 | this.HashMap = gremlin.HashMap; 22 | this.Table = gremlin.Table; 23 | this.Tree = gremlin.Tree; 24 | this.isType = gremlin.isType.bind(gremlin); 25 | this.toList = gremlin.toList.bind(gremlin); 26 | this.toListSync = gremlin.toListSync.bind(gremlin); 27 | 28 | this.toJSON = function (callback) { 29 | return Q.nbind(this.gremlin.toJSON, this.gremlin)(this.graph) 30 | .nodeify(callback); 31 | }; 32 | 33 | this.toJSONSync = function () { 34 | return this.gremlin.toJSONSync(this.graph); 35 | }; 36 | }; 37 | 38 | GraphWrapper.prototype.loadGraphMLSync = function (filename) { 39 | var Reader = this.java.import('com.tinkerpop.blueprints.util.io.graphml.GraphMLReader'); 40 | var reader = new Reader(this.graph); 41 | reader.inputGraphSync(filename); 42 | }; 43 | 44 | GraphWrapper.prototype.saveGraphMLSync = function (filename) { 45 | var Writer = this.java.import('com.tinkerpop.blueprints.util.io.graphml.GraphMLWriter'); 46 | var writer = new Writer(this.graph); 47 | writer.outputGraphSync(filename); 48 | }; 49 | 50 | GraphWrapper.prototype.loadGraphSONSync = function (filename) { 51 | var Reader = this.java.import('com.tinkerpop.blueprints.util.io.graphson.GraphSONReader'); 52 | var reader = new Reader(this.graph); 53 | reader.inputGraphSync(filename); 54 | }; 55 | 56 | GraphWrapper.prototype.saveGraphSONSync = function (filename) { 57 | var className = 'com.tinkerpop.blueprints.util.io.graphson.GraphSONWriter'; 58 | var method = 'outputGraph'; 59 | this.java.callStaticMethodSync(className, method, this.graph, filename); 60 | }; 61 | 62 | GraphWrapper.prototype._getTransaction = function () { 63 | // Transactions in TransactionalGraph's are often, by default, bound against the 64 | // executing thread (e.g. as a ThreadLocal variable). This behavior is not very 65 | // helpful in JavaScript because while the main execution is in fact performed 66 | // on a single thread, often a pool of threads exist to service asynchronous tasks, 67 | // making our tasks often operate on an incorrect transaction instance. 68 | // 69 | // Due to this, we try and avoid this default thread-bound functionality and manage 70 | // our own life-cycle if the supplied graph instance provides the interface to create 71 | // a transaction independent of the executing thread. 72 | // 73 | if (this.graph.txn) { 74 | return this.graph.txn; 75 | } 76 | if (!this.isType(this.graph, 'com.tinkerpop.blueprints.ThreadedTransactionalGraph')) { 77 | return this.graph; 78 | } 79 | this.graph.txn = this.graph.newTransactionSync(); 80 | return this.graph.txn; 81 | }; 82 | 83 | GraphWrapper.prototype._clearTransaction = function () { 84 | if (this.graph.txn) { 85 | this.graph.txn = null; 86 | } 87 | }; 88 | 89 | // com.tinkerpop.blueprints.Graph interface 90 | GraphWrapper.prototype.addVertex = function (id) { 91 | var gremlin = this.gremlin; 92 | var argPair = gremlin.extractArguments(Array.prototype.slice.call(arguments)); 93 | var txn = this._getTransaction(); 94 | 95 | var deferred = Q.defer(); 96 | 97 | txn.addVertex(id, function (err, v) { 98 | if (err) 99 | deferred.reject(err); 100 | else 101 | deferred.resolve(gremlin.wrapVertex(v)); 102 | }); 103 | 104 | return deferred.promise.nodeify(argPair.callback); 105 | }; 106 | 107 | GraphWrapper.prototype.getVertex = function (id, callback) { 108 | var gremlin = this.gremlin; 109 | var txn = this._getTransaction(); 110 | 111 | return Q.nbind(txn.getVertex, txn)(id) 112 | .then(function (v) { return new Q(v ? gremlin.wrapVertex(v) : null); }) 113 | .nodeify(callback); 114 | }; 115 | 116 | GraphWrapper.prototype.removeVertex = function (vertex, callback) { 117 | var txn = this._getTransaction(); 118 | 119 | if (!(vertex instanceof VertexWrapper)) { 120 | throw new TypeError('vertex must be an instance of VertexWrapper'); 121 | } 122 | 123 | return Q.nbind(txn.removeVertex, txn)(vertex.unwrap()) 124 | .nodeify(callback); 125 | }; 126 | 127 | GraphWrapper.prototype.addEdge = function (id, outVertex, inVertex, label, callback) { 128 | var gremlin = this.gremlin; 129 | var txn = this._getTransaction(); 130 | 131 | if (!(outVertex instanceof VertexWrapper)) { 132 | throw new TypeError('outVertex must be an instance of VertexWrapper'); 133 | } 134 | if (!(inVertex instanceof VertexWrapper)) { 135 | throw new TypeError('inVertex must be an instance of VertexWrapper'); 136 | } 137 | 138 | return Q.nbind(txn.addEdge, txn)(id, outVertex.unwrap(), inVertex.unwrap(), label) 139 | .then(function (e) { return new Q(gremlin.wrapEdge(e)); }) 140 | .nodeify(callback); 141 | }; 142 | 143 | GraphWrapper.prototype.getEdge = function (id, callback) { 144 | var gremlin = this.gremlin; 145 | var txn = this._getTransaction(); 146 | 147 | return Q.nbind(txn.getEdge, txn)(id) 148 | .then(function (e) { return new Q(e ? gremlin.wrapEdge(e) : null); }) 149 | .nodeify(callback); 150 | }; 151 | 152 | GraphWrapper.prototype.removeEdge = function (edge, callback) { 153 | var txn = this._getTransaction(); 154 | 155 | if (!(edge instanceof EdgeWrapper)) { 156 | throw new TypeError('edge must be an instance of EdgeWrapper'); 157 | } 158 | 159 | return Q.nbind(txn.removeEdge, txn)(edge.unwrap()) 160 | .nodeify(callback); 161 | }; 162 | 163 | GraphWrapper.prototype.query = function () { 164 | var txn = this._getTransaction(); 165 | return this.gremlin.wrapQuery(txn.querySync()); 166 | }; 167 | 168 | // com.tinkerpop.blueprints.ThreadedTransactionalGraph interface 169 | GraphWrapper.prototype.newTransaction = function () { 170 | if (!this.isType(this.graph, 'com.tinkerpop.blueprints.ThreadedTransactionalGraph')) { 171 | throw new Error('Graph instance must implement com.tinkerpop.blueprints.ThreadedTransactionalGraph'); 172 | } 173 | var txn = this.graph.newTransactionSync(); 174 | return this.gremlin.wrap(txn); 175 | }; 176 | 177 | // com.tinkerpop.blueprints.TransactionalGraph interface 178 | GraphWrapper.prototype.commit = function (callback) { 179 | if (!this.isType(this.graph, 'com.tinkerpop.blueprints.TransactionalGraph')) { 180 | throw new Error('Graph instance must implement com.tinkerpop.blueprints.TransactionalGraph'); 181 | } 182 | var txn = this._getTransaction(); 183 | this._clearTransaction(); 184 | txn.commit(callback); 185 | }; 186 | 187 | GraphWrapper.prototype.rollback = function (callback) { 188 | if (!this.isType(this.graph, 'com.tinkerpop.blueprints.TransactionalGraph')) { 189 | throw new Error('Graph instance must implement com.tinkerpop.blueprints.TransactionalGraph'); 190 | } 191 | var txn = this._getTransaction(); 192 | this._clearTransaction(); 193 | txn.rollback(callback); 194 | }; 195 | 196 | GraphWrapper.prototype.shutdown = function (callback) { 197 | if (!this.isType(this.graph, 'com.tinkerpop.blueprints.TransactionalGraph')) { 198 | throw new Error('Graph instance must implement com.tinkerpop.blueprints.TransactionalGraph'); 199 | } 200 | var txn = this._getTransaction(); 201 | this._clearTransaction(); 202 | txn.shutdown(callback); 203 | }; 204 | 205 | // gremlin shell extensions for the graph object 206 | GraphWrapper.prototype._ = function () { 207 | var txn = this._getTransaction(); 208 | var pipeline = this.gremlin.wrapPipeline(txn); 209 | pipeline.pipeline._Sync(); 210 | return pipeline; 211 | }; 212 | 213 | GraphWrapper.prototype.start = function (start) { 214 | var txn = this._getTransaction(); 215 | var pipeline = this.gremlin.wrapPipeline(txn); 216 | // conditionally unwrap, we may be being passed a Java list instead 217 | // of one of our wrapper JavaScript objects 218 | if (start.unwrap) { 219 | start = start.unwrap(); 220 | } 221 | return pipeline.start(start); 222 | }; 223 | 224 | GraphWrapper.prototype.V = function () { 225 | var args = Array.prototype.slice.call(arguments); 226 | var txn = this._getTransaction(); 227 | var pipeline = this.gremlin.wrapPipeline(txn); 228 | return pipeline.V.apply(pipeline, args); 229 | }; 230 | 231 | GraphWrapper.prototype.E = function () { 232 | var args = Array.prototype.slice.call(arguments); 233 | var txn = this._getTransaction(); 234 | var pipeline = this.gremlin.wrapPipeline(txn); 235 | return pipeline.E.apply(pipeline, args); 236 | }; 237 | 238 | GraphWrapper.prototype.v = function () { 239 | var txn = this._getTransaction(); 240 | var gremlin = this.gremlin; 241 | var argPair = gremlin.extractArguments(Array.prototype.slice.call(arguments)); 242 | if (argPair.args.length === 0) 243 | throw new Error('v() requires at least one argument.'); 244 | 245 | return Q.all(argPair.args.map(function (id) { 246 | return Q.nbind(txn.getVertex, txn)(id); 247 | })) 248 | .then(function (vertices) { 249 | var list = new gremlin.ArrayList(); 250 | vertices.forEach(function (v) { 251 | list.addSync(v); 252 | }); 253 | return new Q(list); 254 | }) 255 | .then(function (list) { 256 | return new Q(gremlin.wrapPipeline(list.iteratorSync())); 257 | }) 258 | .nodeify(argPair.callback); 259 | }; 260 | 261 | GraphWrapper.prototype.e = function () { 262 | var txn = this._getTransaction(); 263 | var gremlin = this.gremlin; 264 | var argPair = gremlin.extractArguments(Array.prototype.slice.call(arguments)); 265 | if (argPair.args.length === 0) 266 | throw new Error('e() requires at least one argument.'); 267 | 268 | return Q.all(argPair.args.map(function (id) { 269 | return Q.nbind(txn.getEdge, txn)(id); 270 | })) 271 | .then(function (edges) { 272 | var list = new gremlin.ArrayList(); 273 | edges.forEach(function (e) { 274 | list.addSync(e); 275 | }); 276 | return new Q(list); 277 | }) 278 | .then(function (list) { 279 | return new Q(gremlin.wrapPipeline(list.iteratorSync())); 280 | }) 281 | .nodeify(argPair.callback); 282 | }; 283 | -------------------------------------------------------------------------------- /lib/gremlin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var fs = require('fs'); 5 | var glob = require('glob'); 6 | var path = require('path'); 7 | var Q = require('q'); 8 | 9 | var GraphWrapper = require('./graph-wrapper'); 10 | var QueryWrapper = require('./query-wrapper'); 11 | var PipelineWrapper = require('./pipeline-wrapper'); 12 | var VertexWrapper = require('./vertex-wrapper'); 13 | var EdgeWrapper = require('./edge-wrapper'); 14 | 15 | var Gremlin = module.exports = function (opts) { 16 | opts = opts || {}; 17 | opts.options = opts.options || []; 18 | opts.classpath = opts.classpath || []; 19 | 20 | // add default globbed lib/**/*.jar classpath 21 | opts.classpath.push(path.join(__dirname, '..', 'target', '**', '*.jar')); 22 | 23 | // initialize java 24 | var java = this.java = require('java'); 25 | 26 | // add options 27 | java.options.push('-Djava.awt.headless=true'); 28 | for (var i = 0; i < opts.options.length; i++) { 29 | java.options.push(opts.options[i]); 30 | } 31 | 32 | // add jar files 33 | for (var i = 0; i < opts.classpath.length; i++) { 34 | var pattern = opts.classpath[i]; 35 | var filenames = glob.sync(pattern); 36 | for (var j = 0; j < filenames.length; j++) { 37 | java.classpath.push(filenames[j]); 38 | } 39 | } 40 | 41 | var MIN_VALUE = 0; 42 | var MAX_VALUE = java.newInstanceSync('java.lang.Long', 2147483647); 43 | var JSONResultConverter = java.import('com.tinkerpop.rexster.gremlin.converter.JSONResultConverter'); 44 | 45 | this.GremlinPipeline = java.import('com.tinkerpop.gremlin.groovy.GremlinGroovyPipeline'); 46 | this.NULL = java.callStaticMethodSync('org.codehaus.groovy.runtime.NullObject', 'getNullObject'); 47 | 48 | var Class = this.Class = java.import('java.lang.Class'); 49 | this.ArrayList = java.import('java.util.ArrayList'); 50 | this.HashMap = java.import('java.util.HashMap'); 51 | this.Table = java.import('com.tinkerpop.pipes.util.structures.Table'); 52 | this.Tree = java.import('com.tinkerpop.pipes.util.structures.Tree'); 53 | 54 | this.Direction = java.import('com.tinkerpop.blueprints.Direction'); 55 | this.Tokens = java.import('com.tinkerpop.gremlin.Tokens$T'); 56 | this.Compare = java.import('com.tinkerpop.blueprints.Compare'); 57 | this.Contains = java.import('com.tinkerpop.blueprints.Contains'); 58 | 59 | this.ClassTypes = { 60 | 'String': Class.forNameSync('java.lang.String'), 61 | 'Vertex': java.getClassLoader().loadClassSync('com.tinkerpop.blueprints.Vertex'), 62 | 'Edge': java.getClassLoader().loadClassSync('com.tinkerpop.blueprints.Edge'), 63 | 'Byte': Class.forNameSync('java.lang.Byte'), 64 | 'Character': Class.forNameSync('java.lang.Character'), 65 | 'Double': Class.forNameSync('java.lang.Double'), 66 | 'Float': Class.forNameSync('java.lang.Float'), 67 | 'Integer': Class.forNameSync('java.lang.Integer'), 68 | 'Long': Class.forNameSync('java.lang.Long'), 69 | 'Short': Class.forNameSync('java.lang.Short'), 70 | 'Number': Class.forNameSync('java.lang.Number'), 71 | 'BigDecimal': Class.forNameSync('java.math.BigDecimal'), 72 | 'BigInteger': Class.forNameSync('java.math.BigInteger') 73 | }; 74 | 75 | this.JSON = new JSONResultConverter(null, MIN_VALUE, MAX_VALUE, null); 76 | }; 77 | 78 | Gremlin.GraphWrapper = require('./graph-wrapper'); 79 | Gremlin.QueryWrapper = require('./query-wrapper'); 80 | Gremlin.PipelineWrapper = require('./pipeline-wrapper'); 81 | Gremlin.ElementWrapper = require('./element-wrapper'); 82 | Gremlin.VertexWrapper = require('./vertex-wrapper'); 83 | Gremlin.EdgeWrapper = require('./edge-wrapper'); 84 | 85 | Gremlin.prototype.isType = function (o, typeName) { 86 | if (!o || !_.isObject(o)) return false; 87 | if (!o._isType) { 88 | o._isType = {}; 89 | } 90 | var res = o._isType[typeName]; 91 | if (res === undefined) { 92 | try { 93 | res = this.java.instanceOf(o, typeName); 94 | } catch (err) { 95 | res = false; 96 | } 97 | o._isType[typeName] = res; 98 | } 99 | return res; 100 | }; 101 | 102 | Gremlin.prototype.toList = function (obj, callback) { 103 | var promise; 104 | if (_.isArray(obj)) { 105 | var list = new this.ArrayList(); 106 | for (var i = 0; i < obj.length; i++) { 107 | list.addSync(obj[i]); 108 | } 109 | promise = new Q(list); 110 | } 111 | else if (obj.getClassSync().isArraySync()) { 112 | promise = Q.nbind(this.java.callStaticMethod, this.java)('java.util.Arrays', 'asList', obj); 113 | } 114 | else { 115 | promise = Q.nbind(this.java.callStaticMethod, this.java)('com.google.common.collect.Lists', 'newArrayList', obj); 116 | } 117 | 118 | return promise.nodeify(callback); 119 | }; 120 | 121 | Gremlin.prototype.toListSync = function (obj) { 122 | if (_.isArray(obj)) { 123 | var list = new this.ArrayList(); 124 | for (var i = 0; i < obj.length; i++) { 125 | list.addSync(obj[i]); 126 | } 127 | return list; 128 | } 129 | if (obj.getClassSync().isArraySync()) { 130 | return this.java.callStaticMethodSync('java.util.Arrays', 'asList', obj); 131 | } 132 | return this.java.callStaticMethodSync('com.google.common.collect.Lists', 'newArrayList', obj); 133 | }; 134 | 135 | Gremlin.prototype.toJSON = function (obj, callback) { 136 | // if this is a wrapped datatype, unwrap it to get the underlying Java object 137 | if (obj && obj.unwrap) { 138 | obj = obj.unwrap(); 139 | } 140 | 141 | return Q.nbind(this.JSON.convert, this.JSON)(obj) 142 | .then(function (json) { return JSON.parse(json.toString()); }) 143 | .nodeify(callback); 144 | }; 145 | 146 | Gremlin.prototype.toJSONSync = function (obj) { 147 | // if this is a wrapped datatype, unwrap it to get the underlying Java object 148 | if (obj && obj.unwrap) { 149 | obj = obj.unwrap(); 150 | } 151 | 152 | var json = this.JSON.convertSync(obj); 153 | json = JSON.parse(json.toString()); 154 | return json; 155 | }; 156 | 157 | Gremlin.prototype.getEngine = function () { 158 | if (this._engine) { 159 | return this._engine; 160 | } 161 | var GremlinGroovyScriptEngine = this.java.import('com.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine'); 162 | this._engine = new GremlinGroovyScriptEngine(); 163 | return this._engine; 164 | }; 165 | 166 | Gremlin.prototype.wrap = function (val) { 167 | return new GraphWrapper(this, val); 168 | }; 169 | 170 | Gremlin.prototype.wrapQuery = function (val) { 171 | return new QueryWrapper(this, val); 172 | }; 173 | 174 | Gremlin.prototype.wrapPipeline = function (val) { 175 | return new PipelineWrapper(this, val); 176 | }; 177 | 178 | Gremlin.prototype.wrapVertex = function (val) { 179 | return new VertexWrapper(this, val); 180 | }; 181 | 182 | Gremlin.prototype.wrapEdge = function (val) { 183 | return new EdgeWrapper(this, val); 184 | }; 185 | 186 | Gremlin.prototype.extractArguments = function (args) { 187 | var callback = _.last(args); 188 | 189 | if (_.isFunction(callback)) 190 | args = _.initial(args); 191 | else 192 | callback = undefined; 193 | 194 | if (args.length === 1 && _.isArray(args[0])) 195 | args = args[0]; 196 | 197 | return { 198 | args: args, 199 | callback: callback 200 | }; 201 | }; 202 | 203 | 204 | -------------------------------------------------------------------------------- /lib/pipeline-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var Q = require('q'); 5 | var dlog = require('debug')('pipeline-wrapper'); 6 | 7 | var PipelineWrapper = module.exports = function (gremlin, start) { 8 | if (start && gremlin.isType(start, 'java.lang.Iterable')) { 9 | throw new Error('Resolve iterable instances asynchronously to iterators to avoid unexpected potential blocking (e.g. it.iterator())'); 10 | } 11 | this.gremlin = gremlin; 12 | this.pipeline = start ? new gremlin.GremlinPipeline(start) : new gremlin.GremlinPipeline(); 13 | }; 14 | 15 | PipelineWrapper.prototype._parseVarargs = function (args, type) { 16 | var va, self = this; 17 | if (_.isArray(args[args.length - 1])) { 18 | va = args.pop(); 19 | } else { 20 | va = []; 21 | // HACK - instead of actually converting JS strings -> java.lang.String 22 | // instances as part of javify, we check the type with _isString 23 | var test = type === 'java.lang.String' ? _.isString : function (o) { 24 | return self.gremlin.isType(o, type); 25 | }; 26 | while (test(args[args.length - 1])) { 27 | va.unshift(args.pop()); 28 | } 29 | } 30 | args.push(this.gremlin.java.newArray(type, va)); 31 | }; 32 | 33 | PipelineWrapper.prototype._isClosure = function (val) { 34 | var closureRegex = /^\{.*\}$/; 35 | return _.isString(val) && val.search(closureRegex) > -1; 36 | }; 37 | 38 | PipelineWrapper.prototype._javify = function (arg) { 39 | if (arg.unwrap) { 40 | return arg.unwrap(); 41 | } else if (this._isClosure(arg)) { 42 | return this.gremlin.getEngine().evalSync(arg); 43 | } 44 | return arg; 45 | }; 46 | 47 | PipelineWrapper.prototype._jsify = function (arg) { 48 | if (!_.isObject(arg)) { 49 | return arg; 50 | } else if (arg.longValue) { 51 | return parseInt(arg.longValue, 10); 52 | } else if (this.gremlin.isType(arg, 'com.tinkerpop.blueprints.Vertex')) { 53 | return this.gremlin.wrapVertex(arg); 54 | } else if (this.gremlin.isType(arg, 'com.tinkerpop.blueprints.Edge')) { 55 | return this.gremlin.wrapEdge(arg); 56 | } else if (this.gremlin.isType(arg, 'java.util.Map')) { 57 | // it seems this type of coercion could be ported to node-java 58 | // https://github.com/joeferner/node-java/issues/56 59 | var map = {}; 60 | var it = arg.entrySetSync().iteratorSync(); 61 | while (it.hasNextSync()) { 62 | var pair = it.nextSync(); 63 | map[pair.getKeySync()] = this._jsify(pair.getValueSync()); 64 | } 65 | return map; 66 | } 67 | return arg; 68 | }; 69 | 70 | PipelineWrapper.prototype.unwrap = function () { 71 | return this.pipeline; 72 | }; 73 | 74 | PipelineWrapper.prototype.add = function (type, args) { 75 | this.pipeline[type + 'Sync'].apply(this.pipeline, args); 76 | return this; 77 | }; 78 | 79 | PipelineWrapper.prototype.V = function () { 80 | var args = Array.prototype.slice.call(arguments); 81 | return this.add('V', args); 82 | }; 83 | 84 | PipelineWrapper.prototype.E = function () { 85 | var args = Array.prototype.slice.call(arguments); 86 | return this.add('E', args); 87 | }; 88 | 89 | PipelineWrapper.prototype.has = function () { 90 | var args = Array.prototype.slice.call(arguments); 91 | return this.add('has', args); 92 | }; 93 | 94 | PipelineWrapper.prototype.hasNot = function () { 95 | var args = Array.prototype.slice.call(arguments); 96 | return this.add('hasNot', args); 97 | }; 98 | 99 | PipelineWrapper.prototype.interval = function () { 100 | var args = Array.prototype.slice.call(arguments); 101 | return this.add('interval', args); 102 | }; 103 | 104 | PipelineWrapper.prototype.bothE = function () { 105 | var args = Array.prototype.slice.call(arguments); 106 | this._parseVarargs(args, 'java.lang.String'); 107 | return this.add('bothE', args); 108 | }; 109 | 110 | PipelineWrapper.prototype.both = function () { 111 | var args = Array.prototype.slice.call(arguments); 112 | this._parseVarargs(args, 'java.lang.String'); 113 | return this.add('both', args); 114 | }; 115 | 116 | PipelineWrapper.prototype.bothV = function () { 117 | return this.add('bothV'); 118 | }; 119 | 120 | PipelineWrapper.prototype.idEdge = function () { 121 | var args = Array.prototype.slice.call(arguments); 122 | return this.add('idEdge', args); 123 | }; 124 | 125 | PipelineWrapper.prototype.id = function () { 126 | return this.add('id'); 127 | }; 128 | 129 | PipelineWrapper.prototype.idVertex = function () { 130 | var args = Array.prototype.slice.call(arguments); 131 | return this.add('idVertex', args); 132 | }; 133 | 134 | PipelineWrapper.prototype.inE = function () { 135 | var args = Array.prototype.slice.call(arguments); 136 | this._parseVarargs(args, 'java.lang.String'); 137 | return this.add('inE', args); 138 | }; 139 | 140 | PipelineWrapper.prototype.in = function () { 141 | var args = Array.prototype.slice.call(arguments); 142 | this._parseVarargs(args, 'java.lang.String'); 143 | return this.add('in', args); 144 | }; 145 | 146 | PipelineWrapper.prototype.inV = function () { 147 | return this.add('inV'); 148 | }; 149 | 150 | PipelineWrapper.prototype.label = function () { 151 | return this.add('label'); 152 | }; 153 | 154 | PipelineWrapper.prototype.outE = function () { 155 | var args = Array.prototype.slice.call(arguments); 156 | this._parseVarargs(args, 'java.lang.String'); 157 | return this.add('outE', args); 158 | }; 159 | 160 | PipelineWrapper.prototype.out = function () { 161 | var args = Array.prototype.slice.call(arguments); 162 | this._parseVarargs(args, 'java.lang.String'); 163 | return this.add('out', args); 164 | }; 165 | 166 | PipelineWrapper.prototype.outV = function () { 167 | return this.add('outV'); 168 | }; 169 | 170 | PipelineWrapper.prototype.map = function () { 171 | var args = Array.prototype.slice.call(arguments); 172 | this._parseVarargs(args, 'java.lang.String'); 173 | return this.add('map', args); 174 | }; 175 | 176 | PipelineWrapper.prototype.property = function () { 177 | var args = Array.prototype.slice.call(arguments); 178 | return this.add('property', args); 179 | }; 180 | 181 | PipelineWrapper.prototype.step = function () { 182 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 183 | return this.add('step', args); 184 | }; 185 | 186 | //////////////////// 187 | /// BRANCH PIPES /// 188 | //////////////////// 189 | 190 | PipelineWrapper.prototype.copySplit = function () { 191 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 192 | this._parseVarargs(args, 'com.tinkerpop.pipes.Pipe'); 193 | return this.add('copySplit', args); 194 | }; 195 | 196 | PipelineWrapper.prototype.exhaustMerge = function () { 197 | return this.add('exhaustMerge'); 198 | }; 199 | 200 | PipelineWrapper.prototype.fairMerge = function () { 201 | return this.add('fairMerge'); 202 | }; 203 | 204 | PipelineWrapper.prototype.ifThenElse = function () { 205 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 206 | return this.add('ifThenElse', args); 207 | }; 208 | 209 | PipelineWrapper.prototype.loop = function () { 210 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 211 | return this.add('loop', args); 212 | }; 213 | 214 | //////////////////// 215 | /// FILTER PIPES /// 216 | //////////////////// 217 | 218 | PipelineWrapper.prototype.and = function (/*final Pipe... pipes*/) { 219 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 220 | this._parseVarargs(args, 'com.tinkerpop.pipes.Pipe'); 221 | return this.add('and', args); 222 | }; 223 | 224 | PipelineWrapper.prototype.back = function (step) { 225 | var args = Array.prototype.slice.call(arguments); 226 | return this.add('back', args); 227 | }; 228 | 229 | PipelineWrapper.prototype.dedup = function (closure) { 230 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 231 | return this.add('dedup', args); 232 | }; 233 | 234 | PipelineWrapper.prototype.except = function () { 235 | var args = Array.prototype.slice.call(arguments); 236 | if (this.gremlin.isType(args[0], 'java.util.Collection')) { 237 | // assume except(final Collection collection) 238 | } else if (_.isArray(args[0])) { 239 | // assume except(final Collection collection) 240 | args[0] = this.gremlin.toListSync(args[0]); 241 | } else { 242 | // assume except(final String... namedSteps) 243 | this._parseVarargs(args, 'java.lang.String'); 244 | } 245 | return this.add('except', args); 246 | }; 247 | 248 | PipelineWrapper.prototype.filter = function (closure) { 249 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 250 | return this.add('filter', args); 251 | }; 252 | 253 | PipelineWrapper.prototype.or = function (/*final Pipe... pipes*/) { 254 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 255 | this._parseVarargs(args, 'com.tinkerpop.pipes.Pipe'); 256 | return this.add('or', args); 257 | }; 258 | 259 | PipelineWrapper.prototype.random = function () { 260 | var args = Array.prototype.slice.call(arguments); 261 | return this.add('random', args); 262 | }; 263 | 264 | PipelineWrapper.prototype.index = function (idx) { 265 | return this.add('range', [idx, idx]); 266 | }; 267 | 268 | PipelineWrapper.prototype.range = function () { 269 | var args = Array.prototype.slice.call(arguments); 270 | return this.add('range', args); 271 | }; 272 | 273 | PipelineWrapper.prototype.retain = function (/*final Collection collection*/) { 274 | var args = Array.prototype.slice.call(arguments); 275 | if (this.gremlin.isType(args[0], 'java.util.Collection')) { 276 | // assume retain(final Collection collection) 277 | } else if (_.isArray(args[0])) { 278 | // assume retain(final Collection collection) 279 | args[0] = this.gremlin.toListSync(args[0]); 280 | } else { 281 | // assume retain(final String... namedSteps) 282 | this._parseVarargs(args, 'java.lang.String'); 283 | } 284 | return this.add('retain', args); 285 | }; 286 | 287 | PipelineWrapper.prototype.simplePath = function () { 288 | return this.add('simplePath'); 289 | }; 290 | 291 | ///////////////////////// 292 | /// SIDE-EFFECT PIPES /// 293 | ///////////////////////// 294 | 295 | PipelineWrapper.prototype.aggregate = function () { 296 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 297 | if (_.isArray(args[0])) { 298 | args[0] = this.gremlin.toListSync(args[0]); 299 | } 300 | return this.add('aggregate', args); 301 | }; 302 | 303 | PipelineWrapper.prototype.optional = function () { 304 | var args = Array.prototype.slice.call(arguments); 305 | return this.add('optional', args); 306 | }; 307 | 308 | PipelineWrapper.prototype.groupBy = function (map, closure) { 309 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 310 | return this.add('groupBy', args); 311 | }; 312 | 313 | PipelineWrapper.prototype.groupCount = function () { 314 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 315 | return this.add('groupCount', args); 316 | }; 317 | 318 | PipelineWrapper.prototype.linkOut = function () { 319 | var args = Array.prototype.slice.call(arguments); 320 | return this.add('linkOut', args); 321 | }; 322 | 323 | PipelineWrapper.prototype.linkIn = function () { 324 | var args = Array.prototype.slice.call(arguments); 325 | return this.add('linkIn', args); 326 | }; 327 | 328 | PipelineWrapper.prototype.linkBoth = function () { 329 | var args = Array.prototype.slice.call(arguments); 330 | return this.add('linkBoth', args); 331 | }; 332 | 333 | PipelineWrapper.prototype.sideEffect = function () { 334 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 335 | return this.add('sideEffect', args); 336 | }; 337 | 338 | PipelineWrapper.prototype.store = function () { 339 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 340 | if (_.isArray(args[0])) { 341 | args[0] = this.gremlin.toListSync(args[0]); 342 | } 343 | return this.add('store', args); 344 | }; 345 | 346 | PipelineWrapper.prototype.table = function () { 347 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 348 | this._parseVarargs(args, 'groovy.lang.Closure'); 349 | if (_.isArray(args[1])) { 350 | args[1] = this.gremlin.toListSync(args[1]); 351 | } 352 | return this.add('table', args); 353 | }; 354 | 355 | PipelineWrapper.prototype.tree = function () { 356 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 357 | this._parseVarargs(args, 'groovy.lang.Closure'); 358 | return this.add('tree', args); 359 | }; 360 | 361 | /////////////////////// 362 | /// TRANSFORM PIPES /// 363 | /////////////////////// 364 | 365 | PipelineWrapper.prototype.gather = function () { 366 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 367 | return this.add('gather', args); 368 | }; 369 | 370 | PipelineWrapper.prototype._ = function () { 371 | return this.add('_'); 372 | }; 373 | 374 | PipelineWrapper.prototype.memoize = function () { 375 | var args = Array.prototype.slice.call(arguments); 376 | return this.add('memoize', args); 377 | }; 378 | 379 | PipelineWrapper.prototype.order = function () { 380 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 381 | return this.add('order', args); 382 | }; 383 | 384 | PipelineWrapper.prototype.path = function () { 385 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 386 | this._parseVarargs(args, 'groovy.lang.Closure'); 387 | return this.add('path', args); 388 | }; 389 | 390 | PipelineWrapper.prototype.scatter = function () { 391 | return this.add('scatter'); 392 | }; 393 | 394 | PipelineWrapper.prototype.select = function () { 395 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 396 | this._parseVarargs(args, 'groovy.lang.Closure'); 397 | if (_.isArray(args[0])) { 398 | args[0] = this.gremlin.toListSync(args[0]); 399 | } 400 | return this.add('select', args); 401 | }; 402 | 403 | PipelineWrapper.prototype.shuffle = function () { 404 | return this.add('shuffle'); 405 | }; 406 | 407 | PipelineWrapper.prototype.cap = function () { 408 | return this.add('cap'); 409 | }; 410 | 411 | PipelineWrapper.prototype.orderMap = function () { 412 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 413 | return this.add('orderMap', args); 414 | }; 415 | 416 | PipelineWrapper.prototype.transform = function () { 417 | var args = Array.prototype.slice.call(arguments).map(this._javify.bind(this)); 418 | return this.add('transform', args); 419 | }; 420 | 421 | ////////////////////// 422 | /// UTILITY PIPES /// 423 | ////////////////////// 424 | 425 | PipelineWrapper.prototype.as = function () { 426 | var args = Array.prototype.slice.call(arguments); 427 | return this.add('as', args); 428 | }; 429 | 430 | PipelineWrapper.prototype.start = function (obj) { 431 | if (this.gremlin.isType(obj, 'java.lang.Iterable')) { 432 | throw new Error('Resolve iterable instances asynchronously to iterators to avoid unexpected potential blocking (e.g. it.iterator())'); 433 | } 434 | return this.add('start', [obj]); 435 | }; 436 | 437 | /////////////////////// 438 | /// UTILITY METHODS /// 439 | /////////////////////// 440 | 441 | function pipePromiseWrap(op) { 442 | return function () { 443 | var argPair = this.gremlin.extractArguments(Array.prototype.slice.call(arguments)); 444 | dlog('pipePromiseWrap(%s)', op, argPair, this.pipeline[op]); 445 | return Q.npost(this.pipeline, op, argPair.args).nodeify(argPair.callback); 446 | }; 447 | } 448 | 449 | function pipePromiseJsifyWrap(op) { 450 | return function () { 451 | var self = this; 452 | var argPair = this.gremlin.extractArguments(Array.prototype.slice.call(arguments)); 453 | dlog('pipePromiseJsifyWrap(%s)', op, argPair, this.pipeline[op]); 454 | return Q.npost(this.pipeline, op, argPair.args) 455 | .then(function (res) { return new Q(self._jsify(res)); }) 456 | .nodeify(argPair.callback); 457 | }; 458 | } 459 | 460 | PipelineWrapper.prototype.count = pipePromiseJsifyWrap('count'); 461 | 462 | PipelineWrapper.prototype.iterate = pipePromiseWrap('iterate'); 463 | PipelineWrapper.prototype.iterator = pipePromiseWrap('iterator'); 464 | PipelineWrapper.prototype.hasNext = pipePromiseWrap('hasNext'); 465 | 466 | PipelineWrapper.prototype.next = pipePromiseJsifyWrap('next'); 467 | 468 | PipelineWrapper.prototype.fill = pipePromiseWrap('fill'); 469 | PipelineWrapper.prototype.enablePath = pipePromiseWrap('enablePath'); 470 | PipelineWrapper.prototype.optimize = pipePromiseWrap('optimize'); 471 | PipelineWrapper.prototype.remove = pipePromiseWrap('remove'); 472 | PipelineWrapper.prototype.reset = pipePromiseWrap('reset'); 473 | PipelineWrapper.prototype.getCurrentPath = pipePromiseWrap('getCurrentPath'); 474 | PipelineWrapper.prototype.getStarts = pipePromiseWrap('getStarts'); 475 | PipelineWrapper.prototype.get = pipePromiseWrap('get'); 476 | PipelineWrapper.prototype.equals = pipePromiseWrap('equals'); 477 | PipelineWrapper.prototype.size = pipePromiseWrap('size'); 478 | PipelineWrapper.prototype.toList = pipePromiseWrap('toList'); 479 | 480 | PipelineWrapper.prototype.toArray = function (callback) { 481 | var self = this; 482 | 483 | var deferred = Q.defer(); 484 | 485 | this.pipeline.toList(function (err, list) { 486 | if (err) 487 | deferred.reject(err); 488 | else { 489 | var arr = []; 490 | for (var i = 0, l = list.sizeSync(); i < l; i++) { 491 | var it = list.getSync(i); 492 | arr.push(self._jsify(it)); 493 | } 494 | deferred.resolve(arr); 495 | } 496 | }); 497 | 498 | return deferred.promise.nodeify(callback); 499 | }; 500 | 501 | PipelineWrapper.prototype.toJSON = function (callback) { 502 | return Q.nbind(this.gremlin.toJSON, this.gremlin)(this.pipeline).nodeify(callback); 503 | }; 504 | 505 | PipelineWrapper.prototype.toJSONSync = function () { 506 | return this.gremlin.toJSONSync(this.pipeline); 507 | }; 508 | -------------------------------------------------------------------------------- /lib/query-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function queryWrapSync(op) { 4 | return function () { 5 | var args = Array.prototype.slice.call(arguments); 6 | this.query[op].apply(this._query, args); 7 | return this; 8 | }; 9 | } 10 | 11 | var QueryWrapper = module.exports = function (gremlin, query) { 12 | this.gremlin = gremlin; 13 | this.query = query; 14 | }; 15 | 16 | QueryWrapper.prototype.has = queryWrapSync('has'); 17 | QueryWrapper.prototype.hasNot = queryWrapSync('hasNot'); 18 | QueryWrapper.prototype.interval = queryWrapSync('interval'); 19 | QueryWrapper.prototype.limit = queryWrapSync('limit'); 20 | QueryWrapper.prototype.vertices = queryWrapSync('vertices'); 21 | QueryWrapper.prototype.edges = queryWrapSync('edges'); 22 | -------------------------------------------------------------------------------- /lib/vertex-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var ElementWrapper = require('./element-wrapper'); 5 | 6 | var VertexWrapper = module.exports = function (gremlin, el) { 7 | ElementWrapper.call(this, gremlin, el); 8 | }; 9 | 10 | util.inherits(VertexWrapper, ElementWrapper); 11 | 12 | // public Iterable getEdges(Direction direction, String... labels); 13 | // public Iterable getVertices(Direction direction, String... labels); 14 | // public VertexQuery query(); 15 | // public Edge addEdge(String label, Vertex inVertex); 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gremlin", 3 | "description": "Gremlin for graph databases which implement the Blueprints property graph data model.", 4 | "version": "0.4.0", 5 | "keywords": [ 6 | "graph", 7 | "database", 8 | "tinkerpop", 9 | "gremlin", 10 | "blueprints" 11 | ], 12 | "homepage": "https://github.com/inolen/gremlin-node", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Frank Panetta", 17 | "email": "frank.panetta@entrendipity.com.au" 18 | }, 19 | { 20 | "name": "Anthony Pesch", 21 | "email": "inolen@gmail.com" 22 | }, 23 | { 24 | "name": "Jim Lloyd", 25 | "email": "jim.lloyd@gmail.com" 26 | } 27 | ], 28 | "contributors": [ 29 | { 30 | "name": "Jared Camins" 31 | } 32 | ], 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/inolen/gremlin-node.git" 36 | }, 37 | "main": "lib/gremlin.js", 38 | "readmeFilename": "README.md", 39 | "scripts": { 40 | "test": "make test", 41 | "install": "make package" 42 | }, 43 | "engines": { 44 | "node": ">=0.10.0" 45 | }, 46 | "dependencies": { 47 | "debug": "^2.0.0", 48 | "glob": "~3.2.6", 49 | "java": ">=0.2.8", 50 | "q": "^1.0.1", 51 | "underscore": "~1.5.2" 52 | }, 53 | "devDependencies": { 54 | "jshint": "~2.3.0", 55 | "mocha": "~1.14.0", 56 | "optimist": "~0.6.0", 57 | "sinon": "~1.7.3", 58 | "tmp": "0.0.24" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | com.entrendipity 7 | gremlin-node 8 | pom 9 | 0.1.14 10 | gremlin 11 | 12 | 13 | 2.5.0 14 | 2.5.0 15 | 2.5.0 16 | 2.5.0 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.apache.commons 23 | commons-lang3 24 | 3.1 25 | 26 | 27 | com.tinkerpop.gremlin 28 | gremlin-java 29 | ${gremlin.version} 30 | 31 | 32 | com.tinkerpop.gremlin 33 | gremlin-groovy 34 | ${blueprints.version} 35 | 36 | 37 | com.tinkerpop.rexster 38 | rexster-server 39 | ${rexster.version} 40 | 41 | 42 | com.tinkerpop.blueprints 43 | blueprints-core 44 | ${blueprints.version} 45 | 46 | 47 | com.tinkerpop 48 | pipes 49 | ${pipes.version} 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-dependency-plugin 58 | 2.4 59 | 60 | 61 | copy-dependencies 62 | package 63 | 64 | copy-dependencies 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/test-graph-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var assert = require('assert'); 5 | var fs = require('fs'); 6 | var sinon = require('sinon'); 7 | var tmp = require('tmp'); 8 | var Q = require('q'); 9 | var Gremlin = require('../lib/gremlin'); 10 | var GraphWrapper = require('../lib/graph-wrapper'); 11 | var VertexWrapper = require('../lib/vertex-wrapper'); 12 | var EdgeWrapper = require('../lib/edge-wrapper'); 13 | 14 | suite('graph-wrapper', function () { 15 | var gremlin; 16 | var graph; 17 | var g; 18 | var sandbox; 19 | 20 | suiteSetup(function () { 21 | gremlin = new Gremlin(); 22 | }); 23 | 24 | setup(function () { 25 | var TinkerGraphFactory = gremlin.java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraphFactory'); 26 | graph = TinkerGraphFactory.createTinkerGraphSync(); 27 | g = new GraphWrapper(gremlin, graph); 28 | sandbox = sinon.sandbox.create(); 29 | }); 30 | 31 | teardown(function () { 32 | sandbox.restore(); 33 | }); 34 | 35 | test('Non ThreadedTransactionalGraph instances do not start unique transactions', function () { 36 | graph.newTransactionSync = sandbox.spy(); 37 | g.addVertex(null, function () {}); 38 | assert(!graph.newTransactionSync.called); 39 | }); 40 | 41 | test('ThreadedTransactionalGraph starts unique transactions', function () { 42 | var fakeTxn = { 43 | addVertex: sandbox.stub() 44 | }; 45 | var fakeGraph = { 46 | newTransactionSync: sandbox.stub().returns(fakeTxn) 47 | }; 48 | var fakeGremlin = { 49 | isType: sandbox.stub() 50 | .withArgs(fakeGraph, 'com.tinkerpop.blueprints.ThreadedTransactionalGraph') 51 | .returns(true), 52 | toList: function () {}, 53 | toListSync: function () {}, 54 | toJSON: function () {}, 55 | toJSONSync: function () {}, 56 | extractArguments: sandbox.stub() 57 | .returns({ args: [ null ], callback: function () {} }) 58 | }; 59 | var g2 = new GraphWrapper(fakeGremlin, fakeGraph); 60 | 61 | // should start a new transaction 62 | g2.addVertex(null, function () {}); 63 | 64 | // should re-use the existing transaction 65 | g2.addVertex(null, function () {}); 66 | 67 | assert(fakeGremlin.isType.calledOnce); 68 | assert(fakeGraph.newTransactionSync.calledOnce); 69 | assert(fakeTxn.addVertex.calledTwice); 70 | }); 71 | 72 | test('addVertex(id) using callback API', function (done) { 73 | g.addVertex(null, function (err, v) { 74 | assert.ifError(err); 75 | assert(v instanceof VertexWrapper); 76 | done(); 77 | }); 78 | }); 79 | 80 | test('addVertex(id) using promise API', function (done) { 81 | g.addVertex(null) 82 | .then(function (v) { assert(v instanceof VertexWrapper); }, assert.ifError) 83 | .done(done); 84 | }); 85 | 86 | test('getVertex(id) using callback API', function (done) { 87 | g.getVertex('1', function (err, v) { 88 | assert.ifError(err); 89 | assert(v instanceof VertexWrapper); 90 | v.getProperty('name', function (err, name) { 91 | assert.ifError(err); 92 | assert.strictEqual(name, 'marko'); 93 | done(); 94 | }); 95 | }); 96 | }); 97 | 98 | test('getVertex(id) using promise API', function (done) { 99 | g.getVertex('1') 100 | .then(function (v) { assert(v instanceof VertexWrapper); return v.getProperty('name'); }, assert.ifError) 101 | .then(function (name) { assert.strictEqual(name, 'marko'); }, assert.ifError) 102 | .nodeify(done); 103 | }); 104 | 105 | test('removeVertex(v) using callback API', function (done) { 106 | g.getVertex('1', function (err, v) { 107 | assert.ifError(err); 108 | assert(v instanceof VertexWrapper); 109 | 110 | g.removeVertex(v, function (err) { 111 | assert.ifError(err); 112 | 113 | g.getVertex('1', function (err, v) { 114 | assert.ifError(err); 115 | assert(!v); 116 | done(); 117 | }); 118 | }); 119 | }); 120 | }); 121 | 122 | test('removeVertex(v) using promise API', function (done) { 123 | g.getVertex('1') 124 | .then(function (v) { assert(v instanceof VertexWrapper); return g.removeVertex(v); }, assert.ifError) 125 | .then(function () { return g.getVertex('1'); }, assert.ifError) 126 | .then(function (v) { assert.strictEqual(v, null); }, assert.ifError) 127 | .nodeify(done); 128 | }); 129 | 130 | test('addEdge(id, v1, v2) using callback API', function (done) { 131 | g.v(1, 2, function (err, pipe) { 132 | assert.ifError(err); 133 | assert(pipe); 134 | 135 | pipe.next(function (err, v1) { 136 | assert.ifError(err); 137 | assert(v1 instanceof VertexWrapper); 138 | 139 | pipe.next(function (err, v2) { 140 | assert.ifError(err); 141 | assert(v2 instanceof VertexWrapper); 142 | 143 | g.addEdge(null, v1, v2, 'buddy', function (err, e) { 144 | assert.ifError(err); 145 | assert(e instanceof EdgeWrapper); 146 | assert.strictEqual(e.getId(), '0'); 147 | assert.strictEqual(e.getLabel(), 'buddy'); 148 | done(); 149 | }); 150 | }); 151 | }); 152 | }); 153 | }); 154 | 155 | test('addEdge(id, v1, v2) using promise API', function (done) { 156 | var pipe, v1; 157 | g.v(1, 2) 158 | .then(function (_pipe) { assert(_pipe); pipe = _pipe; return pipe.next(); }, assert.ifError) 159 | .then(function (_v1) { v1 = _v1; assert(v1 instanceof VertexWrapper); return pipe.next(); }, assert.ifError) 160 | .then(function (v2) { 161 | assert(v2 instanceof VertexWrapper); 162 | return g.addEdge(null, v1, v2, 'buddy'); 163 | }, assert.ifError) 164 | .then(function (e) { 165 | assert(e instanceof EdgeWrapper); 166 | assert.strictEqual(e.getId(), '0'); 167 | assert.strictEqual(e.getLabel(), 'buddy'); 168 | }, assert.ifError) 169 | .done(done); 170 | }); 171 | 172 | test('getEdge(id) using callback API', function (done) { 173 | g.getEdge('7', function (err, e) { 174 | assert.ifError(err); 175 | assert(e instanceof EdgeWrapper); 176 | assert.strictEqual(e.getId(), '7'); 177 | assert.strictEqual(e.getLabel(), 'knows'); 178 | e.getProperty('weight') 179 | .then(function (weight) { 180 | console.log('Edge(7) weight is:', weight); 181 | assert(weight > 0.0 && weight < 1.0); 182 | }, assert.ifError) 183 | .done(done); 184 | }); 185 | }); 186 | 187 | test('getEdge(id) using promise API', function (done) { 188 | g.getEdge('7') 189 | .then(function (e) { 190 | assert(e instanceof EdgeWrapper); 191 | assert.strictEqual(e.getId(), '7'); 192 | assert.strictEqual(e.getLabel(), 'knows'); 193 | }, assert.ifError) 194 | .done(done); 195 | }); 196 | 197 | test('removeEdge(e) using callback API', function (done) { 198 | g.getEdge('7', function (err, e) { 199 | assert.ifError(err); 200 | assert(e instanceof EdgeWrapper); 201 | 202 | g.removeEdge(e, function (err) { 203 | assert.ifError(err); 204 | 205 | g.getEdge('7', function (err, e) { 206 | assert.ifError(err); 207 | assert(!e); 208 | done(); 209 | }); 210 | }); 211 | }); 212 | }); 213 | 214 | test('removeEdge(e) using promise API', function (done) { 215 | g.getEdge('7') 216 | .then(function (e) {assert(e instanceof EdgeWrapper); return g.removeEdge(e); }, assert.ifError) 217 | .then(function () { return g.getEdge('7'); }, assert.ifError) 218 | .then(function (e) { assert(!e); }, assert.ifError) 219 | .done(done); 220 | }); 221 | 222 | test('setProperty(key, value) / getProperty(key) using callback API', function (done) { 223 | g.getVertex('1', function (err, v) { 224 | assert.ifError(err); 225 | assert(v instanceof VertexWrapper); 226 | v.setProperty('name', 'john', function (err) { 227 | assert.ifError(err); 228 | v.getProperty('name', function (err, name) { 229 | assert.ifError(err); 230 | assert.strictEqual(name, 'john'); 231 | done(); 232 | }); 233 | }); 234 | }); 235 | }); 236 | 237 | test('setProperty(key, value) / getProperty(key) using promise API', function (done) { 238 | var v; 239 | g.getVertex('1') 240 | .then(function (_v) { v = _v; assert(v instanceof VertexWrapper); return v; }, assert.ifError) 241 | .then(function () { return v.getProperty('name'); }, assert.ifError) 242 | .then(function (name) { assert.strictEqual(name, 'marko'); return v; }, assert.ifError) 243 | .then(function () { return v.setProperty('name', 'john'); }, assert.ifError) 244 | .then(function () { return v.getProperty('name'); }, assert.ifError) 245 | .then(function (name) { assert.strictEqual(name, 'john'); }, assert.ifError) 246 | .done(done); 247 | }); 248 | 249 | test('setProperties(props) / getProperties(props) using callback API', function (done) { 250 | g.getVertex('1', function (err, v) { 251 | var expectedProps = { 'name': 'josh', 'age': 45, 'foo': 23, 'bar': 42, 'xxx': 'yyy' }; 252 | v.setProperties(expectedProps, function (err) { 253 | assert.ifError(err); 254 | v.getProperties(Object.keys(expectedProps), function (err, props) { 255 | assert.ifError(err); 256 | assert.deepEqual(props, expectedProps); 257 | done(); 258 | }); 259 | }); 260 | }); 261 | }); 262 | 263 | test('setProperties(props) / getProperties(props) using promise API', function (done) { 264 | g.getVertex('1', function (err, v) { 265 | var expectedProps = { 'name': 'josh', 'age': 45, 'foo': 23, 'bar': 42, 'xxx': 'yyy' }; 266 | v.setProperties(expectedProps) 267 | .then(function () { return v.getProperties(Object.keys(expectedProps)); }, assert.ifError) 268 | .then(function (props) { assert.deepEqual(props, expectedProps); }, assert.ifError) 269 | .done(done); 270 | }); 271 | }); 272 | 273 | test('removeProperty(key) using callback API', function (done) { 274 | g.getVertex('1', function (err, v) { 275 | assert.ifError(err); 276 | assert(v instanceof VertexWrapper); 277 | v.removeProperty('name', function (err, res) { 278 | assert.ifError(err); 279 | v.getProperty('name', function (err, name) { 280 | assert.ifError(err); 281 | assert.strictEqual(name, null); 282 | done(); 283 | }); 284 | }); 285 | }); 286 | }); 287 | 288 | test('removeProperty(key) using promises API', function (done) { 289 | var v; 290 | g.getVertex('1') 291 | .then(function (_v) { v = _v; assert(v instanceof VertexWrapper); return v; }, assert.ifError) 292 | .then(function () { return v.removeProperty('name'); }, assert.ifError) 293 | .then(function () { return v.getProperty('name'); }, assert.ifError) 294 | .then(function (name) { assert.strictEqual(name, null); }, assert.ifError) 295 | .done(done); 296 | }); 297 | 298 | test('removeProperties(props) using callback API', function (done) { 299 | g.getVertex('1', function (err, v) { 300 | assert.ifError(err); 301 | assert(v instanceof VertexWrapper); 302 | v.removeProperties(['name', 'age'], function (err) { 303 | assert.ifError(err); 304 | v.getProperties(['name', 'age'], function (err, props) { 305 | assert.ifError(err); 306 | assert.deepEqual(props, {name: null, age: null}); 307 | done(); 308 | }); 309 | }); 310 | }); 311 | }); 312 | 313 | test('removeProperties(props) using promises API', function (done) { 314 | var v; 315 | g.getVertex('1') 316 | .then(function (_v) { v = _v; assert(v instanceof VertexWrapper); return v; }, assert.ifError) 317 | .then(function () { return v.getProperties(['name', 'age']); }, assert.ifError) 318 | .then(function (props) { assert.deepEqual(props, {name: 'marko', age: 29}); }, assert.ifError) 319 | .then(function () { return v.removeProperties(['name', 'age']); }, assert.ifError) 320 | .then(function () { return v.getProperties(['name', 'age']); }, assert.ifError) 321 | .then(function (props) { assert.deepEqual(props, {name: null, age: null}); }, assert.ifError) 322 | .done(done); 323 | }); 324 | 325 | test('v(id) with single id using callback API', function (done) { 326 | g.v('2', function (err, pipe) { 327 | assert.ifError(err); 328 | pipe.id().toJSON(function (err, ids) { 329 | assert.ifError(err); 330 | assert.deepEqual(ids, ['2']); 331 | done(); 332 | }); 333 | }); 334 | }); 335 | 336 | test('v(id) with single id using promise API', function (done) { 337 | var expected = ['2']; 338 | g.v('2') 339 | .then(function (pipe) { return pipe.id().toJSON(); }, assert.ifError) 340 | .then(function (json) { assert.deepEqual(json, expected); }, assert.ifError) 341 | .done(done); 342 | }); 343 | 344 | test('v(id) with id list using callback API', function (done) { 345 | g.v('2', '4', function (err, pipe) { 346 | assert.ifError(err); 347 | pipe.id().toJSON(function (err, ids) { 348 | assert.ifError(err); 349 | assert.deepEqual(ids, ['2', '4']); 350 | done(); 351 | }); 352 | }); 353 | }); 354 | 355 | test('v(id...) with id list using promise API', function (done) { 356 | var expected = ['2', '4']; 357 | g.v('2', '4') 358 | .then(function (pipe) { return pipe.id().toJSON(); }, assert.ifError) 359 | .then(function (json) { assert.deepEqual(json, expected); }, assert.ifError) 360 | .done(done); 361 | }); 362 | 363 | test('v(id...) with id array using promise API', function (done) { 364 | var expected = ['2', '4']; 365 | g.v(['2', '4']) 366 | .then(function (pipe) { return pipe.id().toJSON(); }, assert.ifError) 367 | .then(function (json) { assert.deepEqual(json, expected); }, assert.ifError) 368 | .done(done); 369 | }); 370 | 371 | test('v(id) with invalid id using promise API', function (done) { 372 | g.v('99') 373 | .then(function (pipe) { return pipe.toJSON(); }, assert.ifError) 374 | .then(function (json) { assert.deepEqual(json, [ null ]); }, assert.ifError) 375 | .done(done); 376 | }); 377 | 378 | test('g.toJSON() using callback API', function (done) { 379 | var expected = [ 'tinkergraph[vertices:6 edges:6]' ]; 380 | g.toJSON(function (err, json) { 381 | assert.ifError(err); 382 | assert.deepEqual(json, expected); 383 | done(); 384 | }); 385 | }); 386 | 387 | test('g.toJSON() using promise API', function (done) { 388 | var expected = [ 'tinkergraph[vertices:6 edges:6]' ]; 389 | g.toJSON() 390 | .then(function (json) { assert.deepEqual(json, expected); }, assert.ifError) 391 | .done(done); 392 | }); 393 | 394 | test('g.toJSONSync()', function (done) { 395 | var json = g.toJSONSync(); 396 | var expected = [ 'tinkergraph[vertices:6 edges:6]' ]; 397 | assert.deepEqual(json, expected); 398 | done(); 399 | }); 400 | 401 | test('g.saveAndLoadGraphML()', function (done) { 402 | tmp.tmpName(function (err, path) { 403 | if (err) { 404 | // A failure in tmpName is not a failure in gremlin-node. 405 | // If this ever fails, it is likely some environmental problem. 406 | throw err; 407 | } 408 | g.saveGraphMLSync(path); 409 | var TinkerGraph = gremlin.java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraph'); 410 | var h = gremlin.wrap(new TinkerGraph()); 411 | var json = h.toJSONSync(); 412 | var expected = [ 'tinkergraph[vertices:0 edges:0]' ]; 413 | assert.deepEqual(json, expected); 414 | h.loadGraphMLSync(path); 415 | json = h.toJSONSync(); 416 | expected = [ 'tinkergraph[vertices:6 edges:6]' ]; 417 | assert.deepEqual(json, expected); 418 | fs.unlink(path, done); 419 | }); 420 | }); 421 | 422 | test('g.saveAndLoadGraphSON()', function (done) { 423 | tmp.tmpName(function (err, path) { 424 | if (err) { 425 | // A failure in tmpName is not a failure in gremlin-node. 426 | // If this ever fails, it is likely some environmental problem. 427 | throw err; 428 | } 429 | g.saveGraphSONSync(path); 430 | var TinkerGraph = gremlin.java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraph'); 431 | var h = gremlin.wrap(new TinkerGraph()); 432 | var json = h.toJSONSync(); 433 | var expected = [ 'tinkergraph[vertices:0 edges:0]' ]; 434 | assert.deepEqual(json, expected); 435 | h.loadGraphSONSync(path); 436 | json = h.toJSONSync(); 437 | expected = [ 'tinkergraph[vertices:6 edges:6]' ]; 438 | assert.deepEqual(json, expected); 439 | fs.unlink(path, done); 440 | }); 441 | }); 442 | 443 | }); 444 | -------------------------------------------------------------------------------- /test/test-gremlin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var assert = require('assert'); 5 | var sinon = require('sinon'); 6 | var Gremlin = require('../lib/gremlin'); 7 | var GraphWrapper = require('../lib/graph-wrapper'); 8 | 9 | suite('gremlin', function () { 10 | var gremlin; 11 | var graph; 12 | var g; 13 | 14 | suiteSetup(function () { 15 | gremlin = new Gremlin(); 16 | }); 17 | 18 | setup(function () { 19 | var TinkerGraphFactory = gremlin.java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraphFactory'); 20 | graph = TinkerGraphFactory.createTinkerGraphSync(); 21 | g = new GraphWrapper(gremlin, graph); 22 | }); 23 | 24 | test('Wrapped objects can be converted to JS objects using gremlin.toJSON', function (done) { 25 | g.v('2', function (err, res) { 26 | gremlin.toJSON(res, function (err, json) { 27 | assert.ifError(err); 28 | assert.strictEqual(json[0]._id, '2'); 29 | done(); 30 | }); 31 | }); 32 | }); 33 | 34 | test('Unwrapped objects can be converted to JS objects using gremlin.toJSON', function (done) { 35 | g.getVertex('2', function (err, res) { 36 | gremlin.toJSON(res.el, function (err, json) { 37 | assert.ifError(err); 38 | assert.strictEqual(json[0]._id, '2'); 39 | done(); 40 | }); 41 | }); 42 | }); 43 | 44 | test('gremlin.toJSON throws error but does not crash when passed null', function (done) { 45 | gremlin.toJSON(null, function (err, json) { 46 | assert(err); 47 | done(); 48 | }); 49 | }); 50 | 51 | test('gremlin.toJSON throws error but does not crash when passed undefined', function (done) { 52 | gremlin.toJSON(undefined, function (err, json) { 53 | assert(err); 54 | done(); 55 | }); 56 | }); 57 | 58 | test('gremlin.toList(jsarray) using callback API', function (done) { 59 | gremlin.toList(['a', 'b', 'c'], function (err, list) { 60 | assert.ifError(err); 61 | assert(gremlin.isType(list, 'java.util.Collection')); 62 | done(); 63 | }); 64 | }); 65 | 66 | test('gremlin.toList(jsarray) using promise API', function (done) { 67 | gremlin.toList(['a', 'b', 'c']) 68 | .then(function (list) { assert(gremlin.isType(list, 'java.util.Collection')); }, assert.ifError) 69 | .done(done); 70 | }); 71 | 72 | }); 73 | -------------------------------------------------------------------------------- /test/test-pipeline-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore'); 4 | var assert = require('assert'); 5 | var path = require('path'); 6 | var Q = require('q'); 7 | var Gremlin = require('../lib/gremlin'); 8 | var GraphWrapper = require('../lib/graph-wrapper'); 9 | 10 | // For reference, see the java interface: 11 | // https://github.com/tinkerpop/gremlin/blob/master/gremlin-java/src/main/java/com/tinkerpop/gremlin/java/GremlinFluentPipeline.java 12 | 13 | function compareValues(aval, bval) { 14 | if (!_.isUndefined(aval) && !_.isUndefined(bval)) { 15 | if (aval === bval) return 0; 16 | if (aval < bval) return -1; 17 | return 1; 18 | } 19 | else if (_.isUndefined(aval) && _.isUndefined(bval)) { 20 | return 0; 21 | } 22 | else if (_.isUndefined(aval)) { 23 | return 1; 24 | } 25 | else { 26 | return -1; 27 | } 28 | } 29 | 30 | function compareBy(keys) { 31 | return function compare(a, b) { 32 | for (var i = 0; i < keys.length; ++i) { 33 | var key = keys[i]; 34 | var comp = compareValues(a[key], b[key]); 35 | if (comp !== 0) 36 | return comp; 37 | } 38 | return 0; 39 | }; 40 | } 41 | 42 | var compareNameAge = compareBy(['name', 'age']); 43 | 44 | suite('pipeline-wrapper', function () { 45 | var gremlin; 46 | var java; 47 | var graph; 48 | var g; 49 | 50 | suiteSetup(function () { 51 | gremlin = new Gremlin(); 52 | java = gremlin.java; 53 | }); 54 | 55 | setup(function () { 56 | var TinkerGraphFactory = java.import('com.tinkerpop.blueprints.impls.tg.TinkerGraphFactory'); 57 | graph = TinkerGraphFactory.createTinkerGraphSync(); 58 | g = new GraphWrapper(gremlin, graph); 59 | }); 60 | 61 | test('V(string key, object value)', function (done) { 62 | g.V('name', 'marko').next(function (err, v) { 63 | assert.ifError(err); 64 | v.getProperty('name', function (err, name) { 65 | assert.ifError(err); 66 | assert.strictEqual(name, 'marko'); 67 | done(); 68 | }); 69 | }); 70 | }); 71 | 72 | // We test map() here early and then use it in multiple tests below to construct expected values. 73 | // This makes the tests somewhat more complex, but serves the useful purpose of making the functions 74 | // more understandable to programmers learning gremlin. 75 | test('map()', function (done) { 76 | g.V().map().toArray(function (err, verts) { 77 | assert.ifError(err); 78 | var expected = [ 79 | { name: 'josh', age: 32 }, 80 | { name: 'lop', lang: 'java' }, 81 | { name: 'marko', age: 29 }, 82 | { name: 'peter', age: 35 }, 83 | { name: 'ripple', lang: 'java' }, 84 | { name: 'vadas', age: 27 } 85 | ]; 86 | assert.deepEqual(verts.sort(compareNameAge), expected.sort(compareNameAge)); 87 | done(); 88 | }); 89 | }); 90 | 91 | test('E(string key, object value)', function (done) { 92 | g.E('weight', java.newFloat(0.5)).next(function (err, e) { 93 | assert.ifError(err); 94 | e.getProperty('weight', function (err, weight) { 95 | assert.ifError(err); 96 | assert.strictEqual(weight, 0.5); 97 | done(); 98 | }); 99 | }); 100 | }); 101 | 102 | test('has(string key, object value)', function (done) { 103 | g.V().has('name', 'marko').next(function (err, v) { 104 | assert.ifError(err); 105 | v.getProperty('name', function (err, name) { 106 | assert.ifError(err); 107 | assert.strictEqual(name, 'marko'); 108 | done(); 109 | }); 110 | }); 111 | }); 112 | 113 | test('has(string key, token, object value)', function (done) { 114 | g.V().has('name', gremlin.Tokens.eq, 'marko').next(function (err, v) { 115 | assert.ifError(err); 116 | v.getProperty('name', function (err, name) { 117 | assert.ifError(err); 118 | assert.strictEqual(name, 'marko'); 119 | done(); 120 | }); 121 | }); 122 | }); 123 | 124 | test('has(string key, predicate, object value)', function (done) { 125 | g.V().has('name', gremlin.Compare.EQUAL, 'marko').next(function (err, v) { 126 | assert.ifError(err); 127 | v.getProperty('name', function (err, name) { 128 | assert.ifError(err); 129 | assert.strictEqual(name, 'marko'); 130 | done(); 131 | }); 132 | }); 133 | }); 134 | 135 | test('hasNot(string key, object value)', function (done) { 136 | g.V().hasNot('age').count(function (err, count) { 137 | assert.ifError(err); 138 | assert.strictEqual(count, 2); 139 | done(); 140 | }); 141 | }); 142 | 143 | test('hasNot(string key, object value)', function (done) { 144 | g.V().hasNot('age', 27).count(function (err, count) { 145 | assert.ifError(err); 146 | assert.strictEqual(count, 5); 147 | done(); 148 | }); 149 | }); 150 | 151 | test('interval(string key, object start, object end)', function (done) { 152 | var lower = 0.3; 153 | var upper = 0.9; 154 | 155 | var pipe = g.E().interval('weight', java.newFloat(lower), java.newFloat(upper)); 156 | pipe.toArray() 157 | .then(function (a) { 158 | assert(_.isArray(a)); 159 | assert.strictEqual(a.length, 3); 160 | var p = a.map(function (e) { return e.getProperty('weight'); }); 161 | Q.all(p) 162 | .then(function (weights) { 163 | weights.map(function (w) { 164 | assert(w >= lower && w <= upper); 165 | }); 166 | }) 167 | .done(done); 168 | }) 169 | .done(); 170 | }); 171 | 172 | test('bothE(string... labels)', function (done) { 173 | g.V().bothE('knows', 'created').toArray(function (err, edges) { 174 | assert.ifError(err); 175 | assert.strictEqual(edges.length, 12); 176 | var counts = _.countBy(edges, function (e) { return e.getLabel(); }); 177 | var expected = { created: 8, knows: 4 }; 178 | assert.deepEqual(counts, expected); 179 | done(); 180 | }); 181 | }); 182 | 183 | test('bothE(int branchFactor, string... labels)', function (done) { 184 | g.V().bothE(1, 'knows', 'created').toArray(function (err, edges) { 185 | assert.ifError(err); 186 | assert.strictEqual(edges.length, 6); 187 | var counts = _.countBy(edges, function (e) { return e.getLabel(); }); 188 | var expected = { created: 3, knows: 3 }; 189 | assert.deepEqual(counts, expected); 190 | done(); 191 | }); 192 | }); 193 | 194 | test('both(string... labels)', function (done) { 195 | g.V().both('knows').dedup().map().toArray(function (err, verts) { 196 | assert.ifError(err); 197 | assert.strictEqual(verts.length, 3); 198 | var expected = [ { age: 29, name: 'marko' }, { age: 27, name: 'vadas' }, { age: 32, name: 'josh' } ]; 199 | assert.deepEqual(verts.sort(compareNameAge), expected.sort(compareNameAge)); 200 | done(); 201 | }); 202 | }); 203 | 204 | test('both(int branchFactor, string... labels)', function (done) { 205 | g.V().both(1, 'knows').dedup().map().toArray(function (err, verts) { 206 | assert.ifError(err); 207 | assert.strictEqual(verts.length, 2); 208 | var expected = [ { age: 29, name: 'marko' }, { age: 27, name: 'vadas' } ]; 209 | assert.deepEqual(verts.sort(compareNameAge), expected.sort(compareNameAge)); 210 | done(); 211 | }); 212 | }); 213 | 214 | test('bothV()', function (done) { 215 | g.E('id', '7').bothV().map().toArray(function (err, verts) { 216 | assert.ifError(err); 217 | assert.strictEqual(verts.length, 2); 218 | var expected = [ { age: 29, name: 'marko' }, { age: 27, name: 'vadas' } ]; 219 | assert.deepEqual(verts.sort(compareNameAge), expected.sort(compareNameAge)); 220 | done(); 221 | }); 222 | }); 223 | 224 | test('inV()', function (done) { 225 | g.E('id', '7').inV().map().toArray(function (err, verts) { 226 | assert.ifError(err); 227 | assert.strictEqual(verts.length, 1); 228 | var expected = [ { age: 27, name: 'vadas' } ]; 229 | assert.deepEqual(verts.sort(compareNameAge), expected.sort(compareNameAge)); 230 | done(); 231 | }); 232 | }); 233 | 234 | test('inE()', function (done) { 235 | g.V('name', 'lop').inE().map().toArray(function (err, edges) { 236 | assert.ifError(err); 237 | assert.strictEqual(edges.length, 3); 238 | done(); 239 | }); 240 | }); 241 | 242 | // PipelineWrapper.prototype.in = function () { 243 | test('in()', function (done) { 244 | g.V('name', 'lop').in().map().toArray(function (err, edges) { 245 | assert.ifError(err); 246 | assert.strictEqual(edges.length, 3); 247 | done(); 248 | }); 249 | }); 250 | 251 | test('outV()', function (done) { 252 | g.E('id', '7').outV().map().toArray(function (err, verts) { 253 | assert.ifError(err); 254 | assert.strictEqual(verts.length, 1); 255 | var expected = [ { age: 29, name: 'marko' } ]; 256 | assert.deepEqual(verts.sort(compareNameAge), expected.sort(compareNameAge)); 257 | done(); 258 | }); 259 | }); 260 | 261 | test('outE()', function (done) { 262 | g.V('name', 'josh').outE().toArray(function (err, edges) { 263 | assert.ifError(err); 264 | assert.strictEqual(edges.length, 2); 265 | done(); 266 | }); 267 | }); 268 | 269 | test('out()', function (done) { 270 | g.V('name', 'josh').out().toArray(function (err, edges) { 271 | assert.ifError(err); 272 | assert.strictEqual(edges.length, 2); 273 | done(); 274 | }); 275 | }); 276 | 277 | test('id()', function (done) { 278 | g.V().id().toArray(function (err, ids) { 279 | assert.ifError(err); 280 | var expected = [ '1', '2', '3', '4', '5', '6' ]; 281 | assert.deepEqual(ids.sort(), expected); 282 | g.E().id().toArray(function (err, ids) { 283 | assert.ifError(err); 284 | var expected = [ '10', '11', '12', '7', '8', '9' ]; 285 | assert.deepEqual(ids.sort(), expected); 286 | done(); 287 | }); 288 | }); 289 | }); 290 | 291 | test('label()', function (done) { 292 | g.E().label().toArray(function (err, labels) { 293 | assert.ifError(err); 294 | var expected = [ 'created', 'knows', 'created', 'knows', 'created', 'created' ]; 295 | assert.deepEqual(labels.sort(), expected.sort()); 296 | done(); 297 | }); 298 | }); 299 | 300 | test('property()', function (done) { 301 | g.V().property('name').toArray(function (err, names) { 302 | assert.ifError(err); 303 | var expected = [ 'lop', 'vadas', 'marko', 'peter', 'ripple', 'josh' ]; 304 | assert.deepEqual(names.sort(), expected.sort()); 305 | g.V().property('age').toArray(function (err, ages) { 306 | assert.ifError(err); 307 | var expected = [ null, 27, 29, 35, null, 32 ]; 308 | assert.deepEqual(ages.sort(), expected.sort()); 309 | done(); 310 | }); 311 | }); 312 | }); 313 | 314 | // TODO 315 | // PipelineWrapper.prototype.idEdge = function () { 316 | // PipelineWrapper.prototype.id = function () { 317 | // PipelineWrapper.prototype.idVertex = function () { 318 | // PipelineWrapper.prototype.step = function () { 319 | test('copySplit(), _(), and fairMerge()', function (done) { 320 | g.V().both().toArray(function (err, bothed) { 321 | g.V().copySplit(g._().in(), g._().out()).fairMerge().toArray(function (err, copied) { 322 | assert.strictEqual(bothed.length, copied.length); 323 | done(); 324 | }); 325 | }); 326 | }); 327 | // PipelineWrapper.prototype.exhaustMerge = function () { 328 | // PipelineWrapper.prototype.fairMerge = function () { 329 | // PipelineWrapper.prototype.ifThenElse = function () { 330 | // PipelineWrapper.prototype.loop = function () { 331 | // PipelineWrapper.prototype.and = function (/*final Pipe... pipes*/) { 332 | test('as() and back()', function (done) { 333 | g.V().as('test').out('knows').back('test').toArray(function (err, recs) { 334 | assert.ifError(err); 335 | assert.strictEqual(recs.length, 1); 336 | done(); 337 | }); 338 | }); 339 | test('dedup()', function (done) { 340 | g.v(3, 3, function (err, verts) { 341 | verts.dedup().toArray(function (err, res) { 342 | assert.ifError(err); 343 | assert.strictEqual(res.length, 1); 344 | done(); 345 | }); 346 | }); 347 | }); 348 | // PipelineWrapper.prototype.except = function () { 349 | test('filter()', function (done) { 350 | this.timeout(5000); // A longer timeout is required on Travis 351 | g.V().filter('{ it -> it.name == "lop" }').map().toArray(function (err, recs) { 352 | assert.ifError(err); 353 | assert.strictEqual(recs.length, 1); 354 | var expected = [ { name: 'lop', lang: 'java' } ]; 355 | assert.deepEqual(recs, expected); 356 | done(); 357 | }); 358 | }); 359 | // PipelineWrapper.prototype.or = function (/*final Pipe... pipes*/) { 360 | // PipelineWrapper.prototype.random = function () { 361 | // PipelineWrapper.prototype.index = function (idx) { 362 | // PipelineWrapper.prototype.range = function () { 363 | // PipelineWrapper.prototype.retain = function (/*final Collection collection*/) { 364 | // PipelineWrapper.prototype.simplePath = function () { 365 | test('aggregate()', function (done) { 366 | var al = new gremlin.ArrayList(); 367 | g.V().has('lang', 'java').aggregate(al).next(function (err, v) { 368 | assert.ifError(err); 369 | assert.ok(v); 370 | assert.strictEqual(al.sizeSync(), 2); 371 | // al is an ArrayList 372 | done(); 373 | }); 374 | }); 375 | // PipelineWrapper.prototype.optional = function () { 376 | // PipelineWrapper.prototype.groupBy = function (map, closure) { 377 | test('groupCount(map, closure)', function (done) { 378 | var m = new gremlin.HashMap(); 379 | g.V().out().groupCount(m, '{ it -> it.id }').iterate(function (err, iterated) { 380 | assert.ifError(err); 381 | assert.strictEqual(iterated, null); 382 | assert.strictEqual(m.getSync('3').longValue, '3'); 383 | assert.strictEqual(m.getSync('2').longValue, '1'); 384 | assert.strictEqual(m.getSync('6'), null); 385 | done(); 386 | }); 387 | }); 388 | // PipelineWrapper.prototype.linkOut = function () { 389 | // PipelineWrapper.prototype.linkIn = function () { 390 | // PipelineWrapper.prototype.linkBoth = function () { 391 | // PipelineWrapper.prototype.sideEffect = function () { 392 | test('store()', function (done) { 393 | var al = new gremlin.ArrayList(); 394 | g.V().has('lang', 'java').store(al).next(function (err, v) { 395 | assert.ifError(err); 396 | assert.ok(v); 397 | assert.strictEqual(al.sizeSync(), 1); 398 | done(); 399 | }); 400 | }); 401 | // PipelineWrapper.prototype.table = function () { 402 | // PipelineWrapper.prototype.tree = function () { 403 | // PipelineWrapper.prototype.gather = function () { 404 | // PipelineWrapper.prototype._ = function () { 405 | // PipelineWrapper.prototype.memoize = function () { 406 | // PipelineWrapper.prototype.order = function () { 407 | // PipelineWrapper.prototype.path = function () { 408 | // PipelineWrapper.prototype.scatter = function () { 409 | // PipelineWrapper.prototype.select = function () { 410 | // PipelineWrapper.prototype.shuffle = function () { 411 | test('groupCount() and cap()', function (done) { 412 | g.V().in().id().groupCount().cap().next(function (err, map) { 413 | assert.ifError(err); 414 | assert(map['1'] === 3); 415 | assert(map['4'] === 2); 416 | assert(map['6'] === 1); 417 | done(); 418 | }); 419 | }); 420 | // PipelineWrapper.prototype.orderMap = function () { 421 | // PipelineWrapper.prototype.transform = function () { 422 | }); 423 | --------------------------------------------------------------------------------