Permalink
Browse files

Merge pull request #47: Gremlin support!

  • Loading branch information...
2 parents 043ffc3 + e505430 commit 51b213a90104564776dcf73cbe5eeda6e5379587 @aseemk aseemk committed Sep 21, 2012
Showing with 150 additions and 3 deletions.
  1. +49 −1 lib/GraphDatabase._coffee
  2. +2 −2 test/cypher._coffee
  3. +98 −0 test/gremlin._coffee
  4. +1 −0 test/index._coffee
@@ -241,7 +241,8 @@ module.exports = class GraphDatabase
catch error
throw adjustError error
- # XXX temporary backwards compatibility shim for query() argument order:
+ # XXX temporary backwards compatibility shim for query() argument order,
+ # and also to support overloaded method signature:
do (actual = @::query) =>
@::query = (query, params, callback) ->
if typeof query is 'function' and typeof params is 'string'
@@ -259,6 +260,53 @@ module.exports = class GraphDatabase
actual.call @, query, params, callback
+ # wrapper around the Gremlin plugin, which comes bundled with Neo4j.
+ # pass in the Gremlin script as a string, and optionally script
+ # parameters as a map -- recommended for both perf and security!
+ # http://docs.neo4j.org/chunked/snapshot/gremlin-plugin.html
+ # returns whatever your script returns, but any values that represent
+ # nodes, relationships or paths are transformed to instances.
+ execute: (script, params, _) ->
+ try
+ services = @getServices _
+ endpoint = services.extensions?.GremlinPlugin?['execute_script']
+
+ if not endpoint
+ throw new Error 'Gremlin plugin not installed'
+
+ response = @_request.post
+ uri: endpoint
+ json: if params then {script, params} else {script}
+ , _
+
+ # XXX workaround for neo4j silent failures for invalid queries:
+ if response.statusCode is status.NO_CONTENT
+ throw new Error """
+ Unknown Neo4j error for Gremlin script:
+
+ #{script}
+
+ """
+
+ if response.statusCode isnt status.OK
+ # Database error
+ throw response
+
+ # Success: transform nodes/relationships
+ return util.transform response.body, this
+
+ catch error
+ throw adjustError error
+
+ # helper for overloaded execute() method:
+ do (actual = @::execute) =>
+ @::execute = (script, params, callback) ->
+ if typeof params is 'function'
+ callback = params
+ params = null
+
+ actual.call @, script, params, callback
+
# executes a query against the given node index. lucene syntax reference:
# http://lucene.apache.org/java/3_1_0/queryparsersyntax.html
queryNodeIndex: (index, query, _) ->
View
@@ -68,8 +68,8 @@ createFollowRelationships = (i, _) ->
i2 = (i + 2) % users.length
i3 = (i + 3) % users.length
# create three relationships in parallel
- # WARNING: don't freaking use a variable called futures!
- # coffeescript's variable shadowing will FUCK. YOU. UP.
+ # WARNING: don't use a variable named futures here!
+ # coffeescript variable shadowing will kick in unexpectedly. =(
f1 = user.createRelationshipTo users[i1], 'follows', {}
f2 = user.createRelationshipTo users[i2], 'follows', {}
f3 = user.createRelationshipTo users[i3], 'follows', {}
View
@@ -0,0 +1,98 @@
+# will be used for testing gremlin script executions
+# as well as validating the return results are as expected
+
+assert = require('assert')
+neo4j = require('..')
+
+db = new neo4j.GraphDatabase 'http://localhost:7474'
+
+# convenience wrapper
+createNode = (name) ->
+ node = db.createNode {name}
+ node.name = name
+ node.toString = -> name
+ node
+
+# create some nodes
+users = for i in [0..6]
+ createNode "gremlinTest#{i}"
+
+# save in parallel
+futures = (user.save() for user in users)
+future _ for future in futures
+
+# convenience aliases
+user0 = users[0]
+user1 = users[1]
+user2 = users[2]
+user3 = users[3]
+user4 = users[4]
+user5 = users[5]
+user6 = users[6]
+
+# test: can query a single user, using param
+result = db.execute """
+ g.v(userId)
+""", {userId: user0.id}, _
+
+assert.ok result
+assert.equal typeof result, 'object'
+assert.equal typeof result.data, 'object'
+assert.equal result.data.name, user0.name
+
+# test: create relationships between users (same as cypher tests), then query by relationships
+createFollowRelationships = (i, _) ->
+ user = users[i]
+ i1 = (i + 1) % users.length
+ i2 = (i + 2) % users.length
+ i3 = (i + 3) % users.length
+ f1 = user.createRelationshipTo users[i1], 'gremlin_follows', {}
+ f2 = user.createRelationshipTo users[i2], 'gremlin_follows', {}
+ f3 = user.createRelationshipTo users[i3], 'gremlin_follows', {}
+ f1 _
+ f2 _
+ f3 _
+
+# create follow relationships for each user in parallel
+futures = (createFollowRelationships(i) for user, i in users)
+future _ for future in futures
+
+rels = db.execute """
+ g.v(#{user0.id}).in('gremlin_follows')
+""", {} , _
+
+assert.ok rels instanceof Array
+assert.equal rels.length, 3
+assert.ok rels[1]
+assert.equal typeof rels[1], 'object'
+# order isn't specified/guaranteed:
+assert.ok rels[1].id in [user4.id, user5.id, user6.id]
+
+# handle multiple types of data return
+traversals = db.execute """
+ g.v(#{user0.id}).transform{ [it, it.out.toList(), it.in.count()] }
+""", {}, _
+
+assert.ok traversals instanceof Array
+assert.equal traversals.length, 1
+
+assert.ok traversals[0] instanceof Array
+assert.equal traversals[0].length, 3
+
+assert.equal typeof traversals[0][0], 'object'
+assert.equal traversals[0][0].id, user0.id
+assert.ok traversals[0][1] instanceof Array
+assert.equal traversals[0][1].length, 3
+assert.ok traversals[0][1][1].id in [user1.id, user2.id, user3.id]
+assert.equal traversals[0][2], 3
+
+# ensure you can call without params
+params_test = db.execute """
+ g.v(#{user0.id})
+""", _
+
+assert.equal typeof params_test, 'object'
+assert.equal params_test.data.name, user0.name
+
+# give some confidence that these tests actually passed ;)
+console.log 'passed gremlin tests'
View
@@ -1,2 +1,3 @@
require './crud'
require './cypher'
+require './gremlin'

0 comments on commit 51b213a

Please sign in to comment.