Skip to content

Gremlin Base Functionality #47

Merged
merged 7 commits into from Sep 21, 2012
View
49 lib/GraphDatabase._coffee
@@ -241,6 +241,45 @@ module.exports = class GraphDatabase
catch error
throw adjustError error
+ # wrapper around the Gremlin plugin to execute scripts 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...
+ execute: (script, params, _) ->
+ try
+ services = @getServices _
+ endpoint = services.gremlin or
+ services.extensions?.GremlinPlugin?['execute_script']
@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

There is no official gremlin endpoint baked in, so simplifying this to only check the plugin endpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ 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
+ results = util.transform response.body, this
+ return results
+
+ catch error
+ throw adjustError error
+
# XXX temporary backwards compatibility shim for query() argument order:
do (actual = @::query) =>
@::query = (query, params, callback) ->
@@ -259,6 +298,16 @@ module.exports = class GraphDatabase
actual.call @, query, params, callback
@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

Minor note: the overload helper for query() should remain just after the query() method itself, so I'll move this up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ #
+ 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
3 package.json
@@ -24,7 +24,8 @@
"clean": "rm lib/*.js",
"postinstall": "npm run build",
"profile": "_coffee test/profile",
- "test": "_coffee test"
+ "test": "_coffee test",
+ "gremlin": "_coffee test/gremlin"
@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

I assume this was for testing during development. =) I'll go ahead remove this, but I'd really like to port our tests to Mocha, which among other things will let you grep which test cases to run!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
},
"keywords": [
"neo4j", "graph", "database", "driver", "rest", "api", "client"
View
93 test/gremlin._coffee
@@ -0,0 +1,93 @@
+# 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
+results = db.execute """
+ g.v(#{user0.id})
+""", {}, _
@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

Given that you already test no params at the end, I'll change this to test params support since it doesn't seem like any other case tests that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+assert.ok typeof results, 'object'
+assert.ok typeof results.data.name, 'string' # dislike this because it will throw for the wrong reasons possibly
@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

You can use results.data?.name to guard against those. =)

@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

But in this case, you don't need to check the name's type since you're already checking its value (via strict equality from assert.equal).

@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

Oh also, watch out -- if you're checking typeof, you want to use assert.equal, not assert.ok. (Tricky because instanceof is the opposite.) I'd love to move to expect.js or similar when moving to Mocha.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+assert.equal results.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
+
+relationships = db.execute """
+ g.v(#{user0.id}).in('gremlin_follows')
+""", {} , _
+
+# above is working, but lib should support returning instances of Node and Relationship if possible
@aseemk
The Thingdom member
aseemk added a note Sep 21, 2012

It does now! ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+
+# 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.ok typeof traversals[0][0], 'object'
+assert.ok traversals[0][1] instanceof Array
+assert.ok typeof traversals[0][2], 'number'
+
+console.log 'Multiple data types appear to have worked with .execute() and util.transform()'
+
+# ensure you can call without params
+
+params_test = db.execute """
+ g.v(#{user0.id})
+""", _
+
+assert.ok typeof params_test, 'object'
+assert.equal params_test.data.name, user0.name
+
+# Should be relatively clear at this point the .execute() function is working with gremlin on some level
+console.log 'Passed initial Gremlin tests'
View
1 test/index._coffee
@@ -1,2 +1,3 @@
require './crud'
require './cypher'
+require './gremlin'
Something went wrong with that request. Please try again.