From a370680fe832531c29135d2ccbd2c2dbec80ebdf Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Tue, 10 Dec 2013 09:26:50 -0500 Subject: [PATCH 01/57] initial write --- index.js => lib/adapter.js | 13 +++++++------ lib/connection.js | 33 +++++++++++++++++++++++++++++++++ package.json | 18 +++++++++++------- test/register.js | 2 +- 4 files changed, 52 insertions(+), 14 deletions(-) rename index.js => lib/adapter.js (97%) create mode 100644 lib/connection.js diff --git a/index.js b/lib/adapter.js similarity index 97% rename from index.js rename to lib/adapter.js index 32ed3d4..f39e431 100644 --- a/index.js +++ b/lib/adapter.js @@ -3,7 +3,8 @@ -> adapter ---------------------------------------------------------------*/ -var async = require('async'); +var async = require('async'), + neo4j = require('neo4j-js'); var adapter = module.exports = { @@ -29,10 +30,10 @@ var adapter = module.exports = { // Default configuration for collections // (same effect as if these properties were included at the top level of the model definitions) defaults: { - - // For example: - // port: 3306, - // host: 'localhost' + // change these to fit your setup + port: 7474, + host: 'localhost', + base: '/db/data/' // If setting syncable, you should consider the migrate option, // which allows you to set how the sync will be performed. @@ -41,7 +42,7 @@ var adapter = module.exports = { // drop => Drop schema and data, then recreate it // alter => Drop/add columns as necessary, but try // safe => Don't change anything (good for production DBs) - migrate: 'alter' + // migrate: 'alter' }, // This method runs when a model is initially registered at server start time diff --git a/lib/connection.js b/lib/connection.js new file mode 100644 index 0000000..1c31946 --- /dev/null +++ b/lib/connection.js @@ -0,0 +1,33 @@ +/*global neoAdapter */ + +var neo4j = require('neo4j-js'). + neoAdapter = require('./adapter.js'); + + +module.exports = (function() { + var activeConnection = false; // if an active connection exists, use it instead of tearing the previous one down + + function connect(cb) { + var path = neoAdapter.defaults.host + ':' + neoAdapter.defaults.port + neoAdapter.defaults.base; + if (!activeConnection) { + neo4j.connect(path, function(err, graph) { + if (err) { + cb(err, null); + } + else { + activeConnection = graph; + return cb(false, activeConnection); + } + }); + } + else { + return cb(false, activeConnection); + } + } + + // API + return { + connect: connect, + + }; +})(); \ No newline at end of file diff --git a/package.json b/package.json index a9f0670..406c076 100755 --- a/package.json +++ b/package.json @@ -1,26 +1,30 @@ { - "name": "sails-adapter-boilerplate", + "name": "sails-neo4j", "version": "0.0.1", - "description": "Boilerplate adapter for Sails.js", - "main": "BoilerplateAdapter.js", + "description": "Neo4j adapter for Sails.js", + "main": "lib/adapter.js", "scripts": { "test": "echo \"Adapter should be tested using Sails.js core.\" && exit 1" }, "repository": { "type": "git", - "url": "https://github.com/balderdashy/sails-adapter-boilerplate.git" + "url": "https://github.com/natgeo/sails-neo4j.git" }, "keywords": [ "orm", "waterline", "sails", "sailsjs", - "sails.js" + "sails.js", + "neo4j", + "graph", + "database" ], - "author": "Your name here", + "author": "National Geographic Society", "license": "MIT", "readmeFilename": "README.md", "dependencies": { - "async": "0.1.22" + "async": "0.1.22", + "neo4j-js": "0.0.7" } } diff --git a/test/register.js b/test/register.js index b4fcbb7..f1f9081 100644 --- a/test/register.js +++ b/test/register.js @@ -1,7 +1,7 @@ describe('registerCollection', function () { it('should not hang or encounter any errors', function (cb) { - var adapter = require('../index.js'); + var adapter = require('../lib/adapter.js'); adapter.registerCollection({ identity: 'foo' }, cb); From fd96c782469ab81722607102287454dccbf01526 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 11 Dec 2013 00:16:29 -0500 Subject: [PATCH 02/57] just an adapter i am i am sam he said the horse --- lib/adapter.js | 433 +++++++++++++++++++++++++++------------------- lib/connection.js | 40 +++-- lib/test.js | 12 ++ package.json | 3 +- 4 files changed, 290 insertions(+), 198 deletions(-) create mode 100644 lib/test.js diff --git a/lib/adapter.js b/lib/adapter.js index f39e431..f9318a8 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -4,14 +4,14 @@ ---------------------------------------------------------------*/ var async = require('async'), - neo4j = require('neo4j-js'); + neo = require('./connection'); -var adapter = module.exports = { +var adapter = module.exports = (function() { // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. // Not terribly relevant if not using a non-SQL / non-schema-ed data store - syncable: false, + var syncable = false, // Including a commitLog config enables transactions in this adapter // Please note that these are not ACID-compliant transactions: @@ -29,196 +29,271 @@ var adapter = module.exports = { // Default configuration for collections // (same effect as if these properties were included at the top level of the model definitions) - defaults: { - // change these to fit your setup - port: 7474, - host: 'localhost', - base: '/db/data/' - - // If setting syncable, you should consider the migrate option, - // which allows you to set how the sync will be performed. - // It can be overridden globally in an app (config/adapters.js) and on a per-model basis. - // - // drop => Drop schema and data, then recreate it - // alter => Drop/add columns as necessary, but try - // safe => Don't change anything (good for production DBs) - // migrate: 'alter' - }, - - // This method runs when a model is initially registered at server start time - registerCollection: function(collection, cb) { - - cb(); - }, - - - // The following methods are optional - //////////////////////////////////////////////////////////// - - // Optional hook fired when a model is unregistered, typically at server halt - // useful for tearing down remaining open connections, etc. - teardown: function(cb) { - cb(); - }, - - - // REQUIRED method if integrating with a schemaful database - define: function(collectionName, definition, cb) { - - // Define a new "table" or "collection" schema in the data store - cb(); - }, - // REQUIRED method if integrating with a schemaful database - describe: function(collectionName, cb) { - - // Respond with the schema (attributes) for a collection or table in the data store - var attributes = {}; - cb(null, attributes); - }, - // REQUIRED method if integrating with a schemaful database - drop: function(collectionName, cb) { - // Drop a "table" or "collection" schema from the data store - cb(); - }, - - // Optional override of built-in alter logic - // Can be simulated with describe(), define(), and drop(), - // but will probably be made much more efficient by an override here - // alter: function (collectionName, attributes, cb) { - // Modify the schema of a table or collection in the data store - // cb(); - // }, - - - // REQUIRED method if users expect to call Model.create() or any methods - create: function(collectionName, values, cb) { - // Create a single new model specified by values - - // Respond with error or newly created model instance - cb(null, values); - }, - - // REQUIRED method if users expect to call Model.find(), Model.findAll() or related methods - // You're actually supporting find(), findAll(), and other methods here - // but the core will take care of supporting all the different usages. - // (e.g. if this is a find(), not a findAll(), it will only send back a single model) - find: function(collectionName, options, cb) { - - // ** Filter by criteria in options to generate result set - - // Respond with an error or a *list* of models in result set - cb(null, []); - }, - - // REQUIRED method if users expect to call Model.update() - update: function(collectionName, options, values, cb) { - - // ** Filter by criteria in options to generate result set - - // Then update all model(s) in the result set - - // Respond with error or a *list* of models that were updated - cb(); - }, - - // REQUIRED method if users expect to call Model.destroy() - destroy: function(collectionName, options, cb) { - - // ** Filter by criteria in options to generate result set - - // Destroy all model(s) in the result set - - // Return an error or nothing at all - cb(); - }, - - - - // REQUIRED method if users expect to call Model.stream() - stream: function(collectionName, options, stream) { - // options is a standard criteria/options object (like in find) - - // stream.write() and stream.end() should be called. - // for an example, check out: - // https://github.com/balderdashy/sails-dirty/blob/master/DirtyAdapter.js#L247 - + defaults = { + // change these to fit your setup + protocol: 'http://', + port: 7474, + host: 'localhost', + base: '/db/data/' + + // If setting syncable, you should consider the migrate option, + // which allows you to set how the sync will be performed. + // It can be overridden globally in an app (config/adapters.js) and on a per-model basis. + // + // drop => Drop schema and data, then recreate it + // alter => Drop/add columns as necessary, but try + // safe => Don't change anything (good for production DBs) + // migrate: 'alter' + }; + //Init + + neo.connect(defaults); + + function parseOne(values) { + var i, names = [], name; + for (i in values) { + if (values.hasOwnProperty(i)) { + name = i + ': {' + i + '}'; + names.push(name); + } + } + return names.join(','); } + function parseMany(values) { + var i, names = [], name; + for (i in values) { + if (values.hasOwnProperty(i)) { + if (Object.prototype.toString.call(values[i]) === '[object Array]') { + name = i; + } + else { + return false; + } + names.push(name); + } + } + return names.join(','); + } - - /* - ********************************************** - * Optional overrides - ********************************************** - - // Optional override of built-in batch create logic for increased efficiency - // otherwise, uses create() - createEach: function (collectionName, cb) { cb(); }, - - // Optional override of built-in findOrCreate logic for increased efficiency - // otherwise, uses find() and create() - findOrCreate: function (collectionName, cb) { cb(); }, - - // Optional override of built-in batch findOrCreate logic for increased efficiency - // otherwise, uses findOrCreate() - findOrCreateEach: function (collectionName, cb) { cb(); } - */ + return { + syncable: syncable, + defaults: defaults, + + // This method runs when a model is initially registered at server start time + // registerCollection: function(collection, cb) { + // // neo4j is schemaless, and therefore there is no 'model registeration' + // console.log(collection + ' Model registered'); + // cb(); + // }, + + + // The following methods are optional + //////////////////////////////////////////////////////////// + + // Optional hook fired when a model is unregistered, typically at server halt + // useful for tearing down remaining open connections, etc. + // teardown: function(cb) { + // cb(); + // }, + + + // Irrelevant for Neo4j + + // // REQUIRED method if integrating with a schemaful database + // define: function(collectionName, definition, cb) { + + // // Define a new "table" or "collection" schema in the data store + // cb(); + // }, + // // REQUIRED method if integrating with a schemaful database + // describe: function(collectionName, cb) { + + // // Respond with the schema (attributes) for a collection or table in the data store + // var attributes = {}; + // cb(null, attributes); + // }, + // // REQUIRED method if integrating with a schemaful database + // drop: function(collectionName, cb) { + // // Drop a "table" or "collection" schema from the data store + // cb(); + // }, + + // Optional override of built-in alter logic + // Can be simulated with describe(), define(), and drop(), + // but will probably be made much more efficient by an override here + // alter: function (collectionName, attributes, cb) { + // Modify the schema of a table or collection in the data store + // cb(); + // }, + + + // REQUIRED method if users expect to call Model.create() or any methods + create: function(collectionName, values, cb) { + // Create a single new model specified by values + var query = [ + 'CREATE (n:' + collectionName + ' { ' + parseOne(values) + ' })', + 'RETURN n' + ]; + + neo.graph(function(gr) { + gr.query(query.join('\n'), values, function (err, results) { + // Respond with error or newly created model instance + if (err) { + console.log(err); + console.log(err.stack); + cb(err, null); + } + else { + console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure + + cb(null, results); + } + }); + }); + }, + + createMany: function(collectionName, map, cb) { + // Create a single new model specified by values + var query = [ + 'CREATE (n:' + collectionName + ' { ' + parseMany(map) + ' })', + 'RETURN n' + ]; + + neo.graph(function(gr) { + gr.query(query.join('\n'), map, function (err, results) { + // Respond with error or newly created model instance + if (err) { + console.log(err); + console.log(err.stack); + cb(err, null); + } + else { + console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure + + cb(null, results); + } + }); + }); + }, + + // REQUIRED method if users expect to call Model.find(), Model.findAll() or related methods + // You're actually supporting find(), findAll(), and other methods here + // but the core will take care of supporting all the different usages. + // (e.g. if this is a find(), not a findAll(), it will only send back a single model) + find: function(collectionName, options, cb) { + + // ** Filter by criteria in options to generate result set + + // Respond with an error or a *list* of models in result set + cb(null, []); + }, + + // REQUIRED method if users expect to call Model.update() + update: function(collectionName, options, values, cb) { + + // ** Filter by criteria in options to generate result set + + // Then update all model(s) in the result set + + // Respond with error or a *list* of models that were updated + cb(); + }, + + // REQUIRED method if users expect to call Model.destroy() + destroy: function(collectionName, options, cb) { + + // ** Filter by criteria in options to generate result set + + // Destroy all model(s) in the result set + + // Return an error or nothing at all + cb(); + }, + + + + // REQUIRED method if users expect to call Model.stream() + stream: function(collectionName, options, stream) { + // options is a standard criteria/options object (like in find) + + // stream.write() and stream.end() should be called. + // for an example, check out: + // https://github.com/balderdashy/sails-dirty/blob/master/DirtyAdapter.js#L247 + + } + + + + /* + ********************************************** + * Optional overrides + ********************************************** + + // Optional override of built-in batch create logic for increased efficiency + // otherwise, uses create() + createEach: function (collectionName, cb) { cb(); }, + + // Optional override of built-in findOrCreate logic for increased efficiency + // otherwise, uses find() and create() + findOrCreate: function (collectionName, cb) { cb(); }, + + // Optional override of built-in batch findOrCreate logic for increased efficiency + // otherwise, uses findOrCreate() + findOrCreateEach: function (collectionName, cb) { cb(); } + */ - /* - ********************************************** - * Custom methods - ********************************************** + /* + ********************************************** + * Custom methods + ********************************************** - //////////////////////////////////////////////////////////////////////////////////////////////////// - // - // > NOTE: There are a few gotchas here you should be aware of. - // - // + The collectionName argument is always prepended as the first argument. - // This is so you can know which model is requesting the adapter. - // - // + All adapter functions are asynchronous, even the completely custom ones, - // and they must always include a callback as the final argument. - // The first argument of callbacks is always an error object. - // For some core methods, Sails.js will add support for .done()/promise usage. - // - // + - // - //////////////////////////////////////////////////////////////////////////////////////////////////// - - - // Any other methods you include will be available on your models - foo: function (collectionName, cb) { - cb(null,"ok"); - }, - bar: function (collectionName, baz, watson, cb) { - cb("Failure!"); - } + //////////////////////////////////////////////////////////////////////////////////////////////////// + // + // > NOTE: There are a few gotchas here you should be aware of. + // + // + The collectionName argument is always prepended as the first argument. + // This is so you can know which model is requesting the adapter. + // + // + All adapter functions are asynchronous, even the completely custom ones, + // and they must always include a callback as the final argument. + // The first argument of callbacks is always an error object. + // For some core methods, Sails.js will add support for .done()/promise usage. + // + // + + // + //////////////////////////////////////////////////////////////////////////////////////////////////// - // Example success usage: + // Any other methods you include will be available on your models + foo: function (collectionName, cb) { + cb(null,"ok"); + }, + bar: function (collectionName, baz, watson, cb) { + cb("Failure!"); + } - Model.foo(function (err, result) { - if (err) console.error(err); - else console.log(result); - // outputs: ok - }) + // Example success usage: - // Example error usage: + Model.foo(function (err, result) { + if (err) console.error(err); + else console.log(result); - Model.bar(235, {test: 'yes'}, function (err, result){ - if (err) console.error(err); - else console.log(result); + // outputs: ok + }) - // outputs: Failure! - }) + // Example error usage: - */ + Model.bar(235, {test: 'yes'}, function (err, result){ + if (err) console.error(err); + else console.log(result); + // outputs: Failure! + }) -}; + */ + }; -////////////// ////////////////////////////////////////// -////////////// Private Methods ////////////////////////////////////////// -////////////// ////////////////////////////////////////// \ No newline at end of file +})(); \ No newline at end of file diff --git a/lib/connection.js b/lib/connection.js index 1c31946..e16ab2d 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -1,33 +1,37 @@ -/*global neoAdapter */ - -var neo4j = require('neo4j-js'). - neoAdapter = require('./adapter.js'); - +var neo4j = require('neo4j-js'), + Q = require('q'); module.exports = (function() { - var activeConnection = false; // if an active connection exists, use it instead of tearing the previous one down + var graph = false, d = Q.defer(); // if an active connection exists, use it instead of tearing the previous one down - function connect(cb) { - var path = neoAdapter.defaults.host + ':' + neoAdapter.defaults.port + neoAdapter.defaults.base; - if (!activeConnection) { - neo4j.connect(path, function(err, graph) { + function connect(connection) { + if (!graph) { + var path = connection.protocol + connection.host + ':' + connection.port + connection.base; + console.log('Connecting to Neo4j@' + path); + neo4j.connect(path, function(err, graphObj) { if (err) { - cb(err, null); - } - else { - activeConnection = graph; - return cb(false, activeConnection); + console.log('An error has occured when trying to connect to Neo4j:'); + d.reject(err); + throw err; } + console.log('Connected@Neo4j'); + graph = graphObj; + d.resolve(graph); + return d.promise; }); } else { - return cb(false, activeConnection); + return d.promise; } } - // API + function graphDo(cb) { + d.promise.then(cb); + } + + // built in this pattern so this can be enhanced later on return { connect: connect, - + graph: graphDo }; })(); \ No newline at end of file diff --git a/lib/test.js b/lib/test.js new file mode 100644 index 0000000..050691d --- /dev/null +++ b/lib/test.js @@ -0,0 +1,12 @@ +var x = require('./adapter'); +x.createMany('User', + { + params: [ + { name: 'ben' }, + { name: 'joe' } + ] + }, + function(err, results) { + console.log(err, results); + } +); \ No newline at end of file diff --git a/package.json b/package.json index 406c076..eba3f2a 100755 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "0.1.22", - "neo4j-js": "0.0.7" + "neo4j-js": "0.0.7", + "q": "~0.9.7" } } From 6e053c813a9bbe6faedcfbd936b45af611505cc8 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 11 Dec 2013 14:55:28 -0500 Subject: [PATCH 03/57] updated adapter with find query --- lib/adapter.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index f9318a8..4a8562a 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -76,6 +76,17 @@ var adapter = module.exports = (function() { return names.join(','); } + function toWhere(object, properties) { + var i, query = [], q; + for (i in properties) { + if (properties.hasOwnProperty(i)) { + q = object + '.' + i + '=' + '{' + i + '}'; + query.push(q); + } + } + return '(' + query.join(' AND ') + ')'; + } + return { syncable: syncable, defaults: defaults, @@ -182,11 +193,28 @@ var adapter = module.exports = (function() { // but the core will take care of supporting all the different usages. // (e.g. if this is a find(), not a findAll(), it will only send back a single model) find: function(collectionName, options, cb) { - // ** Filter by criteria in options to generate result set + var query = [ + 'MATCH (n)', + 'WHERE n:' + collectionName + ' AND ' + toWhere('n', options), + 'RETURN n' + ]; - // Respond with an error or a *list* of models in result set - cb(null, []); + neo.graph(function(gr) { + gr.query(query.join('\n'), options, function (err, results) { + // Respond with error or newly created model instance + if (err) { + console.log(err); + console.log(err.stack); + cb(err, null); + } + else { + console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure + + cb(null, results); + } + }); + }); }, // REQUIRED method if users expect to call Model.update() From 0066c42a5a42d5a26bf90d3015c6544c1e6a695e Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 11 Dec 2013 17:01:40 -0500 Subject: [PATCH 04/57] and now! drum roll please! adapter with tests --- lib/adapter.js | 114 +++++++++++++++++++++++----------------------- lib/connection.js | 11 ++--- lib/test.js | 12 ----- package.json | 5 +- test/create.js | 22 +++++++++ test/init.js | 12 +++++ test/register.js | 14 ------ 7 files changed, 99 insertions(+), 91 deletions(-) delete mode 100644 lib/test.js create mode 100644 test/create.js create mode 100644 test/init.js delete mode 100644 test/register.js diff --git a/lib/adapter.js b/lib/adapter.js index 4a8562a..8a8591b 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -1,5 +1,5 @@ /*--------------------------------------------------------------- - :: sails-boilerplate + :: sails-neo4j -> adapter ---------------------------------------------------------------*/ @@ -12,6 +12,7 @@ var adapter = module.exports = (function() { // If true, the schema for models using this adapter will be automatically synced when the server starts. // Not terribly relevant if not using a non-SQL / non-schema-ed data store var syncable = false, + connection, // Including a commitLog config enables transactions in this adapter // Please note that these are not ACID-compliant transactions: @@ -47,7 +48,7 @@ var adapter = module.exports = (function() { }; //Init - neo.connect(defaults); + connection = neo.connect(defaults); function parseOne(values) { var i, names = [], name; @@ -87,9 +88,32 @@ var adapter = module.exports = (function() { return '(' + query.join(' AND ') + ')'; } + function neoQuery(query, params, cb) { + neo.graph(function(gr) { + gr.query(query.join('\n'), params, function (err, results) { + // Respond with error or newly created model instance + if (err) { + // console.log(err); + // console.log(err.stack); + cb(err, null); + } + else { + // console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure + + cb(null, results); + } + }); + }); + } + + function getConnection() { + return neo.connect(defaults); + } + return { syncable: syncable, defaults: defaults, + getConnection: getConnection, // This method runs when a model is initially registered at server start time // registerCollection: function(collection, cb) { @@ -140,81 +164,59 @@ var adapter = module.exports = (function() { // REQUIRED method if users expect to call Model.create() or any methods - create: function(collectionName, values, cb) { + create: function(collectionName, params, cb) { // Create a single new model specified by values - var query = [ - 'CREATE (n:' + collectionName + ' { ' + parseOne(values) + ' })', + var query, delimiter = ''; + + if (collectionName === null) { collectionName = ''; } // do we have a label? + else { collectionName = ':' + collectionName; } + + if (params !== null && collectionName !== null) { delimiter = ' AND '; } // do we have a label and params? + + query = [ + 'CREATE (n' + collectionName + ' { ' + parseOne(params) + ' })', 'RETURN n' ]; - neo.graph(function(gr) { - gr.query(query.join('\n'), values, function (err, results) { - // Respond with error or newly created model instance - if (err) { - console.log(err); - console.log(err.stack); - cb(err, null); - } - else { - console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure - - cb(null, results); - } - }); - }); + neoQuery(query, params, cb); }, - createMany: function(collectionName, map, cb) { + createMany: function(collectionName, params, cb) { // Create a single new model specified by values - var query = [ - 'CREATE (n:' + collectionName + ' { ' + parseMany(map) + ' })', + var query, delimiter = ''; + + if (collectionName === null) { collectionName = ''; } // do we have a label? + else { collectionName = ':' + collectionName; } + + if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? + + query = [ + 'CREATE (n' + collectionName + ' { ' + parseMany(params) + ' })', 'RETURN n' ]; - neo.graph(function(gr) { - gr.query(query.join('\n'), map, function (err, results) { - // Respond with error or newly created model instance - if (err) { - console.log(err); - console.log(err.stack); - cb(err, null); - } - else { - console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure - - cb(null, results); - } - }); - }); + neoQuery(query, params, cb); }, // REQUIRED method if users expect to call Model.find(), Model.findAll() or related methods // You're actually supporting find(), findAll(), and other methods here // but the core will take care of supporting all the different usages. // (e.g. if this is a find(), not a findAll(), it will only send back a single model) - find: function(collectionName, options, cb) { + find: function(collectionName, params, cb) { // ** Filter by criteria in options to generate result set - var query = [ + var query, delimiter = ''; + + if (collectionName === null) { collectionName = ''; } // do we have a label? + else { collectionName = 'n:' + collectionName; } + + if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? + query = [ 'MATCH (n)', - 'WHERE n:' + collectionName + ' AND ' + toWhere('n', options), + 'WHERE ' + collectionName + delimiter + toWhere('n', params), 'RETURN n' ]; - neo.graph(function(gr) { - gr.query(query.join('\n'), options, function (err, results) { - // Respond with error or newly created model instance - if (err) { - console.log(err); - console.log(err.stack); - cb(err, null); - } - else { - console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure - - cb(null, results); - } - }); - }); + neoQuery(query, params, cb); }, // REQUIRED method if users expect to call Model.update() diff --git a/lib/connection.js b/lib/connection.js index e16ab2d..3a6e44e 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -7,22 +7,17 @@ module.exports = (function() { function connect(connection) { if (!graph) { var path = connection.protocol + connection.host + ':' + connection.port + connection.base; - console.log('Connecting to Neo4j@' + path); - neo4j.connect(path, function(err, graphObj) { + graph = true; + neo4j.connect(path, function(err, graph) { if (err) { console.log('An error has occured when trying to connect to Neo4j:'); d.reject(err); throw err; } - console.log('Connected@Neo4j'); - graph = graphObj; d.resolve(graph); - return d.promise; }); } - else { - return d.promise; - } + return d.promise; } function graphDo(cb) { diff --git a/lib/test.js b/lib/test.js deleted file mode 100644 index 050691d..0000000 --- a/lib/test.js +++ /dev/null @@ -1,12 +0,0 @@ -var x = require('./adapter'); -x.createMany('User', - { - params: [ - { name: 'ben' }, - { name: 'joe' } - ] - }, - function(err, results) { - console.log(err, results); - } -); \ No newline at end of file diff --git a/package.json b/package.json index eba3f2a..a32068e 100755 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { - "test": "echo \"Adapter should be tested using Sails.js core.\" && exit 1" + "test": "mocha --reporter spec" }, "repository": { "type": "git", @@ -27,5 +27,8 @@ "async": "0.1.22", "neo4j-js": "0.0.7", "q": "~0.9.7" + }, + "devDependencies": { + "mocha": "~1.15.1" } } diff --git a/test/create.js b/test/create.js new file mode 100644 index 0000000..90314e4 --- /dev/null +++ b/test/create.js @@ -0,0 +1,22 @@ +var assert = require('assert'); + +describe('Creating Nodes', function () { + var nodes = []; + it('should create one node with a property test = "1"', function (done) { + var adapter = require('../lib/adapter.js'); + adapter.create(null, { test: 1 }, function(err, results) { + if (err) { throw err; } + nodes.push(results); + done(); + }); + }); + + it('should create multiple nodes with the property test = "1"', function(done) { + var adapter = require('../lib/adapter.js'); + adapter.createMany(null,{params:[{ test: 1 },{ test: 1 }]}, function(err, results) { + if (err) { throw err; } + nodes.push(results); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test/init.js b/test/init.js new file mode 100644 index 0000000..ce0aa9b --- /dev/null +++ b/test/init.js @@ -0,0 +1,12 @@ +var assert = require('assert'); + +describe('init', function () { + + it('should succeed when a valid connection is created', function (done) { + var adapter = require('../lib/adapter.js'); + adapter.getConnection().then(function() { + assert.equal(adapter.getConnection().inspect().state, 'fulfilled'); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test/register.js b/test/register.js deleted file mode 100644 index f1f9081..0000000 --- a/test/register.js +++ /dev/null @@ -1,14 +0,0 @@ -describe('registerCollection', function () { - - it('should not hang or encounter any errors', function (cb) { - var adapter = require('../lib/adapter.js'); - adapter.registerCollection({ - identity: 'foo' - }, cb); - }); - - // e.g. - // it('should create a mysql connection pool', function () {}) - // it('should create an HTTP connection pool', function () {}) - // ... and so on. -}); \ No newline at end of file From e11220b9f8a7a1001fb145084e838b0b59379044 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Thu, 12 Dec 2013 09:54:11 -0500 Subject: [PATCH 05/57] added query to adapter --- lib/adapter.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 8a8591b..e001072 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -88,9 +88,9 @@ var adapter = module.exports = (function() { return '(' + query.join(' AND ') + ')'; } - function neoQuery(query, params, cb) { + function query(q, params, cb) { neo.graph(function(gr) { - gr.query(query.join('\n'), params, function (err, results) { + gr.query(q.join('\n'), params, function (err, results) { // Respond with error or newly created model instance if (err) { // console.log(err); @@ -114,6 +114,7 @@ var adapter = module.exports = (function() { syncable: syncable, defaults: defaults, getConnection: getConnection, + query: query, // This method runs when a model is initially registered at server start time // registerCollection: function(collection, cb) { @@ -178,7 +179,7 @@ var adapter = module.exports = (function() { 'RETURN n' ]; - neoQuery(query, params, cb); + query(query, params, cb); }, createMany: function(collectionName, params, cb) { @@ -195,7 +196,7 @@ var adapter = module.exports = (function() { 'RETURN n' ]; - neoQuery(query, params, cb); + query(query, params, cb); }, // REQUIRED method if users expect to call Model.find(), Model.findAll() or related methods @@ -216,7 +217,7 @@ var adapter = module.exports = (function() { 'RETURN n' ]; - neoQuery(query, params, cb); + query(query, params, cb); }, // REQUIRED method if users expect to call Model.update() From d7ffa0c06da0a4ce5213d9555f1ee3f0a288f49d Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 27 Dec 2013 16:11:02 -0500 Subject: [PATCH 06/57] added a small query debug option --- lib/adapter.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index e001072..8a22915 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -35,7 +35,8 @@ var adapter = module.exports = (function() { protocol: 'http://', port: 7474, host: 'localhost', - base: '/db/data/' + base: '/db/data/', + debug: true // If setting syncable, you should consider the migrate option, // which allows you to set how the sync will be performed. @@ -46,8 +47,9 @@ var adapter = module.exports = (function() { // safe => Don't change anything (good for production DBs) // migrate: 'alter' }; + //Init - + connection = neo.connect(defaults); function parseOne(values) { @@ -90,6 +92,9 @@ var adapter = module.exports = (function() { function query(q, params, cb) { neo.graph(function(gr) { + if (defaults.debug) { + console.log('q: ', q.join('\n'), 'p: ', params); // spit out the queries for debug + } gr.query(q.join('\n'), params, function (err, results) { // Respond with error or newly created model instance if (err) { @@ -167,36 +172,36 @@ var adapter = module.exports = (function() { // REQUIRED method if users expect to call Model.create() or any methods create: function(collectionName, params, cb) { // Create a single new model specified by values - var query, delimiter = ''; + var q, delimiter = ''; if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = ':' + collectionName; } if (params !== null && collectionName !== null) { delimiter = ' AND '; } // do we have a label and params? - query = [ + q = [ 'CREATE (n' + collectionName + ' { ' + parseOne(params) + ' })', 'RETURN n' ]; - query(query, params, cb); + query(q, params, cb); }, createMany: function(collectionName, params, cb) { // Create a single new model specified by values - var query, delimiter = ''; + var q, delimiter = ''; if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = ':' + collectionName; } if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? - query = [ + q = [ 'CREATE (n' + collectionName + ' { ' + parseMany(params) + ' })', 'RETURN n' ]; - query(query, params, cb); + query(q, params, cb); }, // REQUIRED method if users expect to call Model.find(), Model.findAll() or related methods @@ -205,19 +210,19 @@ var adapter = module.exports = (function() { // (e.g. if this is a find(), not a findAll(), it will only send back a single model) find: function(collectionName, params, cb) { // ** Filter by criteria in options to generate result set - var query, delimiter = ''; + var q, delimiter = ''; if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = 'n:' + collectionName; } if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? - query = [ + q = [ 'MATCH (n)', 'WHERE ' + collectionName + delimiter + toWhere('n', params), 'RETURN n' ]; - query(query, params, cb); + query(q, params, cb); }, // REQUIRED method if users expect to call Model.update() From 783bda384d25789c7c7bd3f5e64762e33f0a1ba6 Mon Sep 17 00:00:00 2001 From: Farhan Syed Date: Fri, 7 Feb 2014 17:13:02 -0500 Subject: [PATCH 07/57] added cypher injection --- lib/helpers/isCypherInjectionFree.js | 28 ++++++++++++++++++++++++++++ test/sanitized.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 lib/helpers/isCypherInjectionFree.js create mode 100644 test/sanitized.js diff --git a/lib/helpers/isCypherInjectionFree.js b/lib/helpers/isCypherInjectionFree.js new file mode 100644 index 0000000..f88a192 --- /dev/null +++ b/lib/helpers/isCypherInjectionFree.js @@ -0,0 +1,28 @@ + +module.exports = function (string) { + var pathRegex = /[-!$%^&*()+|~=`{}\[\]:";'<>?,.\/]/; + cypherKeywords = [ + 'match', + 'merge', + 'start', + 'where', + 'create', + 'set', + 'delete', + 'remove', + 'foreach' + ]; + + function containsKeywords (element, index, array) { + o = string.indexOf(element); + if (o === -1) { + return false + } + return true + } + + if (pathRegex.test(string) === false && cypherKeywords.some(containsKeywords) === false) { + return true + } + return false +} diff --git a/test/sanitized.js b/test/sanitized.js new file mode 100644 index 0000000..254be02 --- /dev/null +++ b/test/sanitized.js @@ -0,0 +1,28 @@ +var assert = require('assert'), +isCypherInjectionFree = require('../lib/helpers/isCypherInjectionFree'); + + +describe('isCypherInjectionFree should return True or False if the string contains any cypher injection', function () { + + it(' Should say True to url that is cypher injection free', function () { + string = 'This is a test'; + o = isCypherInjectionFree(string); + assert.equal(o, true); + + }); + + it('Should catch quotes and non alphanumeric', function () { + + string = "This is aweosme '"; + o = isCypherInjectionFree(string); + assert.equal(o, false); + + }); + + it('Should catch Cypher Keywords', function () { + + string = '/app_model/12_MATCH/12/'; + o = isCypherInjectionFree(string); + assert.equal(o, false); + }) +}); From 93a23ef69f804b0bd338f4df3b6d5fc2e6e50df3 Mon Sep 17 00:00:00 2001 From: Farhan Syed Date: Sat, 8 Feb 2014 17:59:15 -0500 Subject: [PATCH 08/57] updated the names of the funciton --- lib/helpers/isCypher.js | 29 ++++++++++++++++++++++++++++ lib/helpers/isCypherInjectionFree.js | 28 --------------------------- test/sanitize.js | 20 +++++++++++++++++++ test/sanitized.js | 28 --------------------------- 4 files changed, 49 insertions(+), 56 deletions(-) create mode 100644 lib/helpers/isCypher.js delete mode 100644 lib/helpers/isCypherInjectionFree.js create mode 100644 test/sanitize.js delete mode 100644 test/sanitized.js diff --git a/lib/helpers/isCypher.js b/lib/helpers/isCypher.js new file mode 100644 index 0000000..a256796 --- /dev/null +++ b/lib/helpers/isCypher.js @@ -0,0 +1,29 @@ + +module.exports = function (string) { + cypherKeywords = [ + 'match', + 'merge', + 'start', + 'where', + 'create', + 'set', + 'delete', + 'remove', + 'foreach', + 'union', + + ]; + + string = string.toLowerCase(); + + function containsCypher (element, index, array) { + o = string.indexOf(element); + if (o === -1) { + return false; + } + return true; + } + + return cypherKeywords.some(containsCypher); + +}; diff --git a/lib/helpers/isCypherInjectionFree.js b/lib/helpers/isCypherInjectionFree.js deleted file mode 100644 index f88a192..0000000 --- a/lib/helpers/isCypherInjectionFree.js +++ /dev/null @@ -1,28 +0,0 @@ - -module.exports = function (string) { - var pathRegex = /[-!$%^&*()+|~=`{}\[\]:";'<>?,.\/]/; - cypherKeywords = [ - 'match', - 'merge', - 'start', - 'where', - 'create', - 'set', - 'delete', - 'remove', - 'foreach' - ]; - - function containsKeywords (element, index, array) { - o = string.indexOf(element); - if (o === -1) { - return false - } - return true - } - - if (pathRegex.test(string) === false && cypherKeywords.some(containsKeywords) === false) { - return true - } - return false -} diff --git a/test/sanitize.js b/test/sanitize.js new file mode 100644 index 0000000..50885eb --- /dev/null +++ b/test/sanitize.js @@ -0,0 +1,20 @@ +var assert = require('assert'), +isCypher = require('../lib/helpers/isCypher'); + + +describe('isCypher should return True or False if the string contains any cypher injection', function () { + + it(' Should say True to url that is cypher injection free', function () { + string = 'This is a test'; + o = isCypher(string); + assert.equal(o, false); + + }); + + it('Should catch Cypher Keywords', function () { + + string = '/app_model/12_MATCH/12/'; + o = isCypher(string); + assert.equal(o, true); + }) +}); diff --git a/test/sanitized.js b/test/sanitized.js deleted file mode 100644 index 254be02..0000000 --- a/test/sanitized.js +++ /dev/null @@ -1,28 +0,0 @@ -var assert = require('assert'), -isCypherInjectionFree = require('../lib/helpers/isCypherInjectionFree'); - - -describe('isCypherInjectionFree should return True or False if the string contains any cypher injection', function () { - - it(' Should say True to url that is cypher injection free', function () { - string = 'This is a test'; - o = isCypherInjectionFree(string); - assert.equal(o, true); - - }); - - it('Should catch quotes and non alphanumeric', function () { - - string = "This is aweosme '"; - o = isCypherInjectionFree(string); - assert.equal(o, false); - - }); - - it('Should catch Cypher Keywords', function () { - - string = '/app_model/12_MATCH/12/'; - o = isCypherInjectionFree(string); - assert.equal(o, false); - }) -}); From bb77fd7e5ce7777cb5f5fbaf6ca812fddd3e13de Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Mon, 10 Mar 2014 18:07:51 -0400 Subject: [PATCH 09/57] updated to latest neo4j-js - should later switch back to tag --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a32068e..61a2a3e 100755 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "0.1.22", - "neo4j-js": "0.0.7", + "neo4j-js": "git://github.com/natgeo/neo4j-js.git#master", "q": "~0.9.7" }, "devDependencies": { From 2a4f484a623ffc7a1d332eb1b3a7189050a0b7e9 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 28 Mar 2014 16:12:29 -0400 Subject: [PATCH 10/57] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 61a2a3e..2217bf9 100755 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "0.1.22", - "neo4j-js": "git://github.com/natgeo/neo4j-js.git#master", + "neo4j-js": "https://github.com/natgeo/neo4j-js.git#master", "q": "~0.9.7" }, "devDependencies": { From dac19496ca9129f195bd3a9aa4cd7e3c30bdb929 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 28 Mar 2014 16:13:48 -0400 Subject: [PATCH 11/57] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2217bf9..8a8298d 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.0.1", + "version": "0.0.2", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From d7b6fc4516a791f41bd14634ed13cdc215a5d409 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 28 Mar 2014 16:32:32 -0400 Subject: [PATCH 12/57] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a8298d..e386bb9 100755 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "0.1.22", - "neo4j-js": "https://github.com/natgeo/neo4j-js.git#master", + "neo4j-js": "https://github.com/natgeo/neo4j-js#master", "q": "~0.9.7" }, "devDependencies": { From d789b90369f5a86e073ed887ca6947122e0e53a1 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 28 Mar 2014 16:33:55 -0400 Subject: [PATCH 13/57] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e386bb9..89347a3 100755 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "0.1.22", - "neo4j-js": "https://github.com/natgeo/neo4j-js#master", + "neo4j-js": "https://github.com/natgeo/neo4j-js", "q": "~0.9.7" }, "devDependencies": { From 5655064dc0cbe4995c9159bebbcd994b8717d358 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 28 Mar 2014 16:34:25 -0400 Subject: [PATCH 14/57] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89347a3..ccff8e2 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.0.2", + "version": "0.0.3", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 828862c01c1bff5c0d679248b7d55296ccc308d9 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 28 Mar 2014 16:35:16 -0400 Subject: [PATCH 15/57] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ccff8e2..ce1b1e8 100755 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "0.1.22", - "neo4j-js": "https://github.com/natgeo/neo4j-js", + "neo4j-js": "git+https://github.com/natgeo/neo4j-js.git", "q": "~0.9.7" }, "devDependencies": { From 08982b4840c9bf34fa0884af3e6f10fbe0de4d65 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Fri, 28 Mar 2014 16:35:22 -0400 Subject: [PATCH 16/57] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce1b1e8..a9810de 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.0.3", + "version": "0.0.4", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 22ac0bb3531835a3a4357db9a95aba96d0d9ebcf Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 2 Apr 2014 13:44:23 -0400 Subject: [PATCH 17/57] changed a bit of the sanitization functionality to enable param checks --- lib/adapter.js | 6 ++++-- lib/helpers/isCypher.js | 29 -------------------------- lib/helpers/security.js | 45 +++++++++++++++++++++++++++++++++++++++++ test/sanitize.js | 36 +++++++++++++++++++++++---------- 4 files changed, 74 insertions(+), 42 deletions(-) delete mode 100644 lib/helpers/isCypher.js create mode 100644 lib/helpers/security.js diff --git a/lib/adapter.js b/lib/adapter.js index 8a22915..7200757 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -4,7 +4,8 @@ ---------------------------------------------------------------*/ var async = require('async'), - neo = require('./connection'); + neo = require('./connection'), + security = require('./helpers/security'); var adapter = module.exports = (function() { @@ -36,7 +37,7 @@ var adapter = module.exports = (function() { port: 7474, host: 'localhost', base: '/db/data/', - debug: true + debug: false // If setting syncable, you should consider the migrate option, // which allows you to set how the sync will be performed. @@ -119,6 +120,7 @@ var adapter = module.exports = (function() { syncable: syncable, defaults: defaults, getConnection: getConnection, + sanitized: security.sanitized, query: query, // This method runs when a model is initially registered at server start time diff --git a/lib/helpers/isCypher.js b/lib/helpers/isCypher.js deleted file mode 100644 index a256796..0000000 --- a/lib/helpers/isCypher.js +++ /dev/null @@ -1,29 +0,0 @@ - -module.exports = function (string) { - cypherKeywords = [ - 'match', - 'merge', - 'start', - 'where', - 'create', - 'set', - 'delete', - 'remove', - 'foreach', - 'union', - - ]; - - string = string.toLowerCase(); - - function containsCypher (element, index, array) { - o = string.indexOf(element); - if (o === -1) { - return false; - } - return true; - } - - return cypherKeywords.some(containsCypher); - -}; diff --git a/lib/helpers/security.js b/lib/helpers/security.js new file mode 100644 index 0000000..ef3adae --- /dev/null +++ b/lib/helpers/security.js @@ -0,0 +1,45 @@ + +module.exports = (function () { + cypherKeywords = [ + 'match', + 'merge', + 'start', + 'where', + 'create', + 'set', + 'delete', + 'remove', + 'foreach', + 'union', + + ]; + specialCharReg = /[-!$%^&*()+|~=`{}\[\]:";'<>?,.]/; + + function hasCypher (param) { + return cypherKeywords.some(function(element, index, array) { + var o = param.indexOf(element); + if (o === -1 && !specialCharReg.test(element)) { + return false; + } + return true; + }); + } + + function sanitized(params) { + var truth = true; + params.forEach(function(element, index, array) { + if (truth) { + if(hasCypher(element.toLowerCase())) { + truth = false; + } + } + }); + + return truth; + } + + return { + sanitized: sanitized + }; + +})(); diff --git a/test/sanitize.js b/test/sanitize.js index 50885eb..2e12a50 100644 --- a/test/sanitize.js +++ b/test/sanitize.js @@ -1,20 +1,34 @@ -var assert = require('assert'), -isCypher = require('../lib/helpers/isCypher'); +var assert = require('assert'); describe('isCypher should return True or False if the string contains any cypher injection', function () { - it(' Should say True to url that is cypher injection free', function () { - string = 'This is a test'; - o = isCypher(string); - assert.equal(o, false); + it('Should return True to params object that is cypher injection free', function () { + var adapter = require('../lib/adapter.js'); + params = ['This is a test']; + o = adapter.sanitized(params); + assert.equal(o, true); - }); + }); - it('Should catch Cypher Keywords', function () { + it('Should return False for params object with Cypher Keywords', function () { + var adapter = require('../lib/adapter.js'); + params = ['/app_model/12_MATCH/12/']; + o = adapter.sanitized(params); + assert.equal(o, false); + }); + + it('Should return False for params object with multiple params and Cypher Keywords', function () { + var adapter = require('../lib/adapter.js'); + params = ['/app_model/12_MATCH/12/', 'something', 'test', 'START n=node(1)']; + o = adapter.sanitized(params); + assert.equal(o, false); + }); - string = '/app_model/12_MATCH/12/'; - o = isCypher(string); + it('Should return True for params object with multiple params and without Cypher Keywords', function () { + var adapter = require('../lib/adapter.js'); + params = ['/app_model/', 'something', 'test', 'something2']; + o = adapter.sanitized(params); assert.equal(o, true); - }) + }); }); From 804dc0e0bb0abaa97e628692e32a00c34e96817c Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 2 Apr 2014 20:37:38 -0400 Subject: [PATCH 18/57] finalized cypher injection traversals --- lib/helpers/security.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/helpers/security.js b/lib/helpers/security.js index ef3adae..921828a 100644 --- a/lib/helpers/security.js +++ b/lib/helpers/security.js @@ -11,14 +11,15 @@ module.exports = (function () { 'remove', 'foreach', 'union', - + 'count', + 'return' ]; specialCharReg = /[-!$%^&*()+|~=`{}\[\]:";'<>?,.]/; - function hasCypher (param) { + function hasCypher(str) { return cypherKeywords.some(function(element, index, array) { - var o = param.indexOf(element); - if (o === -1 && !specialCharReg.test(element)) { + var o = str.indexOf(element); + if (o === -1 && !specialCharReg.test(str)) { return false; } return true; @@ -26,15 +27,22 @@ module.exports = (function () { } function sanitized(params) { - var truth = true; - params.forEach(function(element, index, array) { - if (truth) { - if(hasCypher(element.toLowerCase())) { - truth = false; + var i, truth = true; + for (i in params) { + if (params.hasOwnProperty(i) && truth) { + if (typeof params[i] === 'object') { + truth = sanitized(params[i]); + } + else { + if(hasCypher(String(params[i]).toLowerCase())) { + truth = false; + } } } - }); - + else { + break; + } + } return truth; } From d09ea0c6aa55a617d2c9a7a950b46c25835bb561 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Thu, 3 Apr 2014 10:43:46 -0400 Subject: [PATCH 19/57] changed unit tests to check with objects instead of array --- lib/helpers/security.js | 12 ++++++++++++ test/sanitize.js | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/helpers/security.js b/lib/helpers/security.js index 921828a..84dfc93 100644 --- a/lib/helpers/security.js +++ b/lib/helpers/security.js @@ -1,5 +1,7 @@ module.exports = (function () { + // A list of cypher related keywords, for sanitization + cypherKeywords = [ 'match', 'merge', @@ -16,6 +18,11 @@ module.exports = (function () { ]; specialCharReg = /[-!$%^&*()+|~=`{}\[\]:";'<>?,.]/; + /** + * hasCypher() accepts a string, and sees if it contains any of the cypher keywords or special characters + * @param {String} str [A string, that comes from the params object passed into sanitized] + * @return {Boolean} [True if it has cypher or special chars, false if it doesn't] + */ function hasCypher(str) { return cypherKeywords.some(function(element, index, array) { var o = str.indexOf(element); @@ -26,6 +33,11 @@ module.exports = (function () { }); } + /** + * sanitized() accepts an object, traverses it, and checks if it contains cypher or special chars + * @param {Object} params [An object which could have other objects inside of it] + * @return {Boolean} truth [True if the object is "Sanitized", false if it isn't] + */ function sanitized(params) { var i, truth = true; for (i in params) { diff --git a/test/sanitize.js b/test/sanitize.js index 2e12a50..6ce6c2d 100644 --- a/test/sanitize.js +++ b/test/sanitize.js @@ -5,7 +5,7 @@ describe('isCypher should return True or False if the string contains any cypher it('Should return True to params object that is cypher injection free', function () { var adapter = require('../lib/adapter.js'); - params = ['This is a test']; + params = {i: 'This is a test' }; o = adapter.sanitized(params); assert.equal(o, true); @@ -13,21 +13,21 @@ describe('isCypher should return True or False if the string contains any cypher it('Should return False for params object with Cypher Keywords', function () { var adapter = require('../lib/adapter.js'); - params = ['/app_model/12_MATCH/12/']; + params = {i: '/app_model/12_MATCH/12/' }; o = adapter.sanitized(params); assert.equal(o, false); }); it('Should return False for params object with multiple params and Cypher Keywords', function () { var adapter = require('../lib/adapter.js'); - params = ['/app_model/12_MATCH/12/', 'something', 'test', 'START n=node(1)']; + params = {i: '/app_model/12_MATCH/12/', x: 'something', t: { z: 'test', y: 'START n=node(1)'} }; o = adapter.sanitized(params); assert.equal(o, false); }); it('Should return True for params object with multiple params and without Cypher Keywords', function () { var adapter = require('../lib/adapter.js'); - params = ['/app_model/', 'something', 'test', 'something2']; + params = {i: '/app_model/', x: 'something', t: { z: 'test', y: 'something2'} }; o = adapter.sanitized(params); assert.equal(o, true); }); From bc95c4500f1396f87706e163661a2970bcab0fd7 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Thu, 3 Apr 2014 11:13:11 -0400 Subject: [PATCH 20/57] updating sails-neo4j to new version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9810de..94aaa28 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.0.4", + "version": "0.1.0", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From e21199151e06b806da18684fdaf1a952d5005683 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 9 Apr 2014 19:58:20 -0400 Subject: [PATCH 21/57] removed special char checks from injection checking --- lib/helpers/security.js | 3 +-- test/sanitize.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/helpers/security.js b/lib/helpers/security.js index 84dfc93..ec49fa7 100644 --- a/lib/helpers/security.js +++ b/lib/helpers/security.js @@ -16,7 +16,6 @@ module.exports = (function () { 'count', 'return' ]; - specialCharReg = /[-!$%^&*()+|~=`{}\[\]:";'<>?,.]/; /** * hasCypher() accepts a string, and sees if it contains any of the cypher keywords or special characters @@ -26,7 +25,7 @@ module.exports = (function () { function hasCypher(str) { return cypherKeywords.some(function(element, index, array) { var o = str.indexOf(element); - if (o === -1 && !specialCharReg.test(str)) { + if (o === -1) { return false; } return true; diff --git a/test/sanitize.js b/test/sanitize.js index 6ce6c2d..efcf02c 100644 --- a/test/sanitize.js +++ b/test/sanitize.js @@ -20,7 +20,7 @@ describe('isCypher should return True or False if the string contains any cypher it('Should return False for params object with multiple params and Cypher Keywords', function () { var adapter = require('../lib/adapter.js'); - params = {i: '/app_model/12_MATCH/12/', x: 'something', t: { z: 'test', y: 'START n=node(1)'} }; + params = {i: '/app_model/COUNT/12/', x: 'something', t: { z: 'test', y: 'START n=node(1)'} }; o = adapter.sanitized(params); assert.equal(o, false); }); From 9be9f56a2e51c2662d826e705813b0e656fe8533 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 9 Apr 2014 20:04:55 -0400 Subject: [PATCH 22/57] updating to patch 0.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 94aaa28..da6ecc3 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.1.0", + "version": "0.1.1", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 8626ef1576113659afc92182bd77520e8a291913 Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Fri, 2 May 2014 21:22:34 +0200 Subject: [PATCH 23/57] Fixed create and find queries. Probably some other broken stuff there... --- lib/adapter.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 7200757..8dabad3 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -112,6 +112,27 @@ var adapter = module.exports = (function() { }); } + function createQuery(q, params, cb) { + neo.graph(function(gr) { + if (defaults.debug) { + console.log('q: ', q.join('\n'), 'p: ', params); // spit out the queries for debug + } + gr.query(q.join('\n'), params, function (err, results) { + // Respond with error or newly created model instance + if (err) { + // console.log(err); + // console.log(err.stack); + cb(err, null); + } + else { + // console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure + + cb(null, results[0]["n"]); + } + }); + }); + } + function getConnection() { return neo.connect(defaults); } @@ -186,7 +207,7 @@ var adapter = module.exports = (function() { 'RETURN n' ]; - query(q, params, cb); + createQuery(q, params, cb); }, createMany: function(collectionName, params, cb) { @@ -220,11 +241,11 @@ var adapter = module.exports = (function() { if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? q = [ 'MATCH (n)', - 'WHERE ' + collectionName + delimiter + toWhere('n', params), + 'WHERE ' + collectionName + delimiter + toWhere('n', params.where), 'RETURN n' ]; - query(q, params, cb); + query(q, params.where, cb); }, // REQUIRED method if users expect to call Model.update() From 50e163a9a0bb7b5abfea07fa17a032a9fcd367ed Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Mon, 12 May 2014 21:51:24 +0200 Subject: [PATCH 24/57] Fixed generic controller/find routes (findAll) --- lib/adapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/adapter.js b/lib/adapter.js index 8dabad3..1b1e873 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -81,6 +81,8 @@ var adapter = module.exports = (function() { } function toWhere(object, properties) { + if (!properties) + return ""; var i, query = [], q; for (i in properties) { if (properties.hasOwnProperty(i)) { @@ -238,7 +240,7 @@ var adapter = module.exports = (function() { if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = 'n:' + collectionName; } - if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? + if (params.where && collectionName) { delimiter = ' AND '; } // do we have a label and params? q = [ 'MATCH (n)', 'WHERE ' + collectionName + delimiter + toWhere('n', params.where), From c7c22739153639cff85c3f78878e3df929b514ed Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Tue, 13 May 2014 23:45:59 +0200 Subject: [PATCH 25/57] Find queries now return a more standard object (without the useless 'n' key for each node) --- lib/adapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/adapter.js b/lib/adapter.js index 1b1e873..f176bcb 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -107,7 +107,8 @@ var adapter = module.exports = (function() { } else { // console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure - + for(i=0; i Date: Wed, 14 May 2014 00:35:24 +0200 Subject: [PATCH 26/57] Added ~= query modifier for case insensitive searchs --- lib/adapter.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index f176bcb..13271bc 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -86,11 +86,19 @@ var adapter = module.exports = (function() { var i, query = [], q; for (i in properties) { if (properties.hasOwnProperty(i)) { - q = object + '.' + i + '=' + '{' + i + '}'; + var equality = "="; + if (properties[i].hasOwnProperty("~=")) + { + properties[i] = "(?i)" + properties[i]["~="]; + equality = "=~"; + } + q = object + '.' + i + equality + '{' + i + '}'; query.push(q); } } - return '(' + query.join(' AND ') + ')'; + var re = '(' + query.join(' AND ') + ')'; + console.log(re); + return re; } function query(q, params, cb) { From c7eec0fa580957ce9ef2ece674498ae190915eb3 Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Wed, 14 May 2014 01:49:02 +0200 Subject: [PATCH 27/57] Added 'or' query modifier --- lib/adapter.js | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 13271bc..9c0d6af 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -80,11 +80,9 @@ var adapter = module.exports = (function() { return names.join(','); } - function toWhere(object, properties) { - if (!properties) - return ""; - var i, query = [], q; - for (i in properties) { + function andJoin(object, properties) { + var query = [], q; + for (var i in properties) { if (properties.hasOwnProperty(i)) { var equality = "="; if (properties[i].hasOwnProperty("~=")) @@ -96,9 +94,35 @@ var adapter = module.exports = (function() { query.push(q); } } - var re = '(' + query.join(' AND ') + ')'; - console.log(re); - return re; + return '(' + query.join(' AND ') + ')'; + } + + function toWhere(object, params) { + var properties = params.where; + if (!properties) + return ""; + var query = [], q; + var count = 0; + for (var i in properties) { + count++; + } + console.log(properties); + if (count === 1 && properties.hasOwnProperty("or")) + { + var targetProperties = new Object(); + for (var i in properties["or"]) + { + q = andJoin(object, properties["or"][i]); + query.push(q); + _.extend(targetProperties,properties["or"][i]); + } + params.where = targetProperties; + } + else + query.push(andJoin(object, properties)); + console.log(properties); + + return '(' + query.join(' OR ') + ')'; } function query(q, params, cb) { @@ -252,9 +276,11 @@ var adapter = module.exports = (function() { if (params.where && collectionName) { delimiter = ' AND '; } // do we have a label and params? q = [ 'MATCH (n)', - 'WHERE ' + collectionName + delimiter + toWhere('n', params.where), + 'WHERE ' + collectionName + delimiter + toWhere('n', params), 'RETURN n' ]; + console.log(q); + console.log(params.where); query(q, params.where, cb); }, From 8077f13d9b850e4f0e3e4ec48f20f701d93a8fae Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Wed, 14 May 2014 01:52:20 +0200 Subject: [PATCH 28/57] Cleared console debug --- lib/adapter.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 9c0d6af..81e34a2 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -106,7 +106,6 @@ var adapter = module.exports = (function() { for (var i in properties) { count++; } - console.log(properties); if (count === 1 && properties.hasOwnProperty("or")) { var targetProperties = new Object(); @@ -120,7 +119,6 @@ var adapter = module.exports = (function() { } else query.push(andJoin(object, properties)); - console.log(properties); return '(' + query.join(' OR ') + ')'; } @@ -279,8 +277,6 @@ var adapter = module.exports = (function() { 'WHERE ' + collectionName + delimiter + toWhere('n', params), 'RETURN n' ]; - console.log(q); - console.log(params.where); query(q, params.where, cb); }, From 56eff2e25acae3b424050f98929b9173ca15bd95 Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Wed, 14 May 2014 20:53:27 +0200 Subject: [PATCH 29/57] Flatten results of find query --- lib/adapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/adapter.js b/lib/adapter.js index 81e34a2..b5bcc34 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -138,7 +138,11 @@ var adapter = module.exports = (function() { else { // console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure for(i=0; i Date: Wed, 14 May 2014 22:43:45 +0200 Subject: [PATCH 30/57] Unified query function for finding and creating --- lib/adapter.js | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index b5bcc34..d4a5461 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -149,27 +149,6 @@ var adapter = module.exports = (function() { }); } - function createQuery(q, params, cb) { - neo.graph(function(gr) { - if (defaults.debug) { - console.log('q: ', q.join('\n'), 'p: ', params); // spit out the queries for debug - } - gr.query(q.join('\n'), params, function (err, results) { - // Respond with error or newly created model instance - if (err) { - // console.log(err); - // console.log(err.stack); - cb(err, null); - } - else { - // console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure - - cb(null, results[0]["n"]); - } - }); - }); - } - function getConnection() { return neo.connect(defaults); } @@ -244,7 +223,7 @@ var adapter = module.exports = (function() { 'RETURN n' ]; - createQuery(q, params, cb); + query(q, params, cb); }, createMany: function(collectionName, params, cb) { From 43368926cb08f834cf7deed89304066c6f4a130a Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Thu, 15 May 2014 01:02:46 +0200 Subject: [PATCH 31/57] Compatibility with findOne(id) --- lib/adapter.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/adapter.js b/lib/adapter.js index d4a5461..1c56ffc 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -90,7 +90,13 @@ var adapter = module.exports = (function() { properties[i] = "(?i)" + properties[i]["~="]; equality = "=~"; } - q = object + '.' + i + equality + '{' + i + '}'; + var field = object + '.' + i; + if (i==="id") + { + field = "id("+object+")"; + properties[i] = parseInt(properties[i]); + } + q = field + equality + '{' + i + '}'; query.push(q); } } From 65ab4430e0cf4d320809a50cb7e90bc0560a88b9 Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Tue, 20 May 2014 22:48:54 +0200 Subject: [PATCH 32/57] Added destroy action --- lib/adapter.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 1c56ffc..7747021 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -282,14 +282,24 @@ var adapter = module.exports = (function() { }, // REQUIRED method if users expect to call Model.destroy() - destroy: function(collectionName, options, cb) { + destroy: function(collectionName, params, cb) { + var q, delimiter = ''; + + if (collectionName === null) { collectionName = ''; } // do we have a label? + else { collectionName = 'n:' + collectionName; } + if (params.where && collectionName) { delimiter = ' AND '; } // do we have a label and params? + q = [ + 'MATCH (n)', + 'WHERE ' + collectionName + delimiter + toWhere('n', params), + 'DELETE n' + ]; // ** Filter by criteria in options to generate result set // Destroy all model(s) in the result set // Return an error or nothing at all - cb(); + query(q, params.where, cb); }, From f42d533da81f7af6c60af202b6efba594dcce60c Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Tue, 20 May 2014 23:32:40 +0200 Subject: [PATCH 33/57] Added update action --- lib/adapter.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 7747021..65ae715 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -271,14 +271,24 @@ var adapter = module.exports = (function() { }, // REQUIRED method if users expect to call Model.update() - update: function(collectionName, options, values, cb) { - + update: function(collectionName, params, values, cb) { // ** Filter by criteria in options to generate result set + var q, delimiter = ''; + + if (collectionName === null) { collectionName = ''; } // do we have a label? + else { collectionName = 'n:' + collectionName; } - // Then update all model(s) in the result set + if (params.where && collectionName) { delimiter = ' AND '; } // do we have a label and params? + q = [ + 'MATCH (n)', + 'WHERE ' + collectionName + delimiter + toWhere('n', params), + 'SET n = {values}', + 'RETURN n' + ]; - // Respond with error or a *list* of models that were updated - cb(); + params.where = _.extend(params.where, {values: values}); + + query(q, params.where, cb); }, // REQUIRED method if users expect to call Model.destroy() From 6e19e0583e12ba623531e47b77f695859aef7718 Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Wed, 21 May 2014 01:12:45 +0200 Subject: [PATCH 34/57] Fixed id integer parsing --- lib/adapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 65ae715..5faadc5 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -145,8 +145,9 @@ var adapter = module.exports = (function() { // console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure for(i=0; i Date: Sat, 24 May 2014 14:18:51 +0200 Subject: [PATCH 35/57] Added unique parameter for query to distinguish query on one or multiple objects --- lib/adapter.js | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 5faadc5..9926fa8 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -129,20 +129,16 @@ var adapter = module.exports = (function() { return '(' + query.join(' OR ') + ')'; } - function query(q, params, cb) { + function query(q, params, cb, unique) { neo.graph(function(gr) { if (defaults.debug) { console.log('q: ', q.join('\n'), 'p: ', params); // spit out the queries for debug } gr.query(q.join('\n'), params, function (err, results) { - // Respond with error or newly created model instance if (err) { - // console.log(err); - // console.log(err.stack); cb(err, null); } else { - // console.log(JSON.stringify(results, null, 5 )); // printing may help to visualize the returned structure for(i=0; i Date: Sat, 24 May 2014 15:29:05 +0200 Subject: [PATCH 36/57] Non updated properties are not erased when updating a node : Fixes #4 --- lib/adapter.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 9926fa8..b374857 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -80,7 +80,7 @@ var adapter = module.exports = (function() { return names.join(','); } - function andJoin(object, properties) { + function andJoin(object, properties, delimiter) { var query = [], q; for (var i in properties) { if (properties.hasOwnProperty(i)) { @@ -100,7 +100,7 @@ var adapter = module.exports = (function() { query.push(q); } } - return '(' + query.join(' AND ') + ')'; + return query.join(delimiter); } function toWhere(object, params) { @@ -117,14 +117,14 @@ var adapter = module.exports = (function() { var targetProperties = new Object(); for (var i in properties["or"]) { - q = andJoin(object, properties["or"][i]); + q = '(' + andJoin(object, properties["or"][i]) + ')'; query.push(q); _.extend(targetProperties,properties["or"][i]); } params.where = targetProperties; } else - query.push(andJoin(object, properties)); + query.push('(' + andJoin(object, properties) + ')'); return '(' + query.join(' OR ') + ')'; } @@ -280,7 +280,7 @@ var adapter = module.exports = (function() { q = [ 'MATCH (n)', 'WHERE ' + collectionName + delimiter + toWhere('n', params), - 'SET n = {values}', + 'SET ' + andJoin('n', _.omit(values, 'id'), ','), 'RETURN n' ]; From 740b0e8de75fd369cb48122dcf19176554be40a0 Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Sat, 24 May 2014 15:42:11 +0200 Subject: [PATCH 37/57] Really Fixes #4 --- lib/adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/adapter.js b/lib/adapter.js index b374857..85affe2 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -284,7 +284,7 @@ var adapter = module.exports = (function() { 'RETURN n' ]; - params.where = _.extend(params.where, {values: values}); + params.where = _.extend(params.where, values); query(q, params.where, cb, true); }, From c5c2906406a5d171777c0568d638fb710b3a06cb Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Sun, 25 May 2014 00:19:38 +0200 Subject: [PATCH 38/57] Added creating relationship support --- lib/adapter.js | 117 +++++++++++++++++++------------------------------ 1 file changed, 46 insertions(+), 71 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 85affe2..89ceb79 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -80,7 +80,7 @@ var adapter = module.exports = (function() { return names.join(','); } - function andJoin(object, properties, delimiter) { + function andJoin(object, properties, andKeyword, namespace) { var query = [], q; for (var i in properties) { if (properties.hasOwnProperty(i)) { @@ -96,15 +96,16 @@ var adapter = module.exports = (function() { field = "id("+object+")"; properties[i] = parseInt(properties[i]); } - q = field + equality + '{' + i + '}'; + completeName = (namespace === null) ? i : namespace + "_" + i; + q = field + equality + '{' + completeName + '}'; query.push(q); } } - return query.join(delimiter); + return query.join(andKeyword); } - function toWhere(object, params) { - var properties = params.where; + function toWhere(object, params, namespace) { + properties = params.where if (!properties) return ""; var query = [], q; @@ -117,23 +118,20 @@ var adapter = module.exports = (function() { var targetProperties = new Object(); for (var i in properties["or"]) { - q = '(' + andJoin(object, properties["or"][i]) + ')'; + q = '(' + andJoin(object, properties["or"][i], 'AND', namespace) + ')'; query.push(q); _.extend(targetProperties,properties["or"][i]); } params.where = targetProperties; } else - query.push('(' + andJoin(object, properties) + ')'); + query.push('(' + andJoin(object, properties, 'AND', namespace) + ')'); return '(' + query.join(' OR ') + ')'; } function query(q, params, cb, unique) { neo.graph(function(gr) { - if (defaults.debug) { - console.log('q: ', q.join('\n'), 'p: ', params); // spit out the queries for debug - } gr.query(q.join('\n'), params, function (err, results) { if (err) { cb(err, null); @@ -164,57 +162,8 @@ var adapter = module.exports = (function() { sanitized: security.sanitized, query: query, - // This method runs when a model is initially registered at server start time - // registerCollection: function(collection, cb) { - // // neo4j is schemaless, and therefore there is no 'model registeration' - // console.log(collection + ' Model registered'); - // cb(); - // }, - - - // The following methods are optional - //////////////////////////////////////////////////////////// - - // Optional hook fired when a model is unregistered, typically at server halt - // useful for tearing down remaining open connections, etc. - // teardown: function(cb) { - // cb(); - // }, - - - // Irrelevant for Neo4j - - // // REQUIRED method if integrating with a schemaful database - // define: function(collectionName, definition, cb) { - - // // Define a new "table" or "collection" schema in the data store - // cb(); - // }, - // // REQUIRED method if integrating with a schemaful database - // describe: function(collectionName, cb) { - - // // Respond with the schema (attributes) for a collection or table in the data store - // var attributes = {}; - // cb(null, attributes); - // }, - // // REQUIRED method if integrating with a schemaful database - // drop: function(collectionName, cb) { - // // Drop a "table" or "collection" schema from the data store - // cb(); - // }, - - // Optional override of built-in alter logic - // Can be simulated with describe(), define(), and drop(), - // but will probably be made much more efficient by an override here - // alter: function (collectionName, attributes, cb) { - // Modify the schema of a table or collection in the data store - // cb(); - // }, - - - // REQUIRED method if users expect to call Model.create() or any methods create: function(collectionName, params, cb) { - // Create a single new model specified by values + var q, delimiter = ''; if (collectionName === null) { collectionName = ''; } // do we have a label? @@ -231,7 +180,7 @@ var adapter = module.exports = (function() { }, createMany: function(collectionName, params, cb) { - // Create a single new model specified by values + var q, delimiter = ''; if (collectionName === null) { collectionName = ''; } // do we have a label? @@ -247,12 +196,8 @@ var adapter = module.exports = (function() { query(q, params, cb, false); }, - // REQUIRED method if users expect to call Model.find(), Model.findAll() or related methods - // You're actually supporting find(), findAll(), and other methods here - // but the core will take care of supporting all the different usages. - // (e.g. if this is a find(), not a findAll(), it will only send back a single model) find: function(collectionName, params, cb) { - // ** Filter by criteria in options to generate result set + var q, delimiter = ''; if (collectionName === null) { collectionName = ''; } // do we have a label? @@ -268,9 +213,8 @@ var adapter = module.exports = (function() { query(q, params.where, cb, false); }, - // REQUIRED method if users expect to call Model.update() update: function(collectionName, params, values, cb) { - // ** Filter by criteria in options to generate result set + var q, delimiter = ''; if (collectionName === null) { collectionName = ''; } // do we have a label? @@ -289,7 +233,6 @@ var adapter = module.exports = (function() { query(q, params.where, cb, true); }, - // REQUIRED method if users expect to call Model.destroy() destroy: function(collectionName, params, cb) { var q, delimiter = ''; @@ -302,8 +245,6 @@ var adapter = module.exports = (function() { 'WHERE ' + collectionName + delimiter + toWhere('n', params), 'DELETE n' ]; - - // Return an error or nothing at all query(q, params.where, cb, true); }, @@ -317,6 +258,40 @@ var adapter = module.exports = (function() { // for an example, check out: // https://github.com/balderdashy/sails-dirty/blob/master/DirtyAdapter.js#L247 + }, + + link: function(collectionName, predecessorParams, successorCollectionName, successorParams, relationshipType, relationshipParams, cb) { + var q, predecessorDelimiter = '', successorDelimiter = '', predecessorNamespace = 'pred', successorNamespace = 'succ'; + + if (collectionName === null) { collectionName = ''; } // do we have a label? + else { collectionName = 'a:' + collectionName; } + if (predecessorParams && collectionName) { predecessorDelimiter = ' AND '; } + + if (successorCollectionName === null) { successorCollectionName = ''; } // do we have a label? + else { successorCollectionName = 'b:' + successorCollectionName; } + if (successorParams && collectionName) { successorDelimiter = ' AND '; } + + relationshipParams = _.isEmpty(relationshipParams) ? "" : " " + JSON.stringify(relationshipParams); + + q = [ + 'MATCH (a),(b)', + 'WHERE ' + collectionName + predecessorDelimiter + toWhere('a', {where: predecessorParams}, predecessorNamespace) + +' AND ' + successorCollectionName + successorDelimiter + toWhere('b', {where: successorParams}, successorNamespace), + 'CREATE (a)-[n:' + relationshipType + relationshipParams + ']->(b)', + 'RETURN n' + ]; + + params = {}; + _.each(predecessorParams, function(value, key) { + key = predecessorNamespace + '_' + key; + params[key] = value; + }); + _.each(successorParams, function(value, key) { + key = successorNamespace + '_' + key; + params[key] = value; + }); + + query(q, params, cb, true); } From 16c310b1878efd200fc776b7d9dcb314f259384f Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Sun, 25 May 2014 00:22:18 +0200 Subject: [PATCH 39/57] Fix for undefined label namespace for node properties --- lib/adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/adapter.js b/lib/adapter.js index 89ceb79..960f37d 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -96,7 +96,7 @@ var adapter = module.exports = (function() { field = "id("+object+")"; properties[i] = parseInt(properties[i]); } - completeName = (namespace === null) ? i : namespace + "_" + i; + completeName = (typeof namespace === "undefined" || namespace === null) ? i : namespace + "_" + i; q = field + equality + '{' + completeName + '}'; query.push(q); } From d73f395eee43862ae1a154cf06daadf38448e1df Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Mon, 2 Jun 2014 21:33:40 +0200 Subject: [PATCH 40/57] Fixed regex typo --- lib/adapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 960f37d..e6c8aa4 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -85,9 +85,9 @@ var adapter = module.exports = (function() { for (var i in properties) { if (properties.hasOwnProperty(i)) { var equality = "="; - if (properties[i].hasOwnProperty("~=")) + if (properties[i].hasOwnProperty("=~")) { - properties[i] = "(?i)" + properties[i]["~="]; + properties[i] = "(?i)" + properties[i]["=~"]; equality = "=~"; } var field = object + '.' + i; From b968b370a97a137f46916cd515c9ea6dafbabf34 Mon Sep 17 00:00:00 2001 From: Woody Rousseau Date: Mon, 2 Jun 2014 21:57:20 +0200 Subject: [PATCH 41/57] Fully switched to single quotes --- lib/adapter.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index e6c8aa4..1bfff82 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -84,19 +84,19 @@ var adapter = module.exports = (function() { var query = [], q; for (var i in properties) { if (properties.hasOwnProperty(i)) { - var equality = "="; - if (properties[i].hasOwnProperty("=~")) + var equality = '='; + if (properties[i].hasOwnProperty('=~')) { - properties[i] = "(?i)" + properties[i]["=~"]; - equality = "=~"; + properties[i] = '(?i)' + properties[i]['=']; + equality = '=~'; } var field = object + '.' + i; - if (i==="id") + if (i==='id') { - field = "id("+object+")"; + field = 'id('+object+')'; properties[i] = parseInt(properties[i]); } - completeName = (typeof namespace === "undefined" || namespace === null) ? i : namespace + "_" + i; + completeName = (typeof namespace === 'undefined' || namespace === null) ? i : namespace + '_' + i; q = field + equality + '{' + completeName + '}'; query.push(q); } @@ -107,20 +107,20 @@ var adapter = module.exports = (function() { function toWhere(object, params, namespace) { properties = params.where if (!properties) - return ""; + return ''; var query = [], q; var count = 0; for (var i in properties) { count++; } - if (count === 1 && properties.hasOwnProperty("or")) + if (count === 1 && properties.hasOwnProperty('or')) { var targetProperties = new Object(); - for (var i in properties["or"]) + for (var i in properties['or']) { - q = '(' + andJoin(object, properties["or"][i], 'AND', namespace) + ')'; + q = '(' + andJoin(object, properties['or'][i], 'AND', namespace) + ')'; query.push(q); - _.extend(targetProperties,properties["or"][i]); + _.extend(targetProperties,properties['or'][i]); } params.where = targetProperties; } @@ -271,7 +271,7 @@ var adapter = module.exports = (function() { else { successorCollectionName = 'b:' + successorCollectionName; } if (successorParams && collectionName) { successorDelimiter = ' AND '; } - relationshipParams = _.isEmpty(relationshipParams) ? "" : " " + JSON.stringify(relationshipParams); + relationshipParams = _.isEmpty(relationshipParams) ? '' : ' ' + JSON.stringify(relationshipParams); q = [ 'MATCH (a),(b)', From 7eaa6b4048ddca6bc29b673ba8a9387b34dba7f9 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 4 Jun 2014 14:14:38 -0400 Subject: [PATCH 42/57] version up --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index da6ecc3..5cc52aa 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.1.1", + "version": "0.1.2", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 8d0bd2eb8866699e33347c865fe86594d5fd5d49 Mon Sep 17 00:00:00 2001 From: Lucas Serven Date: Wed, 4 Jun 2014 15:18:59 -0400 Subject: [PATCH 43/57] Bump version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5cc52aa..75bb26a 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.1.2", + "version": "0.2.0", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 52b45206034e19b4385074a12bb7e8bae93169e7 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 4 Jun 2014 17:31:25 -0400 Subject: [PATCH 44/57] revert back to 0.1.2 because the 0.2.0 tag wasn't with the appropriate version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 75bb26a..5cc52aa 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.2.0", + "version": "0.1.2", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From a918b96b205ff65cc7761f634e32e2a9c7300fa1 Mon Sep 17 00:00:00 2001 From: Lucas Serven Date: Wed, 11 Jun 2014 18:46:37 -0400 Subject: [PATCH 45/57] Fix bug in query function. --- lib/adapter.js | 13 +++++++------ package.json | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 1bfff82..a8550c3 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -5,7 +5,8 @@ var async = require('async'), neo = require('./connection'), - security = require('./helpers/security'); + security = require('./helpers/security'), + _ = require('lodash'); var adapter = module.exports = (function() { @@ -105,7 +106,7 @@ var adapter = module.exports = (function() { } function toWhere(object, params, namespace) { - properties = params.where + properties = params.where; if (!properties) return ''; var query = [], q; @@ -115,7 +116,7 @@ var adapter = module.exports = (function() { } if (count === 1 && properties.hasOwnProperty('or')) { - var targetProperties = new Object(); + var targetProperties = {}; for (var i in properties['or']) { q = '(' + andJoin(object, properties['or'][i], 'AND', namespace) + ')'; @@ -139,8 +140,8 @@ var adapter = module.exports = (function() { else { for(i=0; i Date: Thu, 12 Jun 2014 11:15:19 -0400 Subject: [PATCH 46/57] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35b9735..afb6e3e 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.1.3", + "version": "0.2.1", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 175a2899a24017466d965763a452d67542308fe2 Mon Sep 17 00:00:00 2001 From: Farhan Syed Date: Mon, 16 Jun 2014 15:49:04 -0400 Subject: [PATCH 47/57] updated to use the 0.9 adapter api --- lib/adapter.js | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index a8550c3..35ae4e6 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -17,14 +17,14 @@ var adapter = module.exports = (function() { connection, // Including a commitLog config enables transactions in this adapter - // Please note that these are not ACID-compliant transactions: + // Please note that these are not ACID-compliant transactions: // They guarantee *ISOLATION*, and use a configurable persistent store, so they are *DURABLE* in the face of server crashes. // However there is no scheduled task that rebuild state from a mid-step commit log at server start, so they're not CONSISTENT yet. // and there is still lots of work to do as far as making them ATOMIC (they're not undoable right now) // // However, for the immediate future, they do a great job of preventing race conditions, and are // better than a naive solution. They add the most value in findOrCreate() and createEach(). - // + // // commitLog: { // identity: '__default_mongo_transaction', // adapter: 'sails-mongo' @@ -40,19 +40,23 @@ var adapter = module.exports = (function() { base: '/db/data/', debug: false - // If setting syncable, you should consider the migrate option, + // If setting syncable, you should consider the migrate option, // which allows you to set how the sync will be performed. // It can be overridden globally in an app (config/adapters.js) and on a per-model basis. // // drop => Drop schema and data, then recreate it - // alter => Drop/add columns as necessary, but try + // alter => Drop/add columns as necessary, but try // safe => Don't change anything (good for production DBs) // migrate: 'alter' }; //Init - connection = neo.connect(defaults); + + function registerCollection(collection, cb) { + connection = neo.connect(collection.adapter.defaults); + cb(); + } function parseOne(values) { var i, names = [], name; @@ -97,7 +101,7 @@ var adapter = module.exports = (function() { field = 'id('+object+')'; properties[i] = parseInt(properties[i]); } - completeName = (typeof namespace === 'undefined' || namespace === null) ? i : namespace + '_' + i; + completeName = (typeof namespace === 'undefined' || namespace === null) ? i : namespace + '_' + i; q = field + equality + '{' + completeName + '}'; query.push(q); } @@ -156,20 +160,23 @@ var adapter = module.exports = (function() { return neo.connect(defaults); } + //return value of adapter function constructor + return { syncable: syncable, defaults: defaults, getConnection: getConnection, sanitized: security.sanitized, query: query, + registerCollection: registerCollection, create: function(collectionName, params, cb) { var q, delimiter = ''; - + if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = ':' + collectionName; } - + if (params !== null && collectionName !== null) { delimiter = ' AND '; } // do we have a label and params? q = [ @@ -183,12 +190,12 @@ var adapter = module.exports = (function() { createMany: function(collectionName, params, cb) { var q, delimiter = ''; - + if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = ':' + collectionName; } - + if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? - + q = [ 'CREATE (n' + collectionName + ' { ' + parseMany(params) + ' })', 'RETURN n' @@ -200,7 +207,7 @@ var adapter = module.exports = (function() { find: function(collectionName, params, cb) { var q, delimiter = ''; - + if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = 'n:' + collectionName; } @@ -217,7 +224,7 @@ var adapter = module.exports = (function() { update: function(collectionName, params, values, cb) { var q, delimiter = ''; - + if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = 'n:' + collectionName; } @@ -230,13 +237,13 @@ var adapter = module.exports = (function() { ]; params.where = _.extend(params.where, values); - + query(q, params.where, cb, true); }, destroy: function(collectionName, params, cb) { var q, delimiter = ''; - + if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = 'n:' + collectionName; } @@ -267,7 +274,7 @@ var adapter = module.exports = (function() { if (collectionName === null) { collectionName = ''; } // do we have a label? else { collectionName = 'a:' + collectionName; } if (predecessorParams && collectionName) { predecessorDelimiter = ' AND '; } - + if (successorCollectionName === null) { successorCollectionName = ''; } // do we have a label? else { successorCollectionName = 'b:' + successorCollectionName; } if (successorParams && collectionName) { successorDelimiter = ' AND '; } @@ -276,12 +283,12 @@ var adapter = module.exports = (function() { q = [ 'MATCH (a),(b)', - 'WHERE ' + collectionName + predecessorDelimiter + toWhere('a', {where: predecessorParams}, predecessorNamespace) + 'WHERE ' + collectionName + predecessorDelimiter + toWhere('a', {where: predecessorParams}, predecessorNamespace) +' AND ' + successorCollectionName + successorDelimiter + toWhere('b', {where: successorParams}, successorNamespace), 'CREATE (a)-[n:' + relationshipType + relationshipParams + ']->(b)', 'RETURN n' ]; - + params = {}; _.each(predecessorParams, function(value, key) { key = predecessorNamespace + '_' + key; @@ -333,7 +340,7 @@ var adapter = module.exports = (function() { // The first argument of callbacks is always an error object. // For some core methods, Sails.js will add support for .done()/promise usage. // - // + + // + // //////////////////////////////////////////////////////////////////////////////////////////////////// From 97ab99a01d52c2318e320c93e058820fb43d7fea Mon Sep 17 00:00:00 2001 From: Farhan Syed Date: Tue, 17 Jun 2014 16:20:22 -0400 Subject: [PATCH 48/57] tests are doing awesome things --- lib/adapter.js | 15 ++++++++++++++- test/create.js | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 35ae4e6..ac7cc2b 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -52,9 +52,21 @@ var adapter = module.exports = (function() { //Init + function createConnection(collection) { + var col; + if (!collection) { + col = {}; + } else { + col = collection.adapter.defaults; + + } + _.merge({}, defaults, col); + neo.connect(defaults); + + } function registerCollection(collection, cb) { - connection = neo.connect(collection.adapter.defaults); + createConnection(); cb(); } @@ -169,6 +181,7 @@ var adapter = module.exports = (function() { sanitized: security.sanitized, query: query, registerCollection: registerCollection, + createConnection: createConnection, create: function(collectionName, params, cb) { diff --git a/test/create.js b/test/create.js index 90314e4..fb865cc 100644 --- a/test/create.js +++ b/test/create.js @@ -4,6 +4,7 @@ describe('Creating Nodes', function () { var nodes = []; it('should create one node with a property test = "1"', function (done) { var adapter = require('../lib/adapter.js'); + adapter.createConnection(); adapter.create(null, { test: 1 }, function(err, results) { if (err) { throw err; } nodes.push(results); @@ -13,10 +14,11 @@ describe('Creating Nodes', function () { it('should create multiple nodes with the property test = "1"', function(done) { var adapter = require('../lib/adapter.js'); + adapter.createConnection(); adapter.createMany(null,{params:[{ test: 1 },{ test: 1 }]}, function(err, results) { if (err) { throw err; } nodes.push(results); done(); }); }); -}); \ No newline at end of file +}); From dcb1bab04d591adb59abc4936c8ca1619a248275 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Tue, 17 Jun 2014 16:21:49 -0400 Subject: [PATCH 49/57] test fixes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afb6e3e..78b874a 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.2.1", + "version": "0.2.2", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 807e8bf76884efda5cec47aa9d4c3dc7f0d283c5 Mon Sep 17 00:00:00 2001 From: Farhan Syed Date: Tue, 17 Jun 2014 17:08:02 -0400 Subject: [PATCH 50/57] have the tests working with correct registerConnection method --- lib/adapter.js | 13 +++++++------ lib/connection.js | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index ac7cc2b..3db0e0e 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -54,19 +54,20 @@ var adapter = module.exports = (function() { function createConnection(collection) { var col; - if (!collection) { - col = {}; - } else { + try { col = collection.adapter.defaults; + } catch(err) { + col = {}; } - _.merge({}, defaults, col); - neo.connect(defaults); + connectionSettings = {}; + _.merge(connectionSettings, defaults, col); + neo.connect(connectionSettings); } function registerCollection(collection, cb) { - createConnection(); + createConnection(collection); cb(); } diff --git a/lib/connection.js b/lib/connection.js index 3a6e44e..0dc3b3d 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -3,7 +3,7 @@ var neo4j = require('neo4j-js'), module.exports = (function() { var graph = false, d = Q.defer(); // if an active connection exists, use it instead of tearing the previous one down - + function connect(connection) { if (!graph) { var path = connection.protocol + connection.host + ':' + connection.port + connection.base; @@ -29,4 +29,4 @@ module.exports = (function() { connect: connect, graph: graphDo }; -})(); \ No newline at end of file +})(); From 7a95a5885157da10c98f30ba6d6d59d35b5b16d0 Mon Sep 17 00:00:00 2001 From: Farhan Syed Date: Tue, 17 Jun 2014 17:08:59 -0400 Subject: [PATCH 51/57] updated package --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 78b874a..6b138a7 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.2.2", + "version": "0.2.3", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 3edd75cfc2c1c7b756b7420634eaabf11d562836 Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Thu, 19 Jun 2014 11:32:13 -0400 Subject: [PATCH 52/57] fixing up the api --- lib/adapter.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 3db0e0e..321a0b8 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -151,19 +151,22 @@ var adapter = module.exports = (function() { function query(q, params, cb, unique) { neo.graph(function(gr) { gr.query(q.join('\n'), params, function (err, results) { + if (connectionSettings.debug) console.log(q.join('\n'), results); if (err) { cb(err, null); } else { - for(i=0; i Date: Thu, 19 Jun 2014 11:32:38 -0400 Subject: [PATCH 53/57] up tag --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b138a7..31abbb3 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.2.3", + "version": "0.2.4", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { From 38c13da7b376a208c296d743e43fc173d4e18b5e Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Wed, 9 Jul 2014 11:55:50 -0400 Subject: [PATCH 54/57] Update README.md --- README.md | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 7c6e466..dd6f5aa 100755 --- a/README.md +++ b/README.md @@ -1,32 +1,3 @@ -![image_squidhome@2x.png](http://i.imgur.com/RIvu9.png) - -# BoilerplateAdapter - -This template exists to make it easier for you to get started writing an official adapter for Sails.js. - - -## Getting started -It's usually pretty easy to add your own adapters for integrating with proprietary systems or existing open APIs. For most things, it's as easy as `require('some-module')` and mapping the appropriate methods to match waterline semantics. To get started: - -1. Fork this repository -2. Set up your README and package.json file. Sails.js adapter module names are of the form sails-*, where * is the name of the datastore or service you're integrating with. -3. Build your adapter. - -## How to test your adapter -1. Run `npm link` in this adapter's directory -2. Clone the sails.js core and modify the tests to use your new adapter. -3. Run `npm link sails-boilerplate` -4. From the sails.js core directory, run `npm test`. - -## Submitting your adapter -1. Do a pull request to this repository (make sure you attribute yourself as the author set the license in the package.json to "MIT") Please let us know about any special instructions for usage/testing. -2. We'll run the tests one last time. If there are any issues, we'll let you know. -3. When it's ready, we'll update the documentation with information about your new adapter -4. Then we'll tweet and post about it on our blog, adoring you with lavish praises. -5. Mike will send you jelly beans. - - -## About Sails.js and Waterline -http://SailsJs.com - -Waterline is a new kind of storage and retrieval engine for Sails.js. It provides a uniform API for accessing stuff from different kinds of databases, protocols, and 3rd party APIs. That means you write the same code to get users, whether they live in mySQL, LDAP, MongoDB, or Facebook. +Sails-Neo4j Adapter; Will update the readme very soon. +For now if you have any questions: +[![Gitter chat](https://badges.gitter.im/natgeo/sails-neo4j.png)](https://gitter.im/natgeo/sails-neo4j) From 6ffbfd23da582797623c0be1d055dfb9e1eb50f5 Mon Sep 17 00:00:00 2001 From: dughetti Date: Thu, 29 Jan 2015 10:16:39 -0300 Subject: [PATCH 55/57] Upgrade adapter for sails 0.10.5. Initial version --- lib/adapter.js | 89 +++++++++++++++++++++-------------------- lib/helpers/security.js | 4 +- test/create.js | 4 +- test/sanitize.js | 8 ++-- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 321a0b8..79930da 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -13,6 +13,7 @@ var adapter = module.exports = (function() { // Set to true if this adapter supports (or requires) things like data types, validations, keys, etc. // If true, the schema for models using this adapter will be automatically synced when the server starts. // Not terribly relevant if not using a non-SQL / non-schema-ed data store + var connections = {}; var syncable = false, connection, @@ -63,10 +64,12 @@ var adapter = module.exports = (function() { connectionSettings = {}; _.merge(connectionSettings, defaults, col); neo.connect(connectionSettings); - } - function registerCollection(collection, cb) { + function registerConnection(connection, collection, cb) { + if(!connection.identity) return cb(new Error('Connection is missing an identity.')); + if(connections[connection.identity]) return cb(new Error('Connection is already registered.')); + connections[connection.identity] = connection; createConnection(collection); cb(); } @@ -148,7 +151,7 @@ var adapter = module.exports = (function() { return '(' + query.join(' OR ') + ')'; } - function query(q, params, cb, unique) { + function query(connection, collection, q, params, cb, unique) { neo.graph(function(gr) { gr.query(q.join('\n'), params, function (err, results) { if (connectionSettings.debug) console.log(q.join('\n'), results); @@ -165,7 +168,7 @@ var adapter = module.exports = (function() { // results[i] = _.extend(id, data); // } // if (unique) cb(null, results[0]); - // else + // else cb(null, results); } }); @@ -184,99 +187,99 @@ var adapter = module.exports = (function() { getConnection: getConnection, sanitized: security.sanitized, query: query, - registerCollection: registerCollection, + registerConnection: registerConnection, createConnection: createConnection, - create: function(collectionName, params, cb) { + create: function(connection, collection, params, cb) { var q, delimiter = ''; - if (collectionName === null) { collectionName = ''; } // do we have a label? - else { collectionName = ':' + collectionName; } + if (collection === null) { collection = ''; } // do we have a label? + else { collection = ':' + collection; } - if (params !== null && collectionName !== null) { delimiter = ' AND '; } // do we have a label and params? + if (params !== null && collection !== null) { delimiter = ' AND '; } // do we have a label and params? q = [ - 'CREATE (n' + collectionName + ' { ' + parseOne(params) + ' })', + 'CREATE (n' + collection + ' { ' + parseOne(params) + ' })', 'RETURN n' ]; - query(q, params, cb, true); + query(connection, collection, q, params, cb, true); }, - createMany: function(collectionName, params, cb) { + createMany: function(connection, collection, params, cb) { var q, delimiter = ''; - if (collectionName === null) { collectionName = ''; } // do we have a label? - else { collectionName = ':' + collectionName; } + if (collection === null) { collection = ''; } // do we have a label? + else { collection = ':' + collection; } - if (params && collectionName) { delimiter = ' AND '; } // do we have a label and params? + if (params && collection) { delimiter = ' AND '; } // do we have a label and params? q = [ - 'CREATE (n' + collectionName + ' { ' + parseMany(params) + ' })', + 'CREATE (n' + collection + ' { ' + parseMany(params) + ' })', 'RETURN n' ]; - query(q, params, cb, false); + query(connection, collection, q, params, cb, false); }, - find: function(collectionName, params, cb) { + find: function(connection, collection, params, cb) { var q, delimiter = ''; - if (collectionName === null) { collectionName = ''; } // do we have a label? - else { collectionName = 'n:' + collectionName; } + if (collection === null) { collection = ''; } // do we have a label? + else { collection = 'n:' + collection; } - if (params.where && collectionName) { delimiter = ' AND '; } // do we have a label and params? + if (params.where && collection) { delimiter = ' AND '; } // do we have a label and params? q = [ 'MATCH (n)', - 'WHERE ' + collectionName + delimiter + toWhere('n', params), + 'WHERE ' + collection + delimiter + toWhere('n', params), 'RETURN n' ]; - query(q, params.where, cb, false); + query(connection, collection, q, params.where, cb, false); }, - update: function(collectionName, params, values, cb) { + update: function(connection, collection, params, values, cb) { var q, delimiter = ''; - if (collectionName === null) { collectionName = ''; } // do we have a label? - else { collectionName = 'n:' + collectionName; } + if (collection === null) { collection = ''; } // do we have a label? + else { collection = 'n:' + collection; } - if (params.where && collectionName) { delimiter = ' AND '; } // do we have a label and params? + if (params.where && collection) { delimiter = ' AND '; } // do we have a label and params? q = [ 'MATCH (n)', - 'WHERE ' + collectionName + delimiter + toWhere('n', params), + 'WHERE ' + collection + delimiter + toWhere('n', params), 'SET ' + andJoin('n', _.omit(values, 'id'), ','), 'RETURN n' ]; params.where = _.extend(params.where, values); - query(q, params.where, cb, true); + query(connection, collection, q, params.where, cb, true); }, - destroy: function(collectionName, params, cb) { + destroy: function(connection, collection, params, cb) { var q, delimiter = ''; - if (collectionName === null) { collectionName = ''; } // do we have a label? - else { collectionName = 'n:' + collectionName; } + if (collection === null) { collection = ''; } // do we have a label? + else { collection = 'n:' + collection; } - if (params.where && collectionName) { delimiter = ' AND '; } // do we have a label and params? + if (params.where && collection) { delimiter = ' AND '; } // do we have a label and params? q = [ 'MATCH (n)', - 'WHERE ' + collectionName + delimiter + toWhere('n', params), + 'WHERE ' + collection + delimiter + toWhere('n', params), 'DELETE n' ]; - query(q, params.where, cb, true); + query(connection, collection, q, params.where, cb, true); }, // REQUIRED method if users expect to call Model.stream() - stream: function(collectionName, options, stream) { + stream: function(connection, collection, options, stream) { // options is a standard criteria/options object (like in find) // stream.write() and stream.end() should be called. @@ -285,22 +288,22 @@ var adapter = module.exports = (function() { }, - link: function(collectionName, predecessorParams, successorCollectionName, successorParams, relationshipType, relationshipParams, cb) { + link: function(connection, collection, predecessorParams, successorCollectionName, successorParams, relationshipType, relationshipParams, cb) { var q, predecessorDelimiter = '', successorDelimiter = '', predecessorNamespace = 'pred', successorNamespace = 'succ'; - if (collectionName === null) { collectionName = ''; } // do we have a label? - else { collectionName = 'a:' + collectionName; } - if (predecessorParams && collectionName) { predecessorDelimiter = ' AND '; } + if (collection === null) { collection = ''; } // do we have a label? + else { collection = 'a:' + collection; } + if (predecessorParams && collection) { predecessorDelimiter = ' AND '; } if (successorCollectionName === null) { successorCollectionName = ''; } // do we have a label? else { successorCollectionName = 'b:' + successorCollectionName; } - if (successorParams && collectionName) { successorDelimiter = ' AND '; } + if (successorParams && collection) { successorDelimiter = ' AND '; } relationshipParams = _.isEmpty(relationshipParams) ? '' : ' ' + JSON.stringify(relationshipParams); q = [ 'MATCH (a),(b)', - 'WHERE ' + collectionName + predecessorDelimiter + toWhere('a', {where: predecessorParams}, predecessorNamespace) + 'WHERE ' + collection + predecessorDelimiter + toWhere('a', {where: predecessorParams}, predecessorNamespace) +' AND ' + successorCollectionName + successorDelimiter + toWhere('b', {where: successorParams}, successorNamespace), 'CREATE (a)-[n:' + relationshipType + relationshipParams + ']->(b)', 'RETURN n' @@ -316,7 +319,7 @@ var adapter = module.exports = (function() { params[key] = value; }); - query(q, params, cb, true); + query(connection, collection, q, params, cb, true); } diff --git a/lib/helpers/security.js b/lib/helpers/security.js index ec49fa7..4eac33a 100644 --- a/lib/helpers/security.js +++ b/lib/helpers/security.js @@ -37,12 +37,12 @@ module.exports = (function () { * @param {Object} params [An object which could have other objects inside of it] * @return {Boolean} truth [True if the object is "Sanitized", false if it isn't] */ - function sanitized(params) { + function sanitized(connection, collection, params) { var i, truth = true; for (i in params) { if (params.hasOwnProperty(i) && truth) { if (typeof params[i] === 'object') { - truth = sanitized(params[i]); + truth = sanitized(connection, collection, params[i]); } else { if(hasCypher(String(params[i]).toLowerCase())) { diff --git a/test/create.js b/test/create.js index fb865cc..e013c6f 100644 --- a/test/create.js +++ b/test/create.js @@ -5,7 +5,7 @@ describe('Creating Nodes', function () { it('should create one node with a property test = "1"', function (done) { var adapter = require('../lib/adapter.js'); adapter.createConnection(); - adapter.create(null, { test: 1 }, function(err, results) { + adapter.create(null, null, { test: 1 }, function(err, results) { if (err) { throw err; } nodes.push(results); done(); @@ -15,7 +15,7 @@ describe('Creating Nodes', function () { it('should create multiple nodes with the property test = "1"', function(done) { var adapter = require('../lib/adapter.js'); adapter.createConnection(); - adapter.createMany(null,{params:[{ test: 1 },{ test: 1 }]}, function(err, results) { + adapter.createMany(null, null,{params:[{ test: 1 },{ test: 1 }]}, function(err, results) { if (err) { throw err; } nodes.push(results); done(); diff --git a/test/sanitize.js b/test/sanitize.js index efcf02c..2194e18 100644 --- a/test/sanitize.js +++ b/test/sanitize.js @@ -6,7 +6,7 @@ describe('isCypher should return True or False if the string contains any cypher it('Should return True to params object that is cypher injection free', function () { var adapter = require('../lib/adapter.js'); params = {i: 'This is a test' }; - o = adapter.sanitized(params); + o = adapter.sanitized(null, null, params); assert.equal(o, true); }); @@ -14,21 +14,21 @@ describe('isCypher should return True or False if the string contains any cypher it('Should return False for params object with Cypher Keywords', function () { var adapter = require('../lib/adapter.js'); params = {i: '/app_model/12_MATCH/12/' }; - o = adapter.sanitized(params); + o = adapter.sanitized(null, null, params); assert.equal(o, false); }); it('Should return False for params object with multiple params and Cypher Keywords', function () { var adapter = require('../lib/adapter.js'); params = {i: '/app_model/COUNT/12/', x: 'something', t: { z: 'test', y: 'START n=node(1)'} }; - o = adapter.sanitized(params); + o = adapter.sanitized(null, null, params); assert.equal(o, false); }); it('Should return True for params object with multiple params and without Cypher Keywords', function () { var adapter = require('../lib/adapter.js'); params = {i: '/app_model/', x: 'something', t: { z: 'test', y: 'something2'} }; - o = adapter.sanitized(params); + o = adapter.sanitized(null, null, params); assert.equal(o, true); }); }); From 564b72f8fe60bf29578ca84727e97ee0f5945316 Mon Sep 17 00:00:00 2001 From: dughetti Date: Fri, 30 Jan 2015 15:50:23 -0300 Subject: [PATCH 56/57] Improved registerConnection --- lib/adapter.js | 22 ++++++++-------------- test/create.js | 20 +++++++++++++------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 79930da..c45bcfe 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -15,7 +15,6 @@ var adapter = module.exports = (function() { // Not terribly relevant if not using a non-SQL / non-schema-ed data store var connections = {}; var syncable = false, - connection, // Including a commitLog config enables transactions in this adapter // Please note that these are not ACID-compliant transactions: @@ -52,25 +51,20 @@ var adapter = module.exports = (function() { }; //Init - - function createConnection(collection) { - var col; - try { - col = collection.adapter.defaults; - - } catch(err) { - col = {}; - } + //Merge the default connection with the connection from sails app (config/connections.js). + function createConnection(connection) { + connection = connection || {}; connectionSettings = {}; - _.merge(connectionSettings, defaults, col); + _.merge(connectionSettings, defaults, connection); neo.connect(connectionSettings); + return neo; } + //Stored the neo4j connection on connections array and then it can be retrieved by identity name ("neo4j") function registerConnection(connection, collection, cb) { if(!connection.identity) return cb(new Error('Connection is missing an identity.')); if(connections[connection.identity]) return cb(new Error('Connection is already registered.')); - connections[connection.identity] = connection; - createConnection(collection); + connections[connection.identity] = createConnection(connection); cb(); } @@ -152,7 +146,7 @@ var adapter = module.exports = (function() { } function query(connection, collection, q, params, cb, unique) { - neo.graph(function(gr) { + connections[connection].graph(function(gr) { gr.query(q.join('\n'), params, function (err, results) { if (connectionSettings.debug) console.log(q.join('\n'), results); if (err) { diff --git a/test/create.js b/test/create.js index e013c6f..a7549c2 100644 --- a/test/create.js +++ b/test/create.js @@ -1,11 +1,19 @@ -var assert = require('assert'); +var assert = require('assert'), + adapter = require('../lib/adapter.js'); describe('Creating Nodes', function () { var nodes = []; + + before(function(done) { + var connection = { + identity: 'neo4j' + }; + adapter.registerConnection(connection,null,function(){}); + done(); + }); + it('should create one node with a property test = "1"', function (done) { - var adapter = require('../lib/adapter.js'); - adapter.createConnection(); - adapter.create(null, null, { test: 1 }, function(err, results) { + adapter.create("neo4j", null, { test: 1 }, function(err, results) { if (err) { throw err; } nodes.push(results); done(); @@ -13,9 +21,7 @@ describe('Creating Nodes', function () { }); it('should create multiple nodes with the property test = "1"', function(done) { - var adapter = require('../lib/adapter.js'); - adapter.createConnection(); - adapter.createMany(null, null,{params:[{ test: 1 },{ test: 1 }]}, function(err, results) { + adapter.createMany("neo4j", null,{params:[{ test: 1 },{ test: 1 }]}, function(err, results) { if (err) { throw err; } nodes.push(results); done(); From d4fc109ccd2d9514e88457889ed8c988d05b169d Mon Sep 17 00:00:00 2001 From: dughetti Date: Mon, 2 Feb 2015 13:32:45 -0300 Subject: [PATCH 57/57] Added travis file. Changed version. --- .travis.yml | 10 ++++++++++ ci/start_neo4j_server.sh | 12 ++++++++++++ package.json | 2 +- test/create.js | 3 +-- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 .travis.yml create mode 100755 ci/start_neo4j_server.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d7d1934 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: node_js +node_js: + - "0.10" +before_install: +- "./ci/start_neo4j_server.sh" +jdk: +- openjdk7 +branches: + only: + - master diff --git a/ci/start_neo4j_server.sh b/ci/start_neo4j_server.sh new file mode 100755 index 0000000..49b362c --- /dev/null +++ b/ci/start_neo4j_server.sh @@ -0,0 +1,12 @@ +VERSION="2.1.5" +TARBALL="neo4j$VERSION.tar.gz" + +cd /tmp + +wget -O $TARBALL "http://dist.neo4j.org/neo4j-community-$VERSION-unix.tar.gz?edition=community&version=$VERSION&distribution=tarball&dlid=2803678" +tar zxf $TARBALL + +cd "neo4j-community-$VERSION" + +./bin/neo4j start +sleep 3 diff --git a/package.json b/package.json index 31abbb3..8914e22 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sails-neo4j", - "version": "0.2.4", + "version": "0.3.0", "description": "Neo4j adapter for Sails.js", "main": "lib/adapter.js", "scripts": { diff --git a/test/create.js b/test/create.js index a7549c2..f9f7df0 100644 --- a/test/create.js +++ b/test/create.js @@ -8,8 +8,7 @@ describe('Creating Nodes', function () { var connection = { identity: 'neo4j' }; - adapter.registerConnection(connection,null,function(){}); - done(); + adapter.registerConnection(connection,null,done); }); it('should create one node with a property test = "1"', function (done) {