From a370680fe832531c29135d2ccbd2c2dbec80ebdf Mon Sep 17 00:00:00 2001 From: Ben Fonarov Date: Tue, 10 Dec 2013 09:26:50 -0500 Subject: [PATCH 01/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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