Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gremlin Base Functionality #47

Merged
merged 7 commits into from
Sep 21, 2012
Merged
49 changes: 49 additions & 0 deletions lib/GraphDatabase._coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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


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) ->
Expand All @@ -259,6 +298,16 @@ module.exports = class GraphDatabase

actual.call @, query, params, callback

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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


#
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, _) ->
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

},
"keywords": [
"neo4j", "graph", "database", "driver", "rest", "api", "client"
Expand Down
93 changes: 93 additions & 0 deletions test/gremlin._coffee
Original file line number Diff line number Diff line change
@@ -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})
""", {}, _
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


assert.ok typeof results, 'object'
assert.ok typeof results.data.name, 'string' # dislike this because it will throw for the wrong reasons possibly
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does now! ;)



# 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'
1 change: 1 addition & 0 deletions test/index._coffee
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
require './crud'
require './cypher'
require './gremlin'