diff --git a/lib/GraphDatabase._coffee b/lib/GraphDatabase._coffee index 8371854..0ace80a 100644 --- a/lib/GraphDatabase._coffee +++ b/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'] + + 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 + + # + 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, _) -> diff --git a/package.json b/package.json index dd776a1..e937b16 100644 --- a/package.json +++ b/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" }, "keywords": [ "neo4j", "graph", "database", "driver", "rest", "api", "client" diff --git a/test/gremlin._coffee b/test/gremlin._coffee new file mode 100644 index 0000000..261f907 --- /dev/null +++ b/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}) +""", {}, _ + +assert.ok typeof results, 'object' +assert.ok typeof results.data.name, 'string' # dislike this because it will throw for the wrong reasons possibly +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 + + +# 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' \ No newline at end of file diff --git a/test/index._coffee b/test/index._coffee index ca36a14..0807bc1 100644 --- a/test/index._coffee +++ b/test/index._coffee @@ -1,2 +1,3 @@ require './crud' require './cypher' +require './gremlin'