From 2ad6dd34d714da7eced92c30a20e49bfdab83920 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 17 Feb 2015 19:30:13 +0000 Subject: [PATCH 01/81] extend: Added method to extend a class with another class/object --- lib/utils.js | 56 ++++++++++++++++++ test/unit/utils.extend.test.js | 100 +++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 test/unit/utils.extend.test.js diff --git a/lib/utils.js b/lib/utils.js index 69f029d..939a6c1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -330,6 +330,62 @@ exports.getAttributeAsObject = function getAttributeAsObject(schema, columnName) return _.find(_.values(schema), { columnName: columnName }); }; +/** + * Extend + * + * Extends a class in a simple manner. + * + * @param {Object} parent + * @param {Object} [source] + * @return {Function} + */ +exports.extend = function extend(parent, source){ + source = source || {}; + var child; + + if (_.isFunction(source)) { + child = function () { return source.apply(this, arguments); }; + } + else if (source.hasOwnProperty('constructor')) { + child = source.constructor; + } + else { + child = function () { return parent.apply(this, arguments); }; + } + + // Inherit parent's prototype + child.prototype = _.create(parent.prototype, { 'constructor': child }); + + var keys, key, i, limit; + + // Inherit parent's properties + for (keys = Object.keys(parent), key = null, i = 0, limit = keys.length; i < limit; i++) { + key = keys[i]; + if (key !== 'prototype') { + child[key] = parent[key]; + } + } + + // Overwrite with source's properties + for (keys = Object.keys(source), key = null, i = 0, limit = keys.length; i < limit; i++) { + key = keys[i]; + if (key !== 'constructor' && key !== 'prototype' && source.hasOwnProperty(key)) { + child[key] = source[key]; + } + } + + // Overwrite with source's prototype properties + if(source.prototype){ + for (keys = Object.keys(source.prototype), key = null, i = 0, limit = keys.length; i < limit; i++) { + key = keys[i]; + if (key !== 'constructor') { + child.prototype[key] = source.prototype[key]; + } + } + } + + return child; +}; /* istanbul ignore next: debug code */ /** diff --git a/test/unit/utils.extend.test.js b/test/unit/utils.extend.test.js new file mode 100644 index 0000000..350ab37 --- /dev/null +++ b/test/unit/utils.extend.test.js @@ -0,0 +1,100 @@ +/** + * Test dependencies + */ +var assert = require('assert'), + utils = require('../../lib/utils'); + + +describe('utils helper class', function() { + + var Shape; + + before(function(done){ + Shape = function(val1, val2) { + this.x = val1; + this.y = val2; + }; + Shape.shapeProperty = 'shape'; + Shape.willBeOverriden = 'shape'; + Shape.prototype.protoShape = 'shape'; + Shape.prototype.protoOverride = 'shape'; + + done(); + }); + + describe('extend:', function() { + + it('should extend classes without source', function(done) { + var Extended = utils.extend(Shape); + var circle = new Extended(1, 2); + assert(circle instanceof Extended); + assert(circle instanceof Shape); + assert.equal(circle.y, 2); + done(); + }); + + it('should extend classes from object with constructor', function(done) { + var Extended = utils.extend(Shape, { constructor: function(val){ this.z = val; } }); + var circle = new Extended(1); + assert(circle instanceof Extended); + assert(circle instanceof Shape); + assert.equal(circle.z, 1); + done(); + }); + + it('should extend classes from object', function(done) { + var Circle = { + willBeOverriden: 'circle', + prototype: { + foo: function(){ return 'foo'; }, + protoOverride: 'circle' + } + }; + Circle.willBeOverriden = 'circle'; + Circle.prototype.foo = function(){ return 'foo'; }; + Circle.prototype.protoOverride = 'circle'; + + var Extended = utils.extend(Shape, Circle); + assert.equal(Extended.shapeProperty, 'shape'); + assert.equal(Extended.willBeOverriden, 'circle'); + + var circle = new Extended(1, 2); + assert(circle instanceof Extended); + assert(circle instanceof Shape); + + assert.equal(circle.y, 2); + assert.equal(circle.foo(), 'foo'); + assert.equal(circle.protoOverride, 'circle'); + + done(); + }); + + + it('should extend classes from function', function(done) { + function Circle() { + Shape.apply(this, arguments); + this.z = arguments[0]; + } + Circle.willBeOverriden = 'circle'; + Circle.prototype.foo = function(){ return 'foo'; }; + Circle.prototype.protoOverride = 'circle'; + + var Extended = utils.extend(Shape, Circle); + assert.equal(Extended.shapeProperty, 'shape'); + assert.equal(Extended.willBeOverriden, 'circle'); + + var circle = new Extended(1, 2); + assert(circle instanceof Extended); + assert(circle instanceof Shape); + + assert.equal(circle.y, 2); + assert.equal(circle.z, 1); + assert.equal(circle.foo(), 'foo'); + assert.equal(circle.protoOverride, 'circle'); + + done(); + }); + + }); + +}); From af28ec0cbe9205d7f962da288bca7ebccf7d4b5c Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 17 Feb 2015 19:38:32 +0000 Subject: [PATCH 02/81] Collection / Edge / Vertex placeholders, inspired in [sails-mongo collection](https://github.com/balderdashy/sails-mongo/blob/master/lib/collection.js) class --- lib/collection/edge.js | 13 ++++++ lib/collection/index.js | 87 ++++++++++++++++++++++++++++++++++++++++ lib/collection/vertex.js | 13 ++++++ 3 files changed, 113 insertions(+) create mode 100644 lib/collection/edge.js create mode 100644 lib/collection/index.js create mode 100644 lib/collection/vertex.js diff --git a/lib/collection/edge.js b/lib/collection/edge.js new file mode 100644 index 0000000..606fd05 --- /dev/null +++ b/lib/collection/edge.js @@ -0,0 +1,13 @@ +"use strict"; + +var utils = require('../utils'), + Collection = require('index'); + +/** + * Manage An Edge + * + * @param {Object} definition + * @param {Connection} connection + * @api public + */ +var Edge = module.exports = utils.extend(Collection); diff --git a/lib/collection/index.js b/lib/collection/index.js new file mode 100644 index 0000000..39bc153 --- /dev/null +++ b/lib/collection/index.js @@ -0,0 +1,87 @@ +"use strict"; + +/** + * Manage A Collection + * + * @param {Object} definition + * @param {Connection} connection + * @api public + */ +var Collection = module.exports = function Collection(definition, connection) { + + // Set an identity for this collection + this.identity = ''; + + // Set the orientdb class (tableName) for this collection + this.klass = ''; + + // Set the orientdb super class (document / vertex / edge) for this collection + this.superKlass = ''; + + // Hold Schema Information + this.schema = null; + + // Hold a reference to an active connection + this.connection = connection; + + // Hold Indexes + // this.indexes = []; + + // Parse the definition into collection attributes + this._parseDefinition(definition); + + // Build an indexes dictionary + //this._buildIndexes(); + + return this; +}; + +///////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +///////////////////////////////////////////////////////////////////////////////// + +/** + * Find Documents + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ +Collection.prototype.find = function find(criteria, cb) { + +}; + +/** + * Insert A New Document + * + * @param {Object|Array} values + * @param {Function} callback + * @api public + */ +Collection.prototype.insert = function insert(values, cb) { + +}; + +/** + * Update Documents + * + * @param {Object} criteria + * @param {Object} values + * @param {Function} callback + * @api public + */ +Collection.prototype.update = function update(criteria, values, cb) { + +}; + +/** + * Destroy Documents + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ +Collection.prototype.destroy = function destroy(criteria, cb) { + +}; + diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js new file mode 100644 index 0000000..b805951 --- /dev/null +++ b/lib/collection/vertex.js @@ -0,0 +1,13 @@ +"use strict"; + +var utils = require('../utils'), + Collection = require('index'); + +/** + * Manage A Vertex + * + * @param {Object} definition + * @param {Connection} connection + * @api public + */ +var Vertex = module.exports = utils.extend(Collection); From 5d99b0675be7b2fa98382231fbc237a7ac42f55a Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 18 Feb 2015 01:51:12 +0000 Subject: [PATCH 03/81] Issue #43: added test in attempt to reproduce OrientDB.RequestError on update --- .../43-orientdb_requestError.js | 52 +++++++++++++++ .../43-orientdb_requestError/image.fixture.js | 31 +++++++++ .../43-orientdb_requestError/post.fixture.js | 65 +++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js create mode 100644 test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js create mode 100644 test/integration-orientdb/bugs/43-orientdb_requestError/post.fixture.js diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js new file mode 100644 index 0000000..242b9f0 --- /dev/null +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js @@ -0,0 +1,52 @@ +var assert = require('assert'), + _ = require('lodash'), + utils = require('../../../../lib/utils'); + +var self = this; + +describe('Bug #43: OrientDB.RequestError on update', function() { + before(function (done) { + var fixtures = { + ImageFixture: require('./image.fixture'), + SubprofileFixture: require('./post.fixture') + }; + CREATE_TEST_WATERLINE(self, 'test_bug_43', fixtures, done); + }); + after(function (done) { + DELETE_TEST_WATERLINE('test_bug_43', done); + }); + + describe('update a created post', function() { + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var postRecord; + + before(function(done) { + self.collections.Post.create({ title: 'a post' }, function(err, post) { + if(err) { return done(err); } + postRecord = post; + done(); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should update a post', function(done) { + self.collections.Post.findOne(postRecord.id) + .then(function(post){ + + post.title = 'new title'; + + self.collections.Post.update({ id: post.id }, post, done); + }) + .catch(done); + }); + + + }); +}); diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js b/test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js new file mode 100644 index 0000000..144f5c5 --- /dev/null +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js @@ -0,0 +1,31 @@ +module.exports = { + + identity: 'image', + + attributes: { + file: { + type: 'json', + isFile: true, + required: true + }, + footer: { + type: 'string' + }, + // author: { + // model: 'author' + // }, + area: { + type: 'string' + }, + isCrop: { + type: 'boolean' + }, + parent: { + model: 'image' + }, + crops: { + collection: 'image', + via: 'parent' + } + } +}; \ No newline at end of file diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/post.fixture.js b/test/integration-orientdb/bugs/43-orientdb_requestError/post.fixture.js new file mode 100644 index 0000000..9eed020 --- /dev/null +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/post.fixture.js @@ -0,0 +1,65 @@ + +module.exports = { + + identity: 'post', + + attributes: { + title: { + type: 'string' + }, + slug: { + type: 'string' + }, + editorialPriority: { + type: 'string' + }, + sectionPriority: { + type: 'string' + }, + html: { + type: 'string' + }, + editor_html: { + type: 'string' + }, + featureImage: { + model: 'image' + }, + area: { + type: 'string' + }, + excerpt: { + type: 'string' + }, + content: { + type: 'string', + }, + publicationDate: { + type: 'datetime' + }, + // categories:{ + // collection: 'category', + // through: 'post_category', + // via: 'post', + // dominant: true + // }, + // author:{ + // model:'author' + // }, + // status:{ + // model:'postStatus' + // }, + address: { + type: 'string' + }, + addressReference: { + type: 'string' + }, + latitude: { + type: 'float' + }, + longitude: { + type: 'float' + } + } +}; \ No newline at end of file From 6a904da303c75de9eeb99f0a790d99ae1ad02a3e Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 18 Feb 2015 20:22:57 +0000 Subject: [PATCH 04/81] Extend: added anonymous function to the test --- test/unit/utils.extend.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/unit/utils.extend.test.js b/test/unit/utils.extend.test.js index 350ab37..ed4b91f 100644 --- a/test/unit/utils.extend.test.js +++ b/test/unit/utils.extend.test.js @@ -69,6 +69,21 @@ describe('utils helper class', function() { done(); }); + it('should extend classes from anonymous function', function(done) { + var Extended = utils.extend(Shape, function () { + Shape.apply(this, arguments); + this.z = arguments[0]; + }); + assert.equal(Extended.shapeProperty, 'shape'); + + var circle = new Extended(1, 2); + assert(circle instanceof Extended); + assert(circle instanceof Shape); + assert.equal(circle.y, 2); + assert.equal(circle.z, 1); + done(); + }); + it('should extend classes from function', function(done) { function Circle() { From f0f2124d256a5da9054dab69ae39daf8370eb178 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 18 Feb 2015 21:52:06 +0000 Subject: [PATCH 05/81] Refactor: registering collections according to its class --- lib/collection/document.js | 117 +++++++++++++++++++++++++++++++++++++ lib/collection/edge.js | 7 ++- lib/collection/index.js | 99 +++++-------------------------- lib/collection/vertex.js | 7 ++- lib/connection.js | 11 +++- 5 files changed, 153 insertions(+), 88 deletions(-) create mode 100644 lib/collection/document.js diff --git a/lib/collection/document.js b/lib/collection/document.js new file mode 100644 index 0000000..6c9e9c0 --- /dev/null +++ b/lib/collection/document.js @@ -0,0 +1,117 @@ +"use strict"; + +var _ = require('lodash'); + +/** + * Manage A Document + * + * @param {Object} definition + * @param {Connection} connection + * @api public + */ +var Document = module.exports = function Document(definition, connection) { + + // Set an identity for this document + this.identity = ''; + + // Set the orientdb super class (document / vertex / edge) for this document + this.superClass = ''; + + // Hold Schema Information + this.schema = null; + + // Hold a reference to an active connection + this.connection = connection; + + // Hold Indexes + // this.indexes = []; + + // Parse the definition into document attributes + this._parseDefinition(definition); + + // Build an indexes dictionary + //this._buildIndexes(); + + return this; +}; + +///////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +///////////////////////////////////////////////////////////////////////////////// + +/** + * Find Documents + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ +Document.prototype.find = function find(criteria, cb) { + +}; + +/** + * Insert A New Document + * + * @param {Object|Array} values + * @param {Function} callback + * @api public + */ +Document.prototype.insert = function insert(values, cb) { + +}; + +/** + * Update Documents + * + * @param {Object} criteria + * @param {Object} values + * @param {Function} callback + * @api public + */ +Document.prototype.update = function update(criteria, values, cb) { + +}; + +/** + * Destroy Documents + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ +Document.prototype.destroy = function destroy(criteria, cb) { + +}; + + + +///////////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS +///////////////////////////////////////////////////////////////////////////////// + + +/** + * Parse Document Definition + * + * @param {Object} definition + * @api private + */ + +Document.prototype._parseDefinition = function _parseDefinition(definition) { + var self = this, + collectionDef = _.cloneDeep(definition); + + // Hold the Schema + this.schema = collectionDef.definition; + + // TODO: not sure why sails-mongo does this... + // if (_.has(this.schema, 'id') && this.schema.id.primaryKey && this.schema.id.type === 'integer') { + // this.schema.id.type = 'objectid'; + // } + + // Set the identity + var ident = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); + this.identity = _.clone(ident); +}; + diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 606fd05..84b07a2 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -1,7 +1,7 @@ "use strict"; var utils = require('../utils'), - Collection = require('index'); + Document = require('./document'); /** * Manage An Edge @@ -10,4 +10,7 @@ var utils = require('../utils'), * @param {Connection} connection * @api public */ -var Edge = module.exports = utils.extend(Collection); +var Edge = module.exports = utils.extend(Document, function() { + Document.apply(this, arguments); + this.superClass = 'E'; +}); diff --git a/lib/collection/index.js b/lib/collection/index.js index 39bc153..b499f07 100644 --- a/lib/collection/index.js +++ b/lib/collection/index.js @@ -1,87 +1,20 @@ "use strict"; -/** - * Manage A Collection - * - * @param {Object} definition - * @param {Connection} connection - * @api public - */ -var Collection = module.exports = function Collection(definition, connection) { - - // Set an identity for this collection - this.identity = ''; - - // Set the orientdb class (tableName) for this collection - this.klass = ''; - - // Set the orientdb super class (document / vertex / edge) for this collection - this.superKlass = ''; - - // Hold Schema Information - this.schema = null; - - // Hold a reference to an active connection - this.connection = connection; - - // Hold Indexes - // this.indexes = []; - - // Parse the definition into collection attributes - this._parseDefinition(definition); - - // Build an indexes dictionary - //this._buildIndexes(); - - return this; -}; - -///////////////////////////////////////////////////////////////////////////////// -// PUBLIC METHODS -///////////////////////////////////////////////////////////////////////////////// - -/** - * Find Documents - * - * @param {Object} criteria - * @param {Function} callback - * @api public - */ -Collection.prototype.find = function find(criteria, cb) { - -}; - -/** - * Insert A New Document - * - * @param {Object|Array} values - * @param {Function} callback - * @api public - */ -Collection.prototype.insert = function insert(values, cb) { - -}; - -/** - * Update Documents - * - * @param {Object} criteria - * @param {Object} values - * @param {Function} callback - * @api public - */ -Collection.prototype.update = function update(criteria, values, cb) { - -}; - -/** - * Destroy Documents - * - * @param {Object} criteria - * @param {Function} callback - * @api public - */ -Collection.prototype.destroy = function destroy(criteria, cb) { - +var utils = require('../utils'); + +var Collection = module.exports = function Collection (definition, connection) { + if(connection.config.databaseType === 'document' || definition.orientdbClass === 'document'){ + return new Collection.Document(definition, connection); + } + + if(definition.orientdbClass === 'E' || + (utils.isJunctionTableThrough(definition) && definition.orientdbClass !== 'V')){ + return new Collection.Edge(definition, connection); + } + + return new Collection.Vertex(definition, connection); }; +Collection.Document = require('./document'); +Collection.Vertex = require('./vertex'); +Collection.Edge = require('./edge'); diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js index b805951..042e664 100644 --- a/lib/collection/vertex.js +++ b/lib/collection/vertex.js @@ -1,7 +1,7 @@ "use strict"; var utils = require('../utils'), - Collection = require('index'); + Document = require('./document'); /** * Manage A Vertex @@ -10,4 +10,7 @@ var utils = require('../utils'), * @param {Connection} connection * @api public */ -var Vertex = module.exports = utils.extend(Collection); +var Vertex = module.exports = utils.extend(Document, function() { + Document.apply(this, arguments); + this.superClass = 'V'; +}); diff --git a/lib/connection.js b/lib/connection.js index 06f8958..3a116a4 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -8,7 +8,8 @@ var Oriento = require('oriento'), Query = require('./query'), Document = require('./document'), Associations = require('./associations'), - log = require('debug-logger')('waterline-orientdb:connection'); + log = require('debug-logger')('waterline-orientdb:connection'), + Collection = require('./collection'); module.exports = (function () { @@ -42,6 +43,14 @@ module.exports = (function () { this.config = _.merge(auxDefaults, config); this.associations = new Associations(this); this.server = server; + + // Build up a registry of collections + var context = this; + this.newCollections = {}; // TODO: replace this.collections + Object.keys(collections).forEach(function(key) { + context.newCollections[key] = new Collection(collections[key], context); + console.log(context.newCollections[key].identity + ': ' + context.newCollections[key].superClass); + }); }, ensureDB = function (connectionProps) { var dbProps = (typeof connectionProps.database === 'object') ? connectionProps.database : { name: connectionProps.database }; From fbf390c6b515ab2a95065769dd0946c7465e77c8 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 18 Feb 2015 22:41:54 +0000 Subject: [PATCH 06/81] Moved .find() from connection to document --- lib/collection/document.js | 67 ++++++++++++++++++++++++++++++++++++-- lib/connection.js | 56 +------------------------------ 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index 6c9e9c0..a8f6dcb 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -1,6 +1,10 @@ "use strict"; -var _ = require('lodash'); +var _ = require('lodash'), + utils = require('../utils'), + Query = require('../query'), + Document = require('../document'), + log = require('debug-logger')('waterline-orientdb:document'); /** * Manage A Document @@ -19,6 +23,9 @@ var Document = module.exports = function Document(definition, connection) { // Hold Schema Information this.schema = null; + + // Hold the waterline schema, used by query (for now) + this.waterlineSchema = null; // Hold a reference to an active connection this.connection = connection; @@ -47,7 +54,61 @@ var Document = module.exports = function Document(definition, connection) { * @api public */ Document.prototype.find = function find(criteria, cb) { - + var self = this; + var _query, query; + + console.log('find, self.identity: ' + self.identity); + + try { + _query = new Query(criteria, self.waterlineSchema, self.connection.config); + } catch(err) { return cb(err); } + + var edge; + if (self.superClass === 'E') { + edge = self.connection.associations.getEdge(self.identity, criteria.where); + } + if (edge) { + // Replace foreign keys with from and to + if(edge.from) { _query.criteria.where.out = edge.from; } + if(edge.to) { _query.criteria.where.in = edge.to; } + if(_query.criteria.where){ + edge.keys.forEach(function(refKey) { delete _query.criteria.where[refKey]; }); + } + } + + try { + query = _query.getSelectQuery(self.identity); + } catch(e) { + log.error('Failed to compose find SQL query.', e); + return cb(e); + } + + log.debug('OrientDB query:', query.query[0]); + + var opts = { params: query.params || {} }; + if(query.params){ + log.debug('params:', opts); + } + if(criteria.fetchPlan){ + opts.fetchPlan = criteria.fetchPlan.where; + log.debug('opts.fetchPlan:', opts.fetchPlan); + } + + self.connection.db + .query(query.query[0], opts) + .all() + .then(function (res) { + if (res && criteria.fetchPlan) { + //log.debug('res', res); + cb(null, utils.rewriteIdsRecursive(res, self.schema)); + } else { + cb(null, utils.rewriteIds(res, self.schema)); + } + }) + .error(function (e) { + log.error('Failed to query the DB.', e); + cb(e); + }); }; /** @@ -113,5 +174,7 @@ Document.prototype._parseDefinition = function _parseDefinition(definition) { // Set the identity var ident = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); this.identity = _.clone(ident); + + this.waterlineSchema = definition.waterline.schema; }; diff --git a/lib/connection.js b/lib/connection.js index 3a116a4..634c488 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -430,61 +430,7 @@ module.exports = (function () { * Retrieves records of class collection that fulfill the criteria in options */ DbHelper.prototype.find = function(collection, options, cb) { - var collectionInstance = this.collections[collection]; - var schema = collectionInstance.waterline.schema; - var attributes = collectionInstance.attributes; - var _query, query; - - try { - _query = new Query(options, schema, this.config); - } catch(err) { return cb(err); } - - var edge; - if (utils.isJunctionTableThrough(collectionInstance)) { - edge = this.associations.getEdge(collection, options.where); - } - if (edge) { - // Replace foreign keys with from and to - if(edge.from) { _query.criteria.where.out = edge.from; } - if(edge.to) { _query.criteria.where.in = edge.to; } - if(_query.criteria.where){ - edge.keys.forEach(function(refKey) { delete _query.criteria.where[refKey]; }); - } - } - - try { - query = _query.getSelectQuery(collection); - } catch(e) { - log.error('Failed to compose find SQL query.', e); - return cb(e); - } - - log.debug('OrientDB query:', query.query[0]); - - var opts = { params: query.params || {} }; - if(query.params){ - log.debug('params:', opts); - } - if(options.fetchPlan){ - opts.fetchPlan = options.fetchPlan.where; - log.debug('opts.fetchPlan:', opts.fetchPlan); - } - - this.db - .query(query.query[0], opts) - .all() - .then(function (res) { - if (res && options.fetchPlan) { - //log.debug('res', res); - cb(null, utils.rewriteIdsRecursive(res, attributes)); - } else { - cb(null, utils.rewriteIds(res, attributes)); - } - }) - .error(function (e) { - log.error('Failed to query the DB.', e); - cb(e); - }); + this.newCollections[collection].find(options, cb); }; From 57ac646a2ddb120192a4b8b622af291ba2e73f37 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 18 Feb 2015 23:30:17 +0000 Subject: [PATCH 07/81] Moved connection.create() to document.insert() --- lib/collection/document.js | 37 ++++++++++++++++++++++++++----- lib/connection.js | 45 +------------------------------------- 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index a8f6dcb..e615090 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -3,7 +3,7 @@ var _ = require('lodash'), utils = require('../utils'), Query = require('../query'), - Document = require('../document'), + Doc = require('../document'), log = require('debug-logger')('waterline-orientdb:document'); /** @@ -57,8 +57,6 @@ Document.prototype.find = function find(criteria, cb) { var self = this; var _query, query; - console.log('find, self.identity: ' + self.identity); - try { _query = new Query(criteria, self.waterlineSchema, self.connection.config); } catch(err) { return cb(err); } @@ -114,12 +112,39 @@ Document.prototype.find = function find(criteria, cb) { /** * Insert A New Document * - * @param {Object|Array} values - * @param {Function} callback + * @param {Object|Array} values + * @param {Function} callback * @api public */ Document.prototype.insert = function insert(values, cb) { - + var self = this, + _document; + + _document = new Doc(values, self.schema, self.connection); + + var edge; + if (self.superClass === 'E'){ + edge = self.connection.associations.getEdge(self.identity, _document.values); + } + + if (edge) { + // Create edge + _document.values['@class'] = self.identity; + edge.keys.forEach(function(refKey) { delete _document.values[refKey]; }); + return self.connection.createEdge(edge.from, edge.to, _document.values, cb); + } + + self.connection.db.insert() + .into(self.identity) + .set(_document.values) + .one() + .then(function(res) { + cb(null, utils.rewriteIds(res, self.schema)); + }) + .error(function(err) { + log.error('Failed to create object. DB error.', err); + cb(err); + }); }; /** diff --git a/lib/connection.js b/lib/connection.js index 634c488..336aa20 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -49,7 +49,6 @@ module.exports = (function () { this.newCollections = {}; // TODO: replace this.collections Object.keys(collections).forEach(function(key) { context.newCollections[key] = new Collection(collections[key], context); - console.log(context.newCollections[key].identity + ': ' + context.newCollections[key].superClass); }); }, ensureDB = function (connectionProps) { @@ -451,49 +450,7 @@ module.exports = (function () { * Creates a new document from a collection */ DbHelper.prototype.create = function(collection, options, cb) { - var attributes, _document, collectionInstance, - self = this; - - collectionInstance = self.collections[collection]; - attributes = collectionInstance.attributes; - - _document = new Document(options, attributes, self); - - return self.dbCreate(collection, _document.values, cb); - }; - - - /** - * Calls Oriento to save a new document - */ - DbHelper.prototype.dbCreate = function(collection, options, cb) { - var collectionInstance = this.collections[collection]; - var attributes = collectionInstance.attributes; - - var edge; - if (utils.isJunctionTableThrough(collectionInstance)){ - edge = this.associations.getEdge(collection, options); - } - - if (edge) { - // Create edge - options['@class'] = collection; - edge.keys.forEach(function(refKey) { delete options[refKey]; }); - return this.createEdge(edge.from, edge.to, options, cb); - } - - this.db.insert() - .into(collection) - .set(options) - .transform(transformers) - .one() - .then(function(res) { - cb(null, utils.rewriteIds(res, attributes)); - }) - .error(function(err) { - log.error('Failed to create object. DB error.', err); - cb(err); - }); + this.newCollections[collection].insert(options, cb); }; From 26645bd3eb8b5864c71856773ce02cc414e33f03 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 18 Feb 2015 23:42:06 +0000 Subject: [PATCH 08/81] Moved update and destroy from connection to collection --- lib/collection/document.js | 60 +++++++++++++++++++++++++++++++++- lib/connection.js | 67 ++------------------------------------ 2 files changed, 61 insertions(+), 66 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index e615090..9edb39c 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -156,7 +156,41 @@ Document.prototype.insert = function insert(values, cb) { * @api public */ Document.prototype.update = function update(criteria, values, cb) { + var _query, + _document, + where, + self = this; + // Catch errors from building query and return to the callback + try { + _query = new Query(criteria, self.waterlineSchema, self.connection.config); + _document = new Doc(values, self.schema, self.connection); + where = _query.getWhereQuery(self.identity); + } catch(e) { + log.error('Failed to compose update SQL query.', e); + return cb(e); + } + + var query = self.connection.db.update(self.identity) + .set(_document.values) + .return('AFTER'); + + if(where.query[0]){ + query.where(where.query[0]); + if(where.params){ + query.addParams(where.params); + } + } + + query + .all() + .then(function(res) { + cb(null, utils.rewriteIds(res, self.schema)); + }) + .error(function(err) { + log.error('Failed to update, error:', err); + cb(err); + }); }; /** @@ -167,7 +201,31 @@ Document.prototype.update = function update(criteria, values, cb) { * @api public */ Document.prototype.destroy = function destroy(criteria, cb) { - + var self = this; + var klassToDelete = self.superClass === 'E' ? 'EDGE' : 'VERTEX'; + cb = cb || _.noop; + + // TODO: should be in a transaction + self.connection.find(self.identity, criteria, function(err, results){ + if(err){ return cb(err); } + + if(results.length === 0){ return cb(null, results); } + + var rids = _.pluck(results, 'id'); + log.debug('deleting rids: ' + rids); + + self.connection.db.delete(klassToDelete) + .where('@rid in [' + rids.join(',') + ']') + .one() + .then(function (count) { + if(parseInt(count) !== rids.length){ + return cb(new Error('Deleted count [' + count + '] does not match rids length [' + rids.length + + '], some vertices may not have been deleted')); + } + cb(null, results); + }) + .error(cb); + }); }; diff --git a/lib/connection.js b/lib/connection.js index 336aa20..cd3022f 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -458,45 +458,7 @@ module.exports = (function () { * Updates a document from a collection */ DbHelper.prototype.update = function(collection, options, values, cb) { - var _query, - _document, - where, - self = this; - var collectionInstance = this.collections[collection]; - var schema = collectionInstance.waterline.schema; - var attributes = collectionInstance.attributes; - - // Catch errors from building query and return to the callback - try { - _query = new Query(options, schema, self.config); - _document = new Document(values, attributes, self); - where = _query.getWhereQuery(collection); - } catch(e) { - log.error('Failed to compose update SQL query.', e); - return cb(e); - } - - var query = this.db.update(collection) - .set(_document.values) - .transform(transformers) - .return('AFTER'); - - if(where.query[0]){ - query.where(where.query[0]); - if(where.params){ - query.addParams(where.params); - } - } - - query - .all() - .then(function(res) { - cb(null, utils.rewriteIds(res, attributes)); - }) - .error(function(err) { - log.error('Failed to update, error:', err); - cb(err); - }); + this.newCollections[collection].update(options, values, cb); }; @@ -504,32 +466,7 @@ module.exports = (function () { * Deletes a document from a collection */ DbHelper.prototype.destroy = function(collection, options, cb) { - var collectionInstance = this.collections[collection]; - var klassToDelete = utils.isJunctionTableThrough(collectionInstance) ? 'EDGE' : 'VERTEX'; - var self = this; - cb = cb || _.noop; - - // TODO: should be in a transaction - self.find(collection, options, function(err, results){ - if(err){ return cb(err); } - - if(results.length === 0){ return cb(null, results); } - - var rids = _.pluck(results, 'id'); - log.info('deleting rids: ' + rids); - - self.db.delete(klassToDelete) - .where('@rid in [' + rids.join(',') + ']') - .one() - .then(function (count) { - if(parseInt(count) !== rids.length){ - return cb(new Error('Deleted count [' + count + '] does not match rids length [' + rids.length + - '], some vertices may not have been deleted')); - } - cb(null, results); - }) - .error(cb); - }); + this.newCollections[collection].destroy(options, cb); }; From 1ca33e7d5d078fbc3bdda98d6f50a10ee5b5b804 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 18 Feb 2015 23:46:16 +0000 Subject: [PATCH 09/81] query.getSelectQuery: small performance improvement --- lib/collection/document.js | 2 +- lib/query.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index 9edb39c..41d651e 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -75,7 +75,7 @@ Document.prototype.find = function find(criteria, cb) { } try { - query = _query.getSelectQuery(self.identity); + query = _query.getSelectQuery(self.identity, self.schema); } catch(e) { log.error('Failed to compose find SQL query.', e); return cb(e); diff --git a/lib/query.js b/lib/query.js index 97b4f8a..54ce069 100644 --- a/lib/query.js +++ b/lib/query.js @@ -191,15 +191,14 @@ Query.prototype.processIdValue = function processIdValue(idValue) { * @param {String} collection * @returns {Object} */ -Query.prototype.getSelectQuery = function getSelectQuery(collection) { +Query.prototype.getSelectQuery = function getSelectQuery(collection, attributes) { var self = this; - var currentTable = _.find(_.values(self.schema), { tableName: collection }).identity; var _query = self.sequel.find(collection, self.criteria); _query.query[0] = _query.query[0].replace(collection.toUpperCase(), collection); _query.params = _.reduce(_query.values[0], function(accumulator, value, index){ var key = _query.keys[0][index]; - var attribute = utils.getAttributeAsObject(self.schema[currentTable].attributes, key) || {}; + var attribute = utils.getAttributeAsObject(attributes, key) || {}; var foreignKeyOrId = key === '@rid' || key && key.indexOf('.@rid') !== -1 || attribute.foreignKey || attribute.model || false; var paramValue = foreignKeyOrId && _.isString(value) && utils.matchRecordId(value) ? new RID(value) : value; From cfc659a5b0d83fadeaa820a7c8ba1d7b46fc20df Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 00:36:17 +0000 Subject: [PATCH 10/81] Extend: added $super to child in order to access parent's methods --- lib/utils.js | 2 ++ test/unit/utils.extend.test.js | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/lib/utils.js b/lib/utils.js index dc56a14..e7d49e7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -384,6 +384,8 @@ exports.extend = function extend(parent, source){ } } + child.prototype.$super = parent; + return child; }; diff --git a/test/unit/utils.extend.test.js b/test/unit/utils.extend.test.js index ed4b91f..ac5fcb0 100644 --- a/test/unit/utils.extend.test.js +++ b/test/unit/utils.extend.test.js @@ -18,6 +18,9 @@ describe('utils helper class', function() { Shape.willBeOverriden = 'shape'; Shape.prototype.protoShape = 'shape'; Shape.prototype.protoOverride = 'shape'; + Shape.prototype.overrideMethod = function(){ + return 'shape'; + }; done(); }); @@ -93,6 +96,9 @@ describe('utils helper class', function() { Circle.willBeOverriden = 'circle'; Circle.prototype.foo = function(){ return 'foo'; }; Circle.prototype.protoOverride = 'circle'; + Circle.prototype.overrideMethod = function(){ + return this.$super.prototype.overrideMethod.call(this) + ' is Circle'; + }; var Extended = utils.extend(Shape, Circle); assert.equal(Extended.shapeProperty, 'shape'); @@ -106,6 +112,7 @@ describe('utils helper class', function() { assert.equal(circle.z, 1); assert.equal(circle.foo(), 'foo'); assert.equal(circle.protoOverride, 'circle'); + assert.equal(circle.overrideMethod(), 'shape is Circle'); done(); }); From 8906b9e42f80ee9e3da3fc9defa97b2a7c6f2a8b Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 00:36:58 +0000 Subject: [PATCH 11/81] Moved edge related code from document.js to edge.js --- lib/collection/document.js | 35 ++++------------------- lib/collection/edge.js | 57 ++++++++++++++++++++++++++++++++++++++ lib/collection/vertex.js | 1 + 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index 41d651e..3cf3f7c 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -18,13 +18,16 @@ var Document = module.exports = function Document(definition, connection) { // Set an identity for this document this.identity = ''; - // Set the orientdb super class (document / vertex / edge) for this document + // Set the orientdb super class ('document' / V / E) for this document this.superClass = ''; + + // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) + this.classCommand; // Hold Schema Information this.schema = null; - // Hold the waterline schema, used by query (for now) + // Hold the waterline schema, used by query namely waterline-sequel-orientdb this.waterlineSchema = null; // Hold a reference to an active connection @@ -61,19 +64,6 @@ Document.prototype.find = function find(criteria, cb) { _query = new Query(criteria, self.waterlineSchema, self.connection.config); } catch(err) { return cb(err); } - var edge; - if (self.superClass === 'E') { - edge = self.connection.associations.getEdge(self.identity, criteria.where); - } - if (edge) { - // Replace foreign keys with from and to - if(edge.from) { _query.criteria.where.out = edge.from; } - if(edge.to) { _query.criteria.where.in = edge.to; } - if(_query.criteria.where){ - edge.keys.forEach(function(refKey) { delete _query.criteria.where[refKey]; }); - } - } - try { query = _query.getSelectQuery(self.identity, self.schema); } catch(e) { @@ -122,18 +112,6 @@ Document.prototype.insert = function insert(values, cb) { _document = new Doc(values, self.schema, self.connection); - var edge; - if (self.superClass === 'E'){ - edge = self.connection.associations.getEdge(self.identity, _document.values); - } - - if (edge) { - // Create edge - _document.values['@class'] = self.identity; - edge.keys.forEach(function(refKey) { delete _document.values[refKey]; }); - return self.connection.createEdge(edge.from, edge.to, _document.values, cb); - } - self.connection.db.insert() .into(self.identity) .set(_document.values) @@ -202,7 +180,6 @@ Document.prototype.update = function update(criteria, values, cb) { */ Document.prototype.destroy = function destroy(criteria, cb) { var self = this; - var klassToDelete = self.superClass === 'E' ? 'EDGE' : 'VERTEX'; cb = cb || _.noop; // TODO: should be in a transaction @@ -214,7 +191,7 @@ Document.prototype.destroy = function destroy(criteria, cb) { var rids = _.pluck(results, 'id'); log.debug('deleting rids: ' + rids); - self.connection.db.delete(klassToDelete) + self.connection.db.delete(self.classCommand) .where('@rid in [' + rids.join(',') + ']') .one() .then(function (count) { diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 84b07a2..31954bd 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -13,4 +13,61 @@ var utils = require('../utils'), var Edge = module.exports = utils.extend(Document, function() { Document.apply(this, arguments); this.superClass = 'E'; + this.classCommand = 'EDGE'; }); + + +///////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +///////////////////////////////////////////////////////////////////////////////// + +/** + * Find Edges + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ +Edge.prototype.find = function find(criteria, cb) { + var self = this; + + var edge = self.connection.associations.getEdge(self.identity, criteria.where); + + if (edge) { + // Replace foreign keys with from and to + if(edge.from) { criteria.where.out = edge.from; } + if(edge.to) { criteria.where.in = edge.to; } + if(criteria.where){ + edge.keys.forEach(function(refKey) { delete criteria.where[refKey]; }); + } + } + + self.$super.prototype.find.call(self, criteria, cb); +}; + + + +/** + * Insert A New Edge + * + * @param {Object|Array} values + * @param {Function} callback + * @api public + */ +Edge.prototype.insert = function insert(values, cb) { + var self = this; + + var edge = self.connection.associations.getEdge(self.identity, values); + + if (edge) { + // Create edge + values['@class'] = self.identity; + edge.keys.forEach(function(refKey) { delete values[refKey]; }); + return self.connection.createEdge(edge.from, edge.to, values, cb); + } + + // creating an edge without connecting it, probably not useful + self.$super.prototype.insert.call(self, values, cb); +}; + + diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js index 042e664..e4d29bb 100644 --- a/lib/collection/vertex.js +++ b/lib/collection/vertex.js @@ -13,4 +13,5 @@ var utils = require('../utils'), var Vertex = module.exports = utils.extend(Document, function() { Document.apply(this, arguments); this.superClass = 'V'; + this.classCommand = 'VERTEX'; }); From 27338810c45945f576100297c9d5e7b27e27f9d9 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 01:55:17 +0000 Subject: [PATCH 12/81] Document / Edge: renamed identity to tableName --- lib/collection/document.js | 20 ++++++++++---------- lib/collection/edge.js | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index 3cf3f7c..d68bdf5 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -15,14 +15,14 @@ var _ = require('lodash'), */ var Document = module.exports = function Document(definition, connection) { - // Set an identity for this document - this.identity = ''; + // Set a tableName for this document + this.tableName = ''; // Set the orientdb super class ('document' / V / E) for this document this.superClass = ''; // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) - this.classCommand; + this.classCommand = undefined; // Hold Schema Information this.schema = null; @@ -65,7 +65,7 @@ Document.prototype.find = function find(criteria, cb) { } catch(err) { return cb(err); } try { - query = _query.getSelectQuery(self.identity, self.schema); + query = _query.getSelectQuery(self.tableName, self.schema); } catch(e) { log.error('Failed to compose find SQL query.', e); return cb(e); @@ -113,7 +113,7 @@ Document.prototype.insert = function insert(values, cb) { _document = new Doc(values, self.schema, self.connection); self.connection.db.insert() - .into(self.identity) + .into(self.tableName) .set(_document.values) .one() .then(function(res) { @@ -143,13 +143,13 @@ Document.prototype.update = function update(criteria, values, cb) { try { _query = new Query(criteria, self.waterlineSchema, self.connection.config); _document = new Doc(values, self.schema, self.connection); - where = _query.getWhereQuery(self.identity); + where = _query.getWhereQuery(self.tableName); } catch(e) { log.error('Failed to compose update SQL query.', e); return cb(e); } - var query = self.connection.db.update(self.identity) + var query = self.connection.db.update(self.tableName) .set(_document.values) .return('AFTER'); @@ -183,7 +183,7 @@ Document.prototype.destroy = function destroy(criteria, cb) { cb = cb || _.noop; // TODO: should be in a transaction - self.connection.find(self.identity, criteria, function(err, results){ + self.connection.find(self.tableName, criteria, function(err, results){ if(err){ return cb(err); } if(results.length === 0){ return cb(null, results); } @@ -232,8 +232,8 @@ Document.prototype._parseDefinition = function _parseDefinition(definition) { // } // Set the identity - var ident = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); - this.identity = _.clone(ident); + var tabl = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); + this.tableName = _.clone(tabl); this.waterlineSchema = definition.waterline.schema; }; diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 31954bd..a3fd081 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -31,7 +31,7 @@ var Edge = module.exports = utils.extend(Document, function() { Edge.prototype.find = function find(criteria, cb) { var self = this; - var edge = self.connection.associations.getEdge(self.identity, criteria.where); + var edge = self.connection.associations.getEdge(self.tableName, criteria.where); if (edge) { // Replace foreign keys with from and to @@ -57,11 +57,11 @@ Edge.prototype.find = function find(criteria, cb) { Edge.prototype.insert = function insert(values, cb) { var self = this; - var edge = self.connection.associations.getEdge(self.identity, values); + var edge = self.connection.associations.getEdge(self.tableName, values); if (edge) { // Create edge - values['@class'] = self.identity; + values['@class'] = self.tableName; edge.keys.forEach(function(refKey) { delete values[refKey]; }); return self.connection.createEdge(edge.from, edge.to, values, cb); } From bb526576675f04fdb13636e251f30e00a6be3f1f Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 02:18:01 +0000 Subject: [PATCH 13/81] Moved getEdgeSides from Associations to Edge, this way it's only performed once at startup --- lib/associations.js | 80 +------------------------ lib/collection/document.js | 12 +++- lib/collection/edge.js | 104 ++++++++++++++++++++++++++++++++- test/unit/associations.test.js | 26 +++++++-- 4 files changed, 136 insertions(+), 86 deletions(-) diff --git a/lib/associations.js b/lib/associations.js index d8ffee0..7bf39ed 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -357,83 +357,9 @@ Associations.prototype.genericJoin = function genericJoin(collectionName, criter * @api private */ Associations.prototype.getEdgeSides = function getEdgeSides(collectionName) { - var self = this, - collection = this.connection.collections[collectionName], - schema = collection.attributes, - identity = collection.identity || collection.tableName, - vertexA, - vertexB; - - Object.keys(schema).forEach(function(key) { - var reference = schema[key].model || schema[key].references; - if(!reference) - return; - - var referencedCollection = self.connection.collectionsByIdentity[reference]; - var referencedSchema = referencedCollection.attributes; - - var referencedAttributeKey; - Object.keys(referencedSchema).forEach(function(referencedSchemaKey) { - var attribute = referencedSchema[referencedSchemaKey]; - if(attribute.through === identity && attribute.via === key) - referencedAttributeKey = referencedSchemaKey; - }); - if(!referencedAttributeKey){ - Object.keys(referencedSchema).forEach(function(referencedSchemaKey) { - var attribute = referencedSchema[referencedSchemaKey]; - // Optimistic attribute assignment... - if(attribute.through === identity) - referencedAttributeKey = referencedSchemaKey; - }); - } - if(!referencedAttributeKey) - return; - - var referencedAttribute = referencedSchema[referencedAttributeKey]; - - var vertex = { - referencedCollectionName: reference, - referencedCollectionTableName: referencedCollection.tableName || reference, - referencedAttributeKey: referencedAttributeKey, - referencedAttributeColumnName: referencedAttribute.columnName || referencedAttributeKey, - dominant: referencedAttribute.dominant, - junctionTableKey: key, - junctionTableColumnName: schema[key].columnName || key - }; - - if(!vertexA) - vertexA = vertex; - else if(!vertexB) - vertexB = vertex; - else - log.error('Too many associations! Unable to process model [' + identity + '] attribute [' + key + '].'); - }); - - if(!vertexA.dominant && !vertexB.dominant){ - var dominantVertex = (vertexA.junctionTableKey < vertexB.junctionTableKey) ? vertexA : vertexB; - dominantVertex.dominant = true; - - log.warn(collectionName + ' junction table associations [' + vertexA.referencedCollectionName + - ', ' + vertexB.referencedCollectionName + '] have no dominant through association. ' + - dominantVertex.junctionTableKey + - ' was chosen as dominant.'); - } - - if(vertexA.dominant){ - vertexA.referencedAttributeEdge = 'out_' + collectionName; - vertexA.edgeOppositeEnd = 'in'; - vertexB.referencedAttributeEdge = 'in_' + collectionName; - vertexB.edgeOppositeEnd = 'out'; - return { out: vertexA, in: vertexB }; - } - - if(vertexB.dominant){ - vertexA.referencedAttributeEdge = 'in_' + collectionName; - vertexA.edgeOppositeEnd = 'out'; - vertexB.referencedAttributeEdge = 'out_' + collectionName; - vertexB.edgeOppositeEnd = 'in'; - return { out: vertexB, in: vertexA }; - } + console.log(collectionName); + var edge = this.connection.newCollections[collectionName]; + return edge.edgeSides; }; diff --git a/lib/collection/document.js b/lib/collection/document.js index d68bdf5..b010a46 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -17,6 +17,9 @@ var Document = module.exports = function Document(definition, connection) { // Set a tableName for this document this.tableName = ''; + + // Set an identity for this document + this.identity = ''; // Set the orientdb super class ('document' / V / E) for this document this.superClass = ''; @@ -220,8 +223,7 @@ Document.prototype.destroy = function destroy(criteria, cb) { */ Document.prototype._parseDefinition = function _parseDefinition(definition) { - var self = this, - collectionDef = _.cloneDeep(definition); + var collectionDef = _.cloneDeep(definition); // Hold the Schema this.schema = collectionDef.definition; @@ -231,10 +233,14 @@ Document.prototype._parseDefinition = function _parseDefinition(definition) { // this.schema.id.type = 'objectid'; // } - // Set the identity + // Set the tableName var tabl = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); this.tableName = _.clone(tabl); + // Set the identity + var ident = definition.identity ? definition.identity : definition.tableName; + this.identity = _.clone(ident); + this.waterlineSchema = definition.waterline.schema; }; diff --git a/lib/collection/edge.js b/lib/collection/edge.js index a3fd081..40e0c3e 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -1,7 +1,8 @@ "use strict"; var utils = require('../utils'), - Document = require('./document'); + Document = require('./document'), + log = require('debug-logger')('waterline-orientdb:edge'); /** * Manage An Edge @@ -12,8 +13,17 @@ var utils = require('../utils'), */ var Edge = module.exports = utils.extend(Document, function() { Document.apply(this, arguments); + + // Set the orientdb super class ('document' / V / E) for this document this.superClass = 'E'; + + // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) this.classCommand = 'EDGE'; + + // Information about edge's in and out properties + this.edgeSides = null; + + this._getEdgeSides(); }); @@ -71,3 +81,95 @@ Edge.prototype.insert = function insert(values, cb) { }; + +///////////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS +///////////////////////////////////////////////////////////////////////////////// + +/** + * Get edge sides + * + * Returns and object describing the out and the in sides of the edge + * + * @param {Object} collectionName + * @return {Object} + * @api private + */ +Edge.prototype._getEdgeSides = function _getEdgeSides() { + var self = this, + vertexA, + vertexB; + + Object.keys(self.schema).forEach(function(key) { + var reference = self.schema[key].model || self.schema[key].references; + if(!reference) + return; + + var referencedCollection = self.connection.collectionsByIdentity[reference]; + var referencedSchema = referencedCollection.attributes; + + var referencedAttributeKey; + Object.keys(referencedSchema).forEach(function(referencedSchemaKey) { + var attribute = referencedSchema[referencedSchemaKey]; + if(attribute.through === self.identity && attribute.via === key) + referencedAttributeKey = referencedSchemaKey; + }); + if(!referencedAttributeKey){ + Object.keys(referencedSchema).forEach(function(referencedSchemaKey) { + var attribute = referencedSchema[referencedSchemaKey]; + // Optimistic attribute assignment... + if(attribute.through === self.identity) + referencedAttributeKey = referencedSchemaKey; + }); + } + if(!referencedAttributeKey) + return; + + var referencedAttribute = referencedSchema[referencedAttributeKey]; + + var vertex = { + referencedCollectionName: reference, + referencedCollectionTableName: referencedCollection.tableName || reference, + referencedAttributeKey: referencedAttributeKey, + referencedAttributeColumnName: referencedAttribute.columnName || referencedAttributeKey, + dominant: referencedAttribute.dominant, + junctionTableKey: key, + junctionTableColumnName: self.schema[key].columnName || key + }; + + if(!vertexA) + vertexA = vertex; + else if(!vertexB) + vertexB = vertex; + else + log.error('Too many associations! Unable to process model [' + self.identity + '] attribute [' + key + '].'); + }); + + if(!vertexA.dominant && !vertexB.dominant){ + var dominantVertex = (vertexA.junctionTableKey < vertexB.junctionTableKey) ? vertexA : vertexB; + dominantVertex.dominant = true; + + log.warn(self.identity + ' junction table associations [' + vertexA.referencedCollectionName + + ', ' + vertexB.referencedCollectionName + '] have no dominant through association. ' + + dominantVertex.junctionTableKey + + ' was chosen as dominant.'); + } + + if(vertexA.dominant){ + vertexA.referencedAttributeEdge = 'out_' + self.tableName; + vertexA.edgeOppositeEnd = 'in'; + vertexB.referencedAttributeEdge = 'in_' + self.tableName; + vertexB.edgeOppositeEnd = 'out'; + self.edgeSides = { out: vertexA, in: vertexB }; + return; + } + + if(vertexB.dominant){ + vertexA.referencedAttributeEdge = 'in_' + self.tableName; + vertexA.edgeOppositeEnd = 'out'; + vertexB.referencedAttributeEdge = 'out_' + self.tableName; + vertexB.edgeOppositeEnd = 'in'; + self.edgeSides = { out: vertexB, in: vertexA }; + } +}; + diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index 2da8b12..a4afa1e 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -3,6 +3,7 @@ */ var assert = require('assert'), util = require('util'), + Edge = require('../../lib/collection').Edge, Associations = require('../../lib/associations'), _ = require('lodash'); @@ -14,12 +15,27 @@ var collections = { comment_parent: require('./fixtures/commentParent.model'), comment_recipe: require('./fixtures/commentRecipe.model') }; + +var connectionMock = { + config: { options: {fetchPlanLevel: 1} }, + collections: collections, + collectionsByIdentity: collections +}; + +collections.authored_comment.waterline = { schema: {} }; +collections.authored_comment.definition = collections.authored_comment.attributes; +collections.comment_parent.waterline = { schema: {} }; +collections.comment_parent.definition = collections.comment_parent.attributes; +collections.comment_recipe.waterline = { schema: {} }; +collections.comment_recipe.definition = collections.comment_recipe.attributes; +var newCollections = { + authored_comment: new Edge(collections.authored_comment, connectionMock), + comment_parent: new Edge(collections.comment_parent, connectionMock), + comment_recipe: new Edge(collections.comment_recipe, connectionMock), +}; +connectionMock.newCollections = newCollections; -var associations = new Associations({ - config: { options: {fetchPlanLevel: 1} }, - collections: collections, - collectionsByIdentity: collections - }); +var associations = new Associations(connectionMock); describe('associations class', function () { From dd7c5b6be8f7f718051a864e6d2a1a02f362367f Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 02:18:28 +0000 Subject: [PATCH 14/81] Linting and some clean up --- lib/collection/vertex.js | 7 ++++++- lib/connection.js | 7 ------- .../tests/associations/manyThrough.find.js | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js index e4d29bb..a2f4368 100644 --- a/lib/collection/vertex.js +++ b/lib/collection/vertex.js @@ -10,8 +10,13 @@ var utils = require('../utils'), * @param {Connection} connection * @api public */ -var Vertex = module.exports = utils.extend(Document, function() { +//var Vertex = +module.exports = utils.extend(Document, function() { Document.apply(this, arguments); + + // Set the orientdb super class ('document' / V / E) for this document this.superClass = 'V'; + + // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) this.classCommand = 'VERTEX'; }); diff --git a/lib/connection.js b/lib/connection.js index cd3022f..eec182d 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -5,8 +5,6 @@ var Oriento = require('oriento'), async = require('async'), _ = require('lodash'), utils = require('./utils'), - Query = require('./query'), - Document = require('./document'), Associations = require('./associations'), log = require('debug-logger')('waterline-orientdb:connection'), Collection = require('./collection'); @@ -27,11 +25,6 @@ module.exports = (function () { storage: 'plocal' }, server, - transformers = { - '@rid': function (rid) { - return '#' + rid.cluster + ':' + rid.position; - } - }, DbHelper = function (db, collections, config) { this.db = db; this.collections = collections; diff --git a/test/integration-orientdb/tests/associations/manyThrough.find.js b/test/integration-orientdb/tests/associations/manyThrough.find.js index 379d95b..414915b 100644 --- a/test/integration-orientdb/tests/associations/manyThrough.find.js +++ b/test/integration-orientdb/tests/associations/manyThrough.find.js @@ -87,7 +87,7 @@ describe('Association Interface', function() { .populate('teams') .populate('sponsor') .then(function(stadium){ - assert(typeof stadium.id === 'string'); + assert.equal(typeof stadium.id, 'string'); assert(stadium.teams.length === 1); assert(stadium.owners.length === 0); assert(!stadium.out_venueTable); From 25d83d6414510acf05f671f2f1eb3bc94bc19608 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 02:26:42 +0000 Subject: [PATCH 15/81] Associations.isThroughJoin: checking instance of edge instead of using util method --- lib/associations.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/associations.js b/lib/associations.js index 7bf39ed..34a5a33 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -5,6 +5,7 @@ var _ = require('lodash'), _runJoins = require('waterline-cursor'), utils = require('./utils'), + Edge = require('./collection').Edge, log = require('debug-logger')('waterline-orientdb:associations'); /** @@ -398,8 +399,8 @@ Associations.prototype.isThroughJoin = function isThroughJoin(criteria) { for(var i=0; i < criteria.joins.length; i++){ var join = criteria.joins[i]; - var collectionInstance = self.connection.collections[join.parent]; - if(utils.isJunctionTableThrough(collectionInstance)) + var collectionInstance = self.connection.newCollections[join.parent]; + if(collectionInstance instanceof Edge) return true; } From 0c0ee2794b1c6140c205618af64d807b80d4a45b Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 14:47:27 +0000 Subject: [PATCH 16/81] Fetching dbClasses before instantiating Collections and passing the reference; Passing collections orderedById to collections, mostly because of edge and, soon, for processing OrientDB links --- lib/adapter.js | 21 +++-- lib/associations.js | 1 - lib/collection/document.js | 5 +- lib/collection/edge.js | 6 +- lib/collection/index.js | 8 +- lib/connection.js | 145 +++++++++++++++++---------------- test/unit/associations.test.js | 6 +- 7 files changed, 105 insertions(+), 87 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index feba09b..64fb0b1 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -2,7 +2,7 @@ /** * Module Dependencies */ -var orient = require('./connection'), +var Connection = require('./connection'), Associations = require('./associations'), utils = require('./utils'); @@ -25,6 +25,8 @@ module.exports = (function() { // You'll want to maintain a reference to each connection // that gets registered with this adapter. + // + // Keep track of all the connections used by the app var connections = {}; // You may also want to store additional, private data @@ -43,10 +45,17 @@ module.exports = (function() { // You don't have to support this feature right off the bat in your // adapter, but it ought to get done eventually. // - var getConn = function(config, collections) { - return orient.create(config, collections); - }; - + // Sounds annoying to deal with... + // ...but it's not bad. In each method, acquire a connection using the config + // for the current model (looking it up from `_modelReferences`), establish + // a connection, then tear it down before calling your method's callback. + // Finally, as an optimization, you might use a db pool for each distinct + // connection configuration, partioning pools for each separate configuration + // for your adapter (i.e. worst case scenario is a pool for each model, best case + // scenario is one single single pool.) For many databases, any change to + // host OR database OR user OR password = separate pool. + // var _dbPools = {}; + var adapter = { // Set to true if this adapter supports (or requires) things like data @@ -109,7 +118,7 @@ module.exports = (function() { // e.g. connections[connection.identity] = new Database(connection, // collections); - getConn(connection, collections) + Connection.create(connection, collections) .then(function(helper) { connections[connection.identity] = helper; cb(); diff --git a/lib/associations.js b/lib/associations.js index 34a5a33..6739f79 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -358,7 +358,6 @@ Associations.prototype.genericJoin = function genericJoin(collectionName, criter * @api private */ Associations.prototype.getEdgeSides = function getEdgeSides(collectionName) { - console.log(collectionName); var edge = this.connection.newCollections[collectionName]; return edge.edgeSides; }; diff --git a/lib/collection/document.js b/lib/collection/document.js index b010a46..60c27c7 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -13,7 +13,7 @@ var _ = require('lodash'), * @param {Connection} connection * @api public */ -var Document = module.exports = function Document(definition, connection) { +var Document = module.exports = function Document(definition, connection, databaseClass/*, collectionsByIdentity*/) { // Set a tableName for this document this.tableName = ''; @@ -24,6 +24,9 @@ var Document = module.exports = function Document(definition, connection) { // Set the orientdb super class ('document' / V / E) for this document this.superClass = ''; + // Set the Oriento class object + this.databaseClass = databaseClass; + // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) this.classCommand = undefined; diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 40e0c3e..e265011 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -23,7 +23,7 @@ var Edge = module.exports = utils.extend(Document, function() { // Information about edge's in and out properties this.edgeSides = null; - this._getEdgeSides(); + this._getEdgeSides(arguments[3]); }); @@ -95,7 +95,7 @@ Edge.prototype.insert = function insert(values, cb) { * @return {Object} * @api private */ -Edge.prototype._getEdgeSides = function _getEdgeSides() { +Edge.prototype._getEdgeSides = function _getEdgeSides(collectionsByIdentity) { var self = this, vertexA, vertexB; @@ -105,7 +105,7 @@ Edge.prototype._getEdgeSides = function _getEdgeSides() { if(!reference) return; - var referencedCollection = self.connection.collectionsByIdentity[reference]; + var referencedCollection = collectionsByIdentity[reference]; var referencedSchema = referencedCollection.attributes; var referencedAttributeKey; diff --git a/lib/collection/index.js b/lib/collection/index.js index b499f07..51006c4 100644 --- a/lib/collection/index.js +++ b/lib/collection/index.js @@ -2,17 +2,17 @@ var utils = require('../utils'); -var Collection = module.exports = function Collection (definition, connection) { +var Collection = module.exports = function Collection (definition, connection, databaseClass, collectionsByIdentity) { if(connection.config.databaseType === 'document' || definition.orientdbClass === 'document'){ - return new Collection.Document(definition, connection); + return new Collection.Document(definition, connection, databaseClass, collectionsByIdentity); } if(definition.orientdbClass === 'E' || (utils.isJunctionTableThrough(definition) && definition.orientdbClass !== 'V')){ - return new Collection.Edge(definition, connection); + return new Collection.Edge(definition, connection, databaseClass, collectionsByIdentity); } - return new Collection.Vertex(definition, connection); + return new Collection.Vertex(definition, connection, databaseClass, collectionsByIdentity); }; Collection.Document = require('./document'); diff --git a/lib/connection.js b/lib/connection.js index eec182d..3ec3d0e 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -25,7 +25,8 @@ module.exports = (function () { storage: 'plocal' }, server, - DbHelper = function (db, collections, config) { + DbHelper = function (db, collections, config, classes) { + var self = this; this.db = db; this.collections = collections; this.collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ @@ -37,11 +38,17 @@ module.exports = (function () { this.associations = new Associations(this); this.server = server; + var dbClasses = {}; + classes.forEach(function(klass){ + dbClasses[klass.name] = klass; + }); + this.dbClasses = dbClasses; + // Build up a registry of collections var context = this; this.newCollections = {}; // TODO: replace this.collections Object.keys(collections).forEach(function(key) { - context.newCollections[key] = new Collection(collections[key], context); + context.newCollections[key] = new Collection(collections[key], context, dbClasses[key], self.collectionsByIdentity); }); }, ensureDB = function (connectionProps) { @@ -52,46 +59,44 @@ module.exports = (function () { dbProps.storage = connectionProps.options.storage; } dbProps = _.extend({}, dbDefaults, dbProps); - var deferred = Q.defer(); - log.debug('Looking for database', connectionProps.database); - server.list().then(function (dbs) { - var dbExists = _.find(dbs, function (db) { - return db.name === dbProps.name; + var deferred = Q.defer(); + log.debug('Looking for database', connectionProps.database); + + server.list() + .then(function(dbs) { + var dbExists = _.find(dbs, function(db) { + return db.name === dbProps.name; + }); + if (dbExists) { + log.debug('database found.'); + deferred.resolve(server.use(dbProps)); + } else { + log.debug('database not found, will create it.'); + server.create(dbProps).then(function(db) { + deferred.resolve(db); }); - if (dbExists) { - log.debug('database found.'); - deferred.resolve(server.use(dbProps)); - } else { - log.debug('database not found, will create it.'); - server.create(dbProps).then(function (db) { - deferred.resolve(db); - }); - } - - + } }); - return deferred.promise; + return deferred.promise; }, getDb = function (connection) { - - var orientoConnection = { - host: connection.host, - port: connection.host, - username: connection.user, - password: connection.password, - transport: connection.transport || 'binary', - enableRIDBags: false, - useToken: false - }; - - if (!server){ - log.info('Connecting to database...'); - server = new Oriento(orientoConnection); - } - - return ensureDB(connection); - + var orientoConnection = { + host : connection.host, + port : connection.host, + username : connection.user, + password : connection.password, + transport : connection.transport || 'binary', + enableRIDBags : false, + useToken : false + }; + + if (!server) { + log.info('Connecting to database...'); + server = new Oriento(orientoConnection); + } + + return ensureDB(connection); }; DbHelper.prototype.db = null; @@ -178,7 +183,7 @@ module.exports = (function () { }; - + /*Makes sure that all the collections are synced to database classes*/ DbHelper.prototype.registerCollections = function () { var deferred = Q.defer(), @@ -513,41 +518,43 @@ module.exports = (function () { }; - var connect = function (connection, collections) { - // if an active connection exists, use - // it instead of tearing the previous - // one down - var d = Q.defer(); - - try { - - getDb(connection, collections).then(function (db) { - var helper = new DbHelper(db, collections, connection); - - helper.registerCollections() - .then(function () { - d.resolve(helper); - }); + var connect = function(connection, collections) { + // if an active connection exists, use + // it instead of tearing the previous + // one down + var d = Q.defer(); + + try { + var database; + getDb(connection, collections) + .then(function(db) { + database = db; + return db.class.list(); + }) + .then(function(classes){ + var helper = new DbHelper(database, collections, connection, classes); + + helper.registerCollections() + .then(function() { + d.resolve(helper); }); + }); + + } catch (err) { + log.error('An error has occured while trying to connect to OrientDB.', err); + d.reject(err); + throw err; + } + return d.promise; - } catch (err) { - log.error('An error has occured while trying to connect to OrientDB.', err); - d.reject(err); - throw err; - } - - - - return d.promise; - - }; + }; + return { + create : function(connection, collections) { + return connect(connection, collections); + } + }; - return { - create: function (connection, collections) { - return connect(connection, collections); - } - }; })(); diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index a4afa1e..b490d62 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -29,9 +29,9 @@ collections.comment_parent.definition = collections.comment_parent.attributes; collections.comment_recipe.waterline = { schema: {} }; collections.comment_recipe.definition = collections.comment_recipe.attributes; var newCollections = { - authored_comment: new Edge(collections.authored_comment, connectionMock), - comment_parent: new Edge(collections.comment_parent, connectionMock), - comment_recipe: new Edge(collections.comment_recipe, connectionMock), + authored_comment: new Edge(collections.authored_comment, connectionMock, null, collections), + comment_parent: new Edge(collections.comment_parent, connectionMock, null, collections), + comment_recipe: new Edge(collections.comment_recipe, connectionMock, null, collections), }; connectionMock.newCollections = newCollections; From cd6c59618902dc1440dd5a8424b8194313def16b Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 17:25:01 +0000 Subject: [PATCH 17/81] Removing property creation logic from connection (in progress) --- lib/connection.js | 188 +++++++++++++++++----------------------------- 1 file changed, 70 insertions(+), 118 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 70f5b64..c0824c5 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -27,6 +27,7 @@ module.exports = (function () { server, DbHelper = function (db, collections, config, classes) { var self = this; + this.linksToBeCreated = []; //TODO: move this this.db = db; this.collections = collections; this.collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ @@ -45,10 +46,9 @@ module.exports = (function () { this.dbClasses = dbClasses; // Build up a registry of collections - var context = this; this.newCollections = {}; // TODO: replace this.collections Object.keys(collections).forEach(function(key) { - context.newCollections[key] = new Collection(collections[key], context, dbClasses[key], self.collectionsByIdentity); + self.newCollections[key] = new Collection(collections[key], self, dbClasses[key], self.collectionsByIdentity); }); }, ensureDB = function (connectionProps) { @@ -182,15 +182,15 @@ module.exports = (function () { return deferred.promise; }; - - + + /*Makes sure that all the collections are synced to database classes*/ DbHelper.prototype.registerCollections = function () { var deferred = Q.defer(), me = this, db = me.db, - collections = this.collections, - linksToBeCreated = []; + newCollections = this.newCollections, + linksToBeCreated = this.linksToBeCreated; async.auto({ @@ -212,105 +212,53 @@ module.exports = (function () { }], registerClasses: ['getClasses', - function (complete, results) { - var classes = results.getClasses, - klassesToBeAdded = _.filter(collections, function (v, k) { - return _.isUndefined(_.find(classes, function (klass) { - return k == klass.name; - })); - }); - - if (klassesToBeAdded.length > 0) { - - async.mapSeries(klassesToBeAdded, function (collection, next) { - var tableName = collection.tableName || collection.identity; - var collectionClass = collection.edge || utils.isJunctionTableThrough(collection) ? 'E' : 'V'; - - db.class - .create(tableName, collectionClass) - .then(function (klass, err) { - //TODO: refactor: move this to own method!!! - // Create OrientDB schema - if (collection.attributes){ - log.debug('Creating DB class [' + tableName + '] for collection [' + collection.identity + ']'); - Object.keys(collection.attributes).forEach(function(attributeName){ - if(attributeName === 'id'){ - // @rid is the equivalent of id, no need to add id. - return; - } - var linkedClass = null, - attributeType = null, - columnName = attributeName; - if(typeof collection.attributes[attributeName] === 'string') - attributeType = collection.attributes[attributeName]; - else if (typeof collection.attributes[attributeName] === 'function') - return; - else if (collection.attributes[attributeName].model || collection.attributes[attributeName].references){ - linkedClass = collection.attributes[attributeName].model || collection.attributes[attributeName].references; - var useLink = me.collectionsByIdentity[linkedClass].primaryKey === 'id'; - attributeType = useLink ? 'Link' : collection.pkFormat; - } - else if (collection.attributes[attributeName].foreignKey){ - attributeType = 'Link'; - } - else if (collection.attributes[attributeName].collection){ - attributeType = 'linkset'; - linkedClass = collection.attributes[attributeName].collection; - } - else - attributeType = collection.attributes[attributeName].type; - - if (attributeType === 'array') - attributeType = 'embeddedlist'; - else if (attributeType === 'json') - attributeType = 'embedded'; - else if (attributeType === 'text' || attributeType === 'email') - attributeType = 'string'; - - if(collection.attributes[attributeName].columnName) - columnName = collection.attributes[attributeName].columnName; - - //log.debug('attributeType for ' + attributeName + ':', attributeType); - - if(attributeType){ - var prop = { - name: columnName, - type: attributeType - }; - if(!!collection.attributes[attributeName].required) { - prop.mandatory = true; - } - klass.property.create(prop).then(function(){ - if(!!collection.attributes[attributeName].unique){ - db.index.create({ - name: tableName + '.' + columnName, - type: 'unique' - }); - } else if(!!collection.attributes[attributeName].index){ - db.index.create({ - name: tableName + '.' + columnName, - type: 'notunique' - }); - } - }); - if(attributeType.toLowerCase().indexOf('link') === 0 && linkedClass) - linksToBeCreated.push({ - attributeName: columnName, - klass: klass, - linkedClass: linkedClass - }); - } - }); - } - next(err, klass); - }); - }, - function (err, created) { - complete(err, created); - }); - return; + function (complete) { + + async.mapSeries(_.values(newCollections), function (collection, next) { + console.log('--- db: ' + collection.tableName); + + if(collection.databaseClass){ + console.log('--- db exist: ' + collection.databaseClass.name); + return next(null, collection.databaseClass); } - complete(null, classes); + + db.class.create(collection.tableName, collection.superClass) + .then(function (klass, err) { + if(err) log.error('b.class.create: ' + err); + + collection.databaseClass = klass; + + //TODO: refactor: move this to own method!!! + // Create OrientDB schema + console.log('--- db created: ' + collection.databaseClass.name); + + _.values(collection.orientdbSchema).forEach(function(prop){ + klass.property.create(prop) + .then(function(){ + console.log('--- Prop: ' + collection.tableName + ': ' + prop.name); + // TODO: indices + // if(!!collection.attributes[attributeName].unique){ + // db.index.create({ + // name: tableName + '.' + columnName, + // type: 'unique' + // }); + // } else if(!!collection.attributes[attributeName].index){ + // db.index.create({ + // name: tableName + '.' + columnName, + // type: 'notunique' + // }); + // } + + + }); + }); + next(err, klass); + }); + }, function (err, created) { + if(err) log.error('getClasses: ' + err); + console.log('- Classes and Properties created !!!'); + complete(err, created); + }); }], setClasses: ['registerClasses', @@ -333,20 +281,25 @@ module.exports = (function () { ], registerLinks: ['setClasses', - function (complete, results) { - + function (complete) { + async.map(linksToBeCreated, function (link, next) { - var linkedClass = results.setClasses[link.linkedClass]; - link.klass.property.update({ - name: link.attributeName, - linkedClass: linkedClass.name - }) - .then(function(){ - next(null, link.klass); - }) - .error(next); - }, + var linkClass = newCollections[link.klass].databaseClass; + var linkedClass = newCollections[link.linkedClass]; + console.log('--- Link class name : ' + link.klass + ': ' + link.attributeName); + console.log('--- Link: ' + linkClass.name); + + linkClass.property.update({ + name: link.attributeName, + linkedClass: linkedClass.tableName + }) + .then(function(){ + next(null, linkClass); + }) + .error(next); + }, function (err, created) { + log.error('registerLinks:', err); complete(err, created); }); }], @@ -356,7 +309,6 @@ module.exports = (function () { // we currently need this for fetch plans to work. We probably should use these transformers // inside a collection class such as, e.g. https://github.com/balderdashy/sails-mongo/blob/master/lib/collection.js // For an example of a transformer, look at: https://github.com/codemix/oriento/blob/aa6257d31d1b873cc19ee07df84e162ea86ff998/test/db/db-test.js#L79 - function transformer (data) { var newData = {}; var keys = Object.keys(data), @@ -374,11 +326,11 @@ module.exports = (function () { db.registerTransformer(klasses[klassKey].name, transformer); }); complete(); - }] }, function (err, results) { if (err) { + log.error('ERROR:', err); deferred.reject(err); return; } From e72e2bd6b54ba9477a7a0c7a3c66dcdbc0e6cae6 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 17:35:30 +0000 Subject: [PATCH 18/81] Connection, more code deleted Document: changes forgotten to commit previously regarding property creation --- lib/collection/document.js | 99 +++++++++++++++++++++++++++++++++++--- lib/connection.js | 28 ++--------- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index 60c27c7..006cad1 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -13,7 +13,7 @@ var _ = require('lodash'), * @param {Connection} connection * @api public */ -var Document = module.exports = function Document(definition, connection, databaseClass/*, collectionsByIdentity*/) { +var Document = module.exports = function Document(definition, connection, databaseClass, collectionsByIdentity) { // Set a tableName for this document this.tableName = ''; @@ -33,6 +33,9 @@ var Document = module.exports = function Document(definition, connection, databa // Hold Schema Information this.schema = null; + // Hold Schema in OrientDB friendly format + this.orientdbSchema = null; + // Hold the waterline schema, used by query namely waterline-sequel-orientdb this.waterlineSchema = null; @@ -41,9 +44,12 @@ var Document = module.exports = function Document(definition, connection, databa // Hold Indexes // this.indexes = []; + + // Holds links between properties (associations) + this.links = []; // Parse the definition into document attributes - this._parseDefinition(definition); + this._parseDefinition(definition, collectionsByIdentity); // Build an indexes dictionary //this._buildIndexes(); @@ -225,8 +231,11 @@ Document.prototype.destroy = function destroy(criteria, cb) { * @api private */ -Document.prototype._parseDefinition = function _parseDefinition(definition) { +Document.prototype._parseDefinition = function _parseDefinition(definition, collectionsByIdentity) { + collectionsByIdentity = collectionsByIdentity || {}; var collectionDef = _.cloneDeep(definition); + + var self = this; // Hold the Schema this.schema = collectionDef.definition; @@ -237,13 +246,89 @@ Document.prototype._parseDefinition = function _parseDefinition(definition) { // } // Set the tableName - var tabl = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); - this.tableName = _.clone(tabl); + var tableName = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); + this.tableName = _.clone(tableName); // Set the identity - var ident = definition.identity ? definition.identity : definition.tableName; - this.identity = _.clone(ident); + var identity = definition.identity ? definition.identity : definition.tableName; + this.identity = _.clone(identity); this.waterlineSchema = definition.waterline.schema; + + + this.orientdbSchema = {}; + + log.debug('Creating DB class [' + tableName + '] for collection [' + identity + ']'); + Object.keys(self.schema).forEach(function(attributeName) { + if (attributeName === 'id') { + // @rid is the equivalent of id, no need to add id. + return; + } + + var linkedClass = null, + attributeType = null, + columnName = attributeName, + linkedIdentity, + linkedDefinition; + + if ( typeof self.schema[attributeName] === 'string') + attributeType = self.schema[attributeName]; + else if ( typeof self.schema[attributeName] === 'function') + return; + else if (self.schema[attributeName].model || self.schema[attributeName].references) { + linkedIdentity = self.schema[attributeName].model || self.schema[attributeName].references; + linkedDefinition = collectionsByIdentity[linkedIdentity]; + var useLink = linkedDefinition.primaryKey === 'id'; + linkedClass = linkedDefinition.tableName ? linkedDefinition.tableName : linkedDefinition.identity.toLowerCase(); + attributeType = useLink ? 'Link' : definition.pkFormat; + } else if (self.schema[attributeName].foreignKey) { + attributeType = 'Link'; + } else if (self.schema[attributeName].collection) { + attributeType = 'linkset'; + linkedIdentity = self.schema[attributeName].collection; + linkedDefinition = collectionsByIdentity[linkedIdentity]; + linkedClass = linkedDefinition.tableName ? linkedDefinition.tableName : linkedDefinition.identity.toLowerCase(); + } else + attributeType = self.schema[attributeName].type; + + if (attributeType === 'array') + attributeType = 'embeddedlist'; + else if (attributeType === 'json') + attributeType = 'embedded'; + else if (attributeType === 'text' || attributeType === 'email') + attributeType = 'string'; + + if (self.schema[attributeName].columnName) + columnName = self.schema[attributeName].columnName; + + if (attributeType) { + var prop = { + name : columnName, + type : attributeType + }; + if (!!self.schema[attributeName].required) { + prop.mandatory = true; + } + self.orientdbSchema[columnName] = prop; + log.debug('attributeType for ' + attributeName + ':', self.orientdbSchema[columnName].type); + + if (attributeType.toLowerCase().indexOf('link') === 0 && linkedClass){ + self.links.push({ + klass : tableName, + attributeName : columnName, + linkedClass : linkedClass + }); + + // TODO: temporary, remove afterwards + self.connection.linksToBeCreated.push({ + klass : tableName, + attributeName : columnName, + linkedClass : linkedClass + }); + } + } + }); + + }; diff --git a/lib/connection.js b/lib/connection.js index c0824c5..548b7d4 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -261,26 +261,7 @@ module.exports = (function () { }); }], - setClasses: ['registerClasses', - function(complete, results){ - var allClasses = (results.getClasses || []).concat(results.registerClasses); - - //flatten the array of classes to key value pairs to ease the retrieval of classes - me._classes = _.reduce(allClasses, function (initial, klass) { - - var collection = _.find(me.collections, function (v) { - return (v.tableName || v.identity) === klass.name; - }); - //If a matching collection is found then store the class reference using the collection name else use class name itself - initial[(collection && collection.identity) || klass.name] = klass; - return initial; - }, {}); - - complete(null, me._classes); - } - ], - - registerLinks: ['setClasses', + registerLinks: ['registerClasses', function (complete) { async.map(linksToBeCreated, function (link, next) { @@ -304,7 +285,7 @@ module.exports = (function () { }); }], - rgisterTransformers: ['setClasses', function (complete, results) { + rgisterTransformers: ['registerClasses', function (complete) { // TODO: this should not be done in such a generic fashion, but truth is // we currently need this for fetch plans to work. We probably should use these transformers // inside a collection class such as, e.g. https://github.com/balderdashy/sails-mongo/blob/master/lib/collection.js @@ -321,9 +302,8 @@ module.exports = (function () { return newData; } - var klasses = results.setClasses; - Object.keys(klasses).forEach(function(klassKey){ - db.registerTransformer(klasses[klassKey].name, transformer); + Object.keys(me.newCollections).forEach(function(key){ + db.registerTransformer(key, transformer); }); complete(); }] From 4bcd8f8dd3eccd076227027b0fc353ed7f347b37 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 17:55:29 +0000 Subject: [PATCH 19/81] connection: createCollection method added - Code deleted --- lib/connection.js | 150 +++++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 81 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 548b7d4..2d3ccfc 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -188,75 +188,21 @@ module.exports = (function () { DbHelper.prototype.registerCollections = function () { var deferred = Q.defer(), me = this, - db = me.db, newCollections = this.newCollections, linksToBeCreated = this.linksToBeCreated; async.auto({ - - ensureIndex: function (next) { - if (me.config.createCustomIndex) { - me.ensureIndex().then(function (indexEnsured, err) { - next(err, indexEnsured); - }); - return; - } - next(null, true); - }, - - getClasses: ['ensureIndex', - function (next) { - db.class.list().then(function (classes, err) { - next(err, classes); - }); - }], - registerClasses: ['getClasses', + registerClasses: [ function (complete) { async.mapSeries(_.values(newCollections), function (collection, next) { - console.log('--- db: ' + collection.tableName); - if(collection.databaseClass){ - console.log('--- db exist: ' + collection.databaseClass.name); - return next(null, collection.databaseClass); - } - - db.class.create(collection.tableName, collection.superClass) - .then(function (klass, err) { - if(err) log.error('b.class.create: ' + err); - - collection.databaseClass = klass; - - //TODO: refactor: move this to own method!!! - // Create OrientDB schema - console.log('--- db created: ' + collection.databaseClass.name); - - _.values(collection.orientdbSchema).forEach(function(prop){ - klass.property.create(prop) - .then(function(){ - console.log('--- Prop: ' + collection.tableName + ': ' + prop.name); - // TODO: indices - // if(!!collection.attributes[attributeName].unique){ - // db.index.create({ - // name: tableName + '.' + columnName, - // type: 'unique' - // }); - // } else if(!!collection.attributes[attributeName].index){ - // db.index.create({ - // name: tableName + '.' + columnName, - // type: 'notunique' - // }); - // } - - - }); - }); - next(err, klass); - }); + me.createCollection(collection.tableName, null, function(err, klass){ + next(err, klass); + }); + }, function (err, created) { - if(err) log.error('getClasses: ' + err); - console.log('- Classes and Properties created !!!'); complete(err, created); }); }], @@ -285,28 +231,6 @@ module.exports = (function () { }); }], - rgisterTransformers: ['registerClasses', function (complete) { - // TODO: this should not be done in such a generic fashion, but truth is - // we currently need this for fetch plans to work. We probably should use these transformers - // inside a collection class such as, e.g. https://github.com/balderdashy/sails-mongo/blob/master/lib/collection.js - // For an example of a transformer, look at: https://github.com/codemix/oriento/blob/aa6257d31d1b873cc19ee07df84e162ea86ff998/test/db/db-test.js#L79 - function transformer (data) { - var newData = {}; - var keys = Object.keys(data), - length = keys.length, - key, i; - for (i = 0; i < length; i++) { - key = keys[i]; - newData[key] = data[key]; - } - return newData; - } - - Object.keys(me.newCollections).forEach(function(key){ - db.registerTransformer(key, transformer); - }); - complete(); - }] }, function (err, results) { if (err) { @@ -321,6 +245,70 @@ module.exports = (function () { }; + /** + * CreateCollection + * + * + * @param {String} collectionName + * @param {Object} definition + * @param {Function} cb + */ + DbHelper.prototype.createCollection = function createCollection(collectionName, definition, cb) { + var self = this; + + var collection = self.newCollections[collectionName]; + + // Create the Collection + if (collection.databaseClass) { + // TODO: update properties + return cb(null, collection.databaseClass); + } + + self.db.class.create(collection.tableName, collection.superClass) + .then(function(klass, err) { + if (err) { log.error('db.class.create: ' + err); } + + collection.databaseClass = klass; + + // Create properties + _.values(collection.orientdbSchema).forEach(function(prop) { + klass.property.create(prop).then(function() { + console.log('--- Prop: ' + collection.tableName + ': ' + prop.name); + // TODO: indices + // if(!!collection.attributes[attributeName].unique){ + // db.index.create({ + // name: tableName + '.' + columnName, + // type: 'unique' + // }); + // } else if(!!collection.attributes[attributeName].index){ + // db.index.create({ + // name: tableName + '.' + columnName, + // type: 'notunique' + // }); + // } + }); + }); + + // Create transformations + function transformer(data) { + var newData = {}; + var keys = Object.keys(data), length = keys.length, key, i; + for ( i = 0; i < length; i++) { + key = keys[i]; + newData[key] = data[key]; + } + return newData; + } + + self.db.registerTransformer(collectionName, transformer); + + cb(null, klass); + }); + + // Create links ? + // Create Indexes + }; + /** * query From c1e1d1f3562f41767e54446091f8a3ef6861949d Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 19:04:55 +0000 Subject: [PATCH 20/81] connection: Removed register collection Enabled syncable (still need to registerLinks) --- lib/adapter.js | 29 ++++++------- lib/collection/document.js | 3 +- lib/connection.js | 89 ++++++++++---------------------------- 3 files changed, 38 insertions(+), 83 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 64fb0b1..ff4022e 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -77,7 +77,7 @@ module.exports = (function() { // alter => Drop/add columns as necessary. // safe => Don't change anything (good for production DBs) // - syncable : false, + syncable : true, // Which type of primary key is used by default pkFormat : 'string', @@ -158,17 +158,20 @@ module.exports = (function() { * Return the Schema of a collection after first creating the collection * and indexes if they don't exist. * - * @param {String} connectionName - * @param {String} collectionName + * @param {String} connection + * @param {String} collection * @param {Function} callback */ describe : function(connection, collection, cb) { // Add in logic here to describe a collection (e.g. DESCRIBE TABLE // logic) - - connections[connection].collection.getProperties(collection, function(res, err) { - cb(err, res); - }); + + // TODO: Fetch properties from db and generate a schema + var collectionInstance = connections[connection].newCollections[collection]; + if(collectionInstance.databaseClass) { + return cb(null, collectionInstance.schema); + } + cb(); }, @@ -185,15 +188,9 @@ module.exports = (function() { * @param {Function} cb */ define : function(connection, collection, definition, cb) { - return connections[connection] - .create(collection, { - waitForSync : true - }) - .then(function(res) { - cb(null, res); - }, function(err) { - cb(err); - }); + + // Create the collection and indexes + connections[connection].createCollection(collection, definition, cb); }, diff --git a/lib/collection/document.js b/lib/collection/document.js index 006cad1..b27e127 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -310,7 +310,8 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll prop.mandatory = true; } self.orientdbSchema[columnName] = prop; - log.debug('attributeType for ' + attributeName + ':', self.orientdbSchema[columnName].type); + + //log.debug('attributeType for ' + attributeName + ':', self.orientdbSchema[columnName].type); if (attributeType.toLowerCase().indexOf('link') === 0 && linkedClass){ self.links.push({ diff --git a/lib/connection.js b/lib/connection.js index 2d3ccfc..611f57f 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -182,72 +182,33 @@ module.exports = (function () { return deferred.promise; }; - - /*Makes sure that all the collections are synced to database classes*/ - DbHelper.prototype.registerCollections = function () { - var deferred = Q.defer(), - me = this, - newCollections = this.newCollections, - linksToBeCreated = this.linksToBeCreated; - - async.auto({ - registerClasses: [ - function (complete) { - - async.mapSeries(_.values(newCollections), function (collection, next) { - - me.createCollection(collection.tableName, null, function(err, klass){ - next(err, klass); - }); - - }, function (err, created) { - complete(err, created); - }); - }], - - registerLinks: ['registerClasses', - function (complete) { - - async.map(linksToBeCreated, function (link, next) { - var linkClass = newCollections[link.klass].databaseClass; - var linkedClass = newCollections[link.linkedClass]; - console.log('--- Link class name : ' + link.klass + ': ' + link.attributeName); - console.log('--- Link: ' + linkClass.name); - - linkClass.property.update({ - name: link.attributeName, - linkedClass: linkedClass.tableName - }) - .then(function(){ - next(null, linkClass); - }) - .error(next); - }, - function (err, created) { - log.error('registerLinks:', err); - complete(err, created); - }); - }], - - }, - function (err, results) { - if (err) { - log.error('ERROR:', err); - deferred.reject(err); - return; - } - - deferred.resolve(results.registerClasses); - }); - return deferred.promise; - }; + // registerLinks: ['registerClasses', + // function (complete) { +// + // async.map(linksToBeCreated, function (link, next) { + // var linkClass = newCollections[link.klass].databaseClass; + // var linkedClass = newCollections[link.linkedClass]; +// + // linkClass.property.update({ + // name: link.attributeName, + // linkedClass: linkedClass.tableName + // }) + // .then(function(){ + // next(null, linkClass); + // }) + // .error(next); + // }, + // function (err, created) { + // log.error('registerLinks:', err); + // complete(err, created); + // }); + // }], /** - * CreateCollection - * + * Create Collection * * @param {String} collectionName * @param {Object} definition @@ -454,11 +415,7 @@ module.exports = (function () { }) .then(function(classes){ var helper = new DbHelper(database, collections, connection, classes); - - helper.registerCollections() - .then(function() { - d.resolve(helper); - }); + d.resolve(helper); }); } catch (err) { From 871e2f509e3370b0d4023b98b29e133846b54fab Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 21:34:15 +0000 Subject: [PATCH 21/81] Re-added links creation --- lib/adapter.js | 8 +--- lib/connection.js | 98 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index ff4022e..bb95986 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -165,13 +165,7 @@ module.exports = (function() { describe : function(connection, collection, cb) { // Add in logic here to describe a collection (e.g. DESCRIBE TABLE // logic) - - // TODO: Fetch properties from db and generate a schema - var collectionInstance = connections[connection].newCollections[collection]; - if(collectionInstance.databaseClass) { - return cb(null, collectionInstance.schema); - } - cb(); + connections[connection].describe(collection, cb); }, diff --git a/lib/connection.js b/lib/connection.js index 611f57f..0d791fb 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -24,7 +24,8 @@ module.exports = (function () { type: 'graph', storage: 'plocal' }, - server, + server, + DbHelper = function (db, collections, config, classes) { var self = this; this.linksToBeCreated = []; //TODO: move this @@ -50,7 +51,14 @@ module.exports = (function () { Object.keys(collections).forEach(function(key) { self.newCollections[key] = new Collection(collections[key], self, dbClasses[key], self.collectionsByIdentity); }); + + // aux variables used to figure out when all collections have been synced + this.collectionSync = { + postProcessed: false, + itemsToProcess: _.clone(collections) + }; }, + ensureDB = function (connectionProps) { var dbProps = (typeof connectionProps.database === 'object') ? connectionProps.database : { name: connectionProps.database }; dbProps.username = connectionProps.user; @@ -183,30 +191,32 @@ module.exports = (function () { }; - - // registerLinks: ['registerClasses', - // function (complete) { -// - // async.map(linksToBeCreated, function (link, next) { - // var linkClass = newCollections[link.klass].databaseClass; - // var linkedClass = newCollections[link.linkedClass]; -// - // linkClass.property.update({ - // name: link.attributeName, - // linkedClass: linkedClass.tableName - // }) - // .then(function(){ - // next(null, linkClass); - // }) - // .error(next); - // }, - // function (err, created) { - // log.error('registerLinks:', err); - // complete(err, created); - // }); - // }], + /** + * Describe + * + * @param {String} collectionName + * @param {Function} callback + */ + DbHelper.prototype.describe = function describe(collectionName, cb) { + var self = this; + if(self.collectionSync.itemsToProcess[collectionName]){ + delete self.collectionSync.itemsToProcess[collectionName]; + } + + // TODO: Fetch properties from db and generate a schema + var collectionInstance = self.newCollections[collectionName]; + if(collectionInstance.databaseClass) { + cb(null, collectionInstance.schema); + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ + self.postProcessing(); + } + } + cb(); + }; + + /** * Create Collection * @@ -221,7 +231,10 @@ module.exports = (function () { // Create the Collection if (collection.databaseClass) { - // TODO: update properties + // TODO: properties may need updating ? + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ + self.postProcessing(); + } return cb(null, collection.databaseClass); } @@ -234,7 +247,6 @@ module.exports = (function () { // Create properties _.values(collection.orientdbSchema).forEach(function(prop) { klass.property.create(prop).then(function() { - console.log('--- Prop: ' + collection.tableName + ': ' + prop.name); // TODO: indices // if(!!collection.attributes[attributeName].unique){ // db.index.create({ @@ -263,12 +275,48 @@ module.exports = (function () { self.db.registerTransformer(collectionName, transformer); + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ + self.postProcessing(); + } cb(null, klass); }); // Create links ? // Create Indexes }; + + + /** + * Post Processing + * + * called after all collections have been created + */ + DbHelper.prototype.postProcessing = function postProcessing(){ + var self = this; + + if(self.collectionSync.postProcessed) { + log.debug('Attempted to postprocess twice. This shouln\'t happen, try to improve the logic behind this.'); + return; + } + self.collectionSync.postProcessed = true; + + log.info('All classes created, post processing'); + + self.linksToBeCreated.forEach(function(link){ + var linkClass = self.newCollections[link.klass].databaseClass; + var linkedClass = self.newCollections[link.linkedClass]; + + linkClass.property.update({ + name : link.attributeName, + linkedClass : linkedClass.tableName + }) + .error(function(err){ + log.error('', err); + }); + }); + + }; + /** From 8e8ef819fc12c26e12626e07992ecbfbe4c5b882 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 19 Feb 2015 23:50:36 +0000 Subject: [PATCH 22/81] (Re)added support for indexes and required --- lib/collection/document.js | 58 +++++++++++++++-- lib/connection.js | 63 +++++++++++-------- test/integration-orientdb/bootstrap.js | 4 +- .../fixtures/indexes.fixture.js | 30 +++++++++ .../tests/define/indexes.js | 60 ++++++++++++++++++ 5 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 test/integration-orientdb/fixtures/indexes.fixture.js create mode 100644 test/integration-orientdb/tests/define/indexes.js diff --git a/lib/collection/document.js b/lib/collection/document.js index 6d4814f..63e2fec 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -43,7 +43,7 @@ var Document = module.exports = function Document(definition, connection, databa this.connection = connection; // Hold Indexes - // this.indexes = []; + this.indexes = []; // Holds links between properties (associations) this.links = []; @@ -52,7 +52,7 @@ var Document = module.exports = function Document(definition, connection, databa this._parseDefinition(definition, collectionsByIdentity); // Build an indexes dictionary - //this._buildIndexes(); + this._buildIndexes(); return this; }; @@ -230,7 +230,6 @@ Document.prototype.destroy = function destroy(criteria, cb) { * @param {Object} definition * @api private */ - Document.prototype._parseDefinition = function _parseDefinition(definition, collectionsByIdentity) { collectionsByIdentity = collectionsByIdentity || {}; var collectionDef = _.cloneDeep(definition); @@ -258,7 +257,6 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll this.orientdbSchema = {}; - log.debug('Creating DB class [' + tableName + '] for collection [' + identity + ']'); Object.keys(self.schema).forEach(function(attributeName) { if (attributeName === 'id') { // @rid is the equivalent of id, no need to add id. @@ -307,13 +305,18 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll name : columnName, type : attributeType }; - if (!!self.schema[attributeName].required) { + + // Check for required flag (not super elegant) + var propAttributes = utils.getAttributeAsObject(definition.attributes, columnName); + if (propAttributes && !!propAttributes.required) { prop.mandatory = true; } + self.orientdbSchema[columnName] = prop; //log.debug('attributeType for ' + attributeName + ':', self.orientdbSchema[columnName].type); + // process links if (attributeType.toLowerCase().indexOf('link') === 0 && linkedClass){ self.links.push({ klass : tableName, @@ -330,7 +333,50 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll } } }); - +}; + + +/** + * Build Internal Indexes Dictionary based on the current schema. + * + * @api private + */ + +Document.prototype._buildIndexes = function _buildIndexes() { + var self = this; + + Object.keys(this.schema).forEach(function(key) { + var columnName = self.schema[key].columnName ? self.schema[key].columnName : key; + var index = { + name: self.tableName + '.' + columnName + }; + // If index key is `id` ignore it because Mongo will automatically handle this + if(key === 'id') { + return; + } + + // Handle Unique Indexes + if(self.schema[key].unique) { + + // set the index type + index.type = 'unique'; + + // Store the index in the collection + self.indexes.push(index); + return; + } + + // Handle non-unique indexes + if(self.schema[key].index) { + + // set the index type + index.type = 'notunique'; + + // Store the index in the collection + self.indexes.push(index); + return; + } + }); }; diff --git a/lib/connection.js b/lib/connection.js index 0d791fb..bab6ac7 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -232,9 +232,7 @@ module.exports = (function () { // Create the Collection if (collection.databaseClass) { // TODO: properties may need updating ? - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ - self.postProcessing(); - } + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ self.postProcessing(); } return cb(null, collection.databaseClass); } @@ -246,20 +244,7 @@ module.exports = (function () { // Create properties _.values(collection.orientdbSchema).forEach(function(prop) { - klass.property.create(prop).then(function() { - // TODO: indices - // if(!!collection.attributes[attributeName].unique){ - // db.index.create({ - // name: tableName + '.' + columnName, - // type: 'unique' - // }); - // } else if(!!collection.attributes[attributeName].index){ - // db.index.create({ - // name: tableName + '.' + columnName, - // type: 'notunique' - // }); - // } - }); + klass.property.create(prop).then(); }); // Create transformations @@ -272,17 +257,13 @@ module.exports = (function () { } return newData; } - self.db.registerTransformer(collectionName, transformer); - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ - self.postProcessing(); - } - cb(null, klass); + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ self.postProcessing(); } + + // Create Indexes + self._ensureIndexes(klass, collection.indexes, cb); }); - - // Create links ? - // Create Indexes }; @@ -444,7 +425,37 @@ module.exports = (function () { .then(function(count) { cb(null, count); }); - }; + }; + + + + ///////////////////////////////////////////////////////////////////////////////// + // PRIVATE METHODS + ///////////////////////////////////////////////////////////////////////////////// + /** + * Ensure Indexes + * + * @param {Object} oriento class + * @param {Array} indexes + * @param {Function} callback + * @api private + */ + + DbHelper.prototype._ensureIndexes = function _ensureIndexes(collection, indexes, cb) { + var self = this; + + function createIndex(item, next) { + self.db.index.create(item) + .then(function(index){ next(null, index); }) + .error(next); + } + + async.each(indexes, createIndex, function(err/*, indices*/){ + if(err) { return cb(err); } + cb(null, collection); + }); + }; + diff --git a/test/integration-orientdb/bootstrap.js b/test/integration-orientdb/bootstrap.js index 68b0e0b..fbbff79 100644 --- a/test/integration-orientdb/bootstrap.js +++ b/test/integration-orientdb/bootstrap.js @@ -35,7 +35,9 @@ var fixtures = { FriendFixture: require('./fixtures/hasManyThrough.friend.fixture'), FollowsFixture: require('./fixtures/hasManyThrough.follows.fixture'), - OwnsFixture: require('./fixtures/hasManyThrough.owns.fixture') + OwnsFixture: require('./fixtures/hasManyThrough.owns.fixture'), + + IndexesFixture: require('./fixtures/indexes.fixture') }; diff --git a/test/integration-orientdb/fixtures/indexes.fixture.js b/test/integration-orientdb/fixtures/indexes.fixture.js new file mode 100644 index 0000000..adfd111 --- /dev/null +++ b/test/integration-orientdb/fixtures/indexes.fixture.js @@ -0,0 +1,30 @@ +/** + * Dependencies + */ + +var Waterline = require('waterline'); + +module.exports = Waterline.Collection.extend({ + + tableName : 'indexesTable', + identity : 'indexes', + connection : 'associations', + + attributes : { + name : 'string', + indexUnique : { + type : 'string', + unique : true + }, + indexNotUnique : { + columnName : 'indexDuplicates', + type : 'string', + index : true + }, + propRequired : { + type : 'string', + required : true + } + } + +}); diff --git a/test/integration-orientdb/tests/define/indexes.js b/test/integration-orientdb/tests/define/indexes.js new file mode 100644 index 0000000..2bf0b97 --- /dev/null +++ b/test/integration-orientdb/tests/define/indexes.js @@ -0,0 +1,60 @@ +var assert = require('assert'), + _ = require('lodash'); + +describe('Define related Operations', function() { + + describe('Indexes', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should properly create unique index', function(done) { + Associations.Indexes.getDB(function(db) { + db.index.get('indexesTable.indexUnique') + .then(function(index) { + assert.equal(index.name, 'indexesTable.indexUnique'); + assert.equal(index.type, 'UNIQUE'); + + done(); + }) + .error(done); + }); + }); + + it('should properly create not unique index', function(done) { + Associations.Indexes.getDB(function(db) { + db.index.get('indexesTable.indexDuplicates') + .then(function(index) { + assert.equal(index.name, 'indexesTable.indexDuplicates'); + assert.equal(index.type, 'NOTUNIQUE'); + + done(); + }) + .error(done); + }); + }); + + it('should properly create not mandatory properties', function(done) { + Associations.Indexes.getDB(function(db) { + return db.class.get('indexesTable') + .then(function(klass) { + return klass.property.get('propRequired'); + }) + .then(function(property) { + console.log('--- property', property); + assert.equal(property.name, 'propRequired'); + assert.equal(property.mandatory, true); + + done(); + }) + .error(done); + }); + }); + + }); +}); \ No newline at end of file From d914069c861bc91abb38eeb05d04e6fd4f28d783 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 00:59:17 +0000 Subject: [PATCH 23/81] Only creating indexes for classes that have been created via defined() method Added tests for testing the creation of properties Removed unused ensureIndex code from connection Refactored document a little bit --- lib/collection/document.js | 39 +++---- lib/connection.js | 108 +++--------------- test/integration-orientdb/bootstrap.js | 3 +- ...s.fixture.js => define.indexes.fixture.js} | 6 +- .../fixtures/define.properties.fixture.js | 33 ++++++ .../tests/define/indexes.js | 17 --- .../tests/define/properties.js | 101 ++++++++++++++++ 7 files changed, 174 insertions(+), 133 deletions(-) rename test/integration-orientdb/fixtures/{indexes.fixture.js => define.indexes.fixture.js} (86%) create mode 100644 test/integration-orientdb/fixtures/define.properties.fixture.js create mode 100644 test/integration-orientdb/tests/define/properties.js diff --git a/lib/collection/document.js b/lib/collection/document.js index 63e2fec..04b63ef 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -268,26 +268,33 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll columnName = attributeName, linkedIdentity, linkedDefinition; - - if ( typeof self.schema[attributeName] === 'string') - attributeType = self.schema[attributeName]; - else if ( typeof self.schema[attributeName] === 'function') + + var propertyDefinition = self.schema[attributeName]; + + // definition.definition doesn't seem to contain everything... using definition.attributes ocasionally + var propAttributes = utils.getAttributeAsObject(definition.attributes, columnName); + + //log.debug('table: ' + self.tableName + ', processing attribute ' + attributeName + ':', propertyDefinition); + + if ( typeof propertyDefinition === 'string') + attributeType = propertyDefinition; + else if ( typeof propertyDefinition === 'function') return; - else if (self.schema[attributeName].model || self.schema[attributeName].references) { - linkedIdentity = self.schema[attributeName].model || self.schema[attributeName].references; + else if (propertyDefinition.model || propertyDefinition.references) { + linkedIdentity = propertyDefinition.model || propertyDefinition.references; linkedDefinition = collectionsByIdentity[linkedIdentity]; var useLink = linkedDefinition.primaryKey === 'id'; linkedClass = linkedDefinition.tableName ? linkedDefinition.tableName : linkedDefinition.identity.toLowerCase(); attributeType = useLink ? 'Link' : definition.pkFormat; - } else if (self.schema[attributeName].foreignKey) { + } else if (propertyDefinition.foreignKey) { attributeType = 'Link'; - } else if (self.schema[attributeName].collection) { + } else if (propertyDefinition.collection) { attributeType = 'linkset'; - linkedIdentity = self.schema[attributeName].collection; + linkedIdentity = propertyDefinition.collection; linkedDefinition = collectionsByIdentity[linkedIdentity]; linkedClass = linkedDefinition.tableName ? linkedDefinition.tableName : linkedDefinition.identity.toLowerCase(); } else - attributeType = self.schema[attributeName].type; + attributeType = propertyDefinition.type; if (attributeType === 'array') attributeType = 'embeddedlist'; @@ -297,8 +304,8 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll ['text', 'email', 'alphanumeric', 'alphanumericdashed'].indexOf(attributeType.toLowerCase()) >= 0) attributeType = 'string'; - if (self.schema[attributeName].columnName) - columnName = self.schema[attributeName].columnName; + if (propertyDefinition.columnName) + columnName = propertyDefinition.columnName; if (attributeType) { var prop = { @@ -307,7 +314,6 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll }; // Check for required flag (not super elegant) - var propAttributes = utils.getAttributeAsObject(definition.attributes, columnName); if (propAttributes && !!propAttributes.required) { prop.mandatory = true; } @@ -323,13 +329,6 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll attributeName : columnName, linkedClass : linkedClass }); - - // TODO: temporary, remove afterwards - self.connection.linksToBeCreated.push({ - klass : tableName, - attributeName : columnName, - linkedClass : linkedClass - }); } } }); diff --git a/lib/connection.js b/lib/connection.js index bab6ac7..752aa5e 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -28,7 +28,6 @@ module.exports = (function () { DbHelper = function (db, collections, config, classes) { var self = this; - this.linksToBeCreated = []; //TODO: move this this.db = db; this.collections = collections; this.collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ @@ -54,6 +53,7 @@ module.exports = (function () { // aux variables used to figure out when all collections have been synced this.collectionSync = { + modifiedCollections: [], postProcessed: false, itemsToProcess: _.clone(collections) }; @@ -110,86 +110,6 @@ module.exports = (function () { DbHelper.prototype.db = null; DbHelper.prototype.collections = null; DbHelper.prototype.config = null; - DbHelper.prototype._classes = null; - - - DbHelper.prototype.ensureIndex = function () { - log.debug('Creating indices...', Object.keys(this), this.config); - var deferred = Q.defer(), - db = this.db, - idProp = this.config.idProperty, - indexName = 'V.' + idProp; - - async.auto({ - getVClass: function (next) { - - db.class.get('V') - .then(function (klass, err) { - next(err, klass); - }); - }, - getProps: ['getVClass', - function (next, results) { - var klass = results.getVClass; - klass.property.list() - .then(function (properties, err) { - next(err, properties); - }); - }], - getIdProp: ['getProps', - function (next, results) { - var klass = results.getVClass, - properties = results.getProps, - prop = _.findWhere(properties, { - name: idProp - }); - - if (!prop) { - klass.property.create({ - name: idProp, - type: 'String' - }).then(function (property, err) { - next(err, property); - }); - return; - } - next(null, prop); - }], - ensureIndex: ['getIdProp', - function (next) { - - var createIndex = function (err) { - if (err) { - db.index.create({ - name: indexName, - type: 'unique' - }).then(function (index, err) { - next(err, true); - }); - return; - } - }; - - db.index.get(indexName) - .error(createIndex) - .done(function (index, err) { - //if index not found then create it - return index && next(err, true); - }); - }] - }, - function (err, results) { - if (err) { - log.error('error while creating indices', err); - deferred.reject(err); - return; - } - log.debug('indices created.'); - deferred.resolve(results); - }); - return deferred.promise; - - }; /** @@ -242,6 +162,8 @@ module.exports = (function () { collection.databaseClass = klass; + self.collectionSync.modifiedCollections.push(collection); + // Create properties _.values(collection.orientdbSchema).forEach(function(prop) { klass.property.create(prop).then(); @@ -282,17 +204,19 @@ module.exports = (function () { self.collectionSync.postProcessed = true; log.info('All classes created, post processing'); - - self.linksToBeCreated.forEach(function(link){ - var linkClass = self.newCollections[link.klass].databaseClass; - var linkedClass = self.newCollections[link.linkedClass]; - - linkClass.property.update({ - name : link.attributeName, - linkedClass : linkedClass.tableName - }) - .error(function(err){ - log.error('', err); + + self.collectionSync.modifiedCollections.forEach(function(collection){ + collection.links.forEach(function(link){ + var linkClass = collection.databaseClass; + var linkedClass = self.newCollections[link.linkedClass]; + + linkClass.property.update({ + name : link.attributeName, + linkedClass : linkedClass.tableName + }) + .error(function(err){ + log.error('Failed to create link', err); + }); }); }); diff --git a/test/integration-orientdb/bootstrap.js b/test/integration-orientdb/bootstrap.js index fbbff79..1b02429 100644 --- a/test/integration-orientdb/bootstrap.js +++ b/test/integration-orientdb/bootstrap.js @@ -37,7 +37,8 @@ var fixtures = { FollowsFixture: require('./fixtures/hasManyThrough.follows.fixture'), OwnsFixture: require('./fixtures/hasManyThrough.owns.fixture'), - IndexesFixture: require('./fixtures/indexes.fixture') + IndexesFixture: require('./fixtures/define.indexes.fixture'), + PropertiesFixture: require('./fixtures/define.properties.fixture') }; diff --git a/test/integration-orientdb/fixtures/indexes.fixture.js b/test/integration-orientdb/fixtures/define.indexes.fixture.js similarity index 86% rename from test/integration-orientdb/fixtures/indexes.fixture.js rename to test/integration-orientdb/fixtures/define.indexes.fixture.js index adfd111..cebefde 100644 --- a/test/integration-orientdb/fixtures/indexes.fixture.js +++ b/test/integration-orientdb/fixtures/define.indexes.fixture.js @@ -21,9 +21,9 @@ module.exports = Waterline.Collection.extend({ type : 'string', index : true }, - propRequired : { - type : 'string', - required : true + + props: { + model: 'properties' } } diff --git a/test/integration-orientdb/fixtures/define.properties.fixture.js b/test/integration-orientdb/fixtures/define.properties.fixture.js new file mode 100644 index 0000000..1d1e2b4 --- /dev/null +++ b/test/integration-orientdb/fixtures/define.properties.fixture.js @@ -0,0 +1,33 @@ +/** + * Dependencies + */ + +var Waterline = require('waterline'); + +module.exports = Waterline.Collection.extend({ + + tableName : 'propertiesTable', + identity : 'properties', + connection : 'associations', + + attributes : { + stringProp : { + type : 'string' + }, + textProp : 'string', + jsonProp : 'json', + floatProp : 'float', + propRequired : { + type : 'string', + required : true + }, + modelProp : { + model : 'indexes' + }, + collectionProp : { + collection : 'indexes', + via : 'props' + } + } + +}); diff --git a/test/integration-orientdb/tests/define/indexes.js b/test/integration-orientdb/tests/define/indexes.js index 2bf0b97..de35ca3 100644 --- a/test/integration-orientdb/tests/define/indexes.js +++ b/test/integration-orientdb/tests/define/indexes.js @@ -38,23 +38,6 @@ describe('Define related Operations', function() { .error(done); }); }); - - it('should properly create not mandatory properties', function(done) { - Associations.Indexes.getDB(function(db) { - return db.class.get('indexesTable') - .then(function(klass) { - return klass.property.get('propRequired'); - }) - .then(function(property) { - console.log('--- property', property); - assert.equal(property.name, 'propRequired'); - assert.equal(property.mandatory, true); - - done(); - }) - .error(done); - }); - }); }); }); \ No newline at end of file diff --git a/test/integration-orientdb/tests/define/properties.js b/test/integration-orientdb/tests/define/properties.js new file mode 100644 index 0000000..ee2ab01 --- /dev/null +++ b/test/integration-orientdb/tests/define/properties.js @@ -0,0 +1,101 @@ +var assert = require('assert'), + _ = require('lodash'), + Oriento = require('oriento'); + +describe('Define related Operations', function() { + + describe('Property creation', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var klass; + + before(function(done) { + Associations.Properties.getDB(function(db) { + db.class.get('propertiesTable') + .then(function(myClass) { + klass = myClass; + done(); + }) + .catch(done); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should properly create mandatory property', function(done) { + klass.property.get('propRequired') + .then(function(property) { + assert.equal(property.name, 'propRequired'); + assert.equal(property.mandatory, true); + done(); + }) + .error(done); + }); + + it('should properly create string property from string', function(done) { + klass.property.get('stringProp') + .then(function(property) { + assert.equal(property.name, 'stringProp'); + assert.equal(Oriento.types[property.type], 'String'); + done(); + }) + .error(done); + }); + + it('should properly create string property from text', function(done) { + klass.property.get('textProp') + .then(function(property) { + assert.equal(Oriento.types[property.type], 'String'); + done(); + }) + .error(done); + }); + + it('should properly create float property from float', function(done) { + klass.property.get('floatProp') + .then(function(property) { + assert.equal(Oriento.types[property.type], 'Float'); + done(); + }) + .error(done); + }); + + it('should properly create Embedded property from json', function(done) { + klass.property.get('jsonProp') + .then(function(property) { + assert.equal(Oriento.types[property.type], 'Embedded'); + done(); + }) + .error(done); + }); + + it('should properly create Link property from model', function(done) { + klass.property.get('modelProp') + .then(function(property) { + assert.equal(Oriento.types[property.type], 'Link'); + assert.equal(property.linkedClass, 'indexesTable'); + done(); + }) + .error(done); + }); + + // Not sure this can happen seen it's only required a Links exists on the associated table + // it('should properly create LinkSet property from collection', function(done) { + // klass.property.get('collectionProp') + // .then(function(property) { + // assert.equal(Oriento.types[property.type], 'LinkSet'); + // assert.equal(property.linkedClass, 'indexesTable'); + // done(); + // }) + // .error(done); + // }); + + + }); +}); \ No newline at end of file From ba0d9f29f9b27ca24db9e6fe1fdd5179d1077ea2 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 01:26:45 +0000 Subject: [PATCH 24/81] Removed connection.collections and replaced it by newCollections --- lib/associations.js | 8 +- lib/collection/document.js | 12 +-- lib/connection.js | 182 ++++++++++++++++----------------- test/unit/associations.test.js | 23 ++--- 4 files changed, 111 insertions(+), 114 deletions(-) diff --git a/lib/associations.js b/lib/associations.js index 6739f79..48aeaab 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -57,7 +57,7 @@ Associations.prototype.join = function join(collectionName, criteria, cb) { Associations.prototype.getFetchPlan = function getFetchPlan(collectionName, criteria, fetchPlanLevel) { var self = this; fetchPlanLevel = fetchPlanLevel || 1; - var parentSchema = self.connection.collections[collectionName].attributes; + var parentSchema = self.connection.newCollections[collectionName].attributes; var fetchPlan = ''; var select = []; var associations = []; @@ -152,7 +152,7 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr var options = _.clone(criteria); options.fetchPlan = self.getFetchPlan(collectionName, criteria, fetchPlanLevel); - var parentSchema = self.connection.collections[collectionName].attributes; + var parentSchema = self.connection.newCollections[collectionName].attributes; self.connection.find(collectionName, options, function(err, results){ if(err) @@ -168,7 +168,7 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr expandedResults.forEach(function(record){ criteria.joins.forEach(function(join){ - var childTableSchema = self.connection.collections[join.child].attributes; + var childTableSchema = self.connection.newCollections[join.child].attributes; if(join.parent === collectionName){ if(join.removeParentKey) @@ -331,7 +331,7 @@ Associations.prototype.genericJoin = function genericJoin(collectionName, criter */ $getPK: function (collectionIdentity) { if (!collectionIdentity) return; - var schema = connectionObject.collections[collectionIdentity].attributes; + var schema = connectionObject.newCollections[collectionIdentity].attributes; if(!schema) return 'id'; diff --git a/lib/collection/document.js b/lib/collection/document.js index 04b63ef..f8984e9 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -30,6 +30,9 @@ var Document = module.exports = function Document(definition, connection, databa // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) this.classCommand = undefined; + // Hold collection definition + this.attributes = null; + // Hold Schema Information this.schema = null; @@ -238,11 +241,8 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll // Hold the Schema this.schema = collectionDef.definition; - - // TODO: not sure why sails-mongo does this... - // if (_.has(this.schema, 'id') && this.schema.id.primaryKey && this.schema.id.type === 'integer') { - // this.schema.id.type = 'objectid'; - // } + + this.attributes = definition.attributes; // Set the tableName var tableName = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); @@ -272,7 +272,7 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll var propertyDefinition = self.schema[attributeName]; // definition.definition doesn't seem to contain everything... using definition.attributes ocasionally - var propAttributes = utils.getAttributeAsObject(definition.attributes, columnName); + var propAttributes = utils.getAttributeAsObject(self.attributes, columnName); //log.debug('table: ' + self.tableName + ', processing attribute ' + attributeName + ':', propertyDefinition); diff --git a/lib/connection.js b/lib/connection.js index 752aa5e..d1c555e 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -13,99 +13,97 @@ var Oriento = require('oriento'), module.exports = (function () { var defaults = { - createCustomIndex: false, - idProperty: 'id', - options: { - fetchPlanLevel: 1, - parameterized: false - } - }, - dbDefaults = { - type: 'graph', - storage: 'plocal' - }, - server, + createCustomIndex: false, + idProperty: 'id', + options: { + fetchPlanLevel: 1, + parameterized: false + } + }, + dbDefaults = { + type: 'graph', + storage: 'plocal' + }, + server, + + DbHelper = function (db, collections, config, classes) { + var self = this; + this.db = db; + this.collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ + accumulator[collection.identity] = collection; + return accumulator; + }, {}); + var auxDefaults = _.merge({}, defaults); + this.config = _.merge(auxDefaults, config); + this.associations = new Associations(this); + this.server = server; - DbHelper = function (db, collections, config, classes) { - var self = this; - this.db = db; - this.collections = collections; - this.collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ - accumulator[collection.identity] = collection; - return accumulator; - }, {}); - var auxDefaults = _.merge({}, defaults); - this.config = _.merge(auxDefaults, config); - this.associations = new Associations(this); - this.server = server; - - var dbClasses = {}; - classes.forEach(function(klass){ - dbClasses[klass.name] = klass; - }); - this.dbClasses = dbClasses; - - // Build up a registry of collections - this.newCollections = {}; // TODO: replace this.collections - Object.keys(collections).forEach(function(key) { - self.newCollections[key] = new Collection(collections[key], self, dbClasses[key], self.collectionsByIdentity); - }); - - // aux variables used to figure out when all collections have been synced - this.collectionSync = { - modifiedCollections: [], - postProcessed: false, - itemsToProcess: _.clone(collections) - }; - }, + this.dbClasses = {}; + classes.forEach(function(klass){ + self.dbClasses[klass.name] = klass; + }); - ensureDB = function (connectionProps) { - var dbProps = (typeof connectionProps.database === 'object') ? connectionProps.database : { name: connectionProps.database }; - dbProps.username = connectionProps.user; - dbProps.password = connectionProps.password; - if(connectionProps.options.storage){ - dbProps.storage = connectionProps.options.storage; - } - dbProps = _.extend({}, dbDefaults, dbProps); - var deferred = Q.defer(); - log.debug('Looking for database', connectionProps.database); - - server.list() - .then(function(dbs) { - var dbExists = _.find(dbs, function(db) { - return db.name === dbProps.name; - }); - if (dbExists) { - log.debug('database found.'); - deferred.resolve(server.use(dbProps)); - } else { - log.debug('database not found, will create it.'); - server.create(dbProps).then(function(db) { - deferred.resolve(db); - }); - } + // Build up a registry of collections + this.newCollections = {}; // TODO: replace this.collections + Object.keys(collections).forEach(function(key) { + self.newCollections[key] = new Collection(collections[key], self, self.dbClasses[key], self.collectionsByIdentity); + }); + + // aux variables used to figure out when all collections have been synced + this.collectionSync = { + modifiedCollections: [], + postProcessed: false, + itemsToProcess: _.clone(collections) + }; + }, + + ensureDB = function (connectionProps) { + var dbProps = (typeof connectionProps.database === 'object') ? connectionProps.database : { name: connectionProps.database }; + dbProps.username = connectionProps.user; + dbProps.password = connectionProps.password; + if(connectionProps.options.storage){ + dbProps.storage = connectionProps.options.storage; + } + dbProps = _.extend({}, dbDefaults, dbProps); + var deferred = Q.defer(); + log.debug('Looking for database', connectionProps.database); + + server.list() + .then(function(dbs) { + var dbExists = _.find(dbs, function(db) { + return db.name === dbProps.name; + }); + if (dbExists) { + log.debug('database found.'); + deferred.resolve(server.use(dbProps)); + } else { + log.debug('database not found, will create it.'); + server.create(dbProps).then(function(db) { + deferred.resolve(db); }); - - return deferred.promise; - }, - getDb = function (connection) { - var orientoConnection = { - host : connection.host, - port : connection.host, - username : connection.user, - password : connection.password, - transport : connection.transport || 'binary', - enableRIDBags : false, - useToken : false - }; - - if (!server) { - log.info('Connecting to database...'); - server = new Oriento(orientoConnection); } - - return ensureDB(connection); - }; + }); + + return deferred.promise; + }, + getDb = function (connection) { + var orientoConnection = { + host : connection.host, + port : connection.host, + username : connection.user, + password : connection.password, + transport : connection.transport || 'binary', + enableRIDBags : false, + useToken : false + }; + + if (!server) { + log.info('Connecting to database...'); + server = new Oriento(orientoConnection); + } + + return ensureDB(connection); + }; DbHelper.prototype.db = null; DbHelper.prototype.collections = null; @@ -309,21 +307,21 @@ module.exports = (function () { * */ DbHelper.prototype.createEdge = function(from, to, options, cb) { - var attributes, + var schema, klass = 'E'; cb = cb || _.noop; options = options || {}; if(options['@class']){ klass = options['@class']; - attributes = this.collections[klass] && this.collections[klass].attributes; + schema = this.newCollections[klass] && this.newCollections[klass].schema; } this.db.create('EDGE', klass).from(from).to(to) .set(options) .one() .then(function(res) { - cb(null, utils.rewriteIds(res, attributes)); + cb(null, utils.rewriteIds(res, schema)); }) .error(cb); }; diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index b490d62..e2a6f88 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -3,7 +3,7 @@ */ var assert = require('assert'), util = require('util'), - Edge = require('../../lib/collection').Edge, + Collection = require('../../lib/collection'), Associations = require('../../lib/associations'), _ = require('lodash'); @@ -22,18 +22,17 @@ var connectionMock = { collectionsByIdentity: collections }; -collections.authored_comment.waterline = { schema: {} }; -collections.authored_comment.definition = collections.authored_comment.attributes; -collections.comment_parent.waterline = { schema: {} }; -collections.comment_parent.definition = collections.comment_parent.attributes; -collections.comment_recipe.waterline = { schema: {} }; -collections.comment_recipe.definition = collections.comment_recipe.attributes; -var newCollections = { - authored_comment: new Edge(collections.authored_comment, connectionMock, null, collections), - comment_parent: new Edge(collections.comment_parent, connectionMock, null, collections), - comment_recipe: new Edge(collections.comment_recipe, connectionMock, null, collections), -}; +var newCollections = {}; +Object.keys(collections).forEach(function(key){ + collections[key].waterline = { schema: {} }; + collections[key].definition = collections[key].attributes; + newCollections[key] = new Collection(collections[key], connectionMock, null, collections); +}); +newCollections.authored_comment = new Collection.Edge(collections.authored_comment, connectionMock, null, collections); +newCollections.comment_parent = new Collection.Edge(collections.comment_parent, connectionMock, null, collections); +newCollections.comment_recipe = new Collection.Edge(collections.comment_recipe, connectionMock, null, collections); connectionMock.newCollections = newCollections; + var associations = new Associations(connectionMock); From 84944cb87af8857943d6ee41e8f81cb56e35c19f Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 01:46:46 +0000 Subject: [PATCH 25/81] Refactor: connection.newCollections are now connection.collections --- lib/associations.js | 12 ++++++------ lib/connection.js | 20 ++++++++++---------- test/unit/associations.test.js | 3 +-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/associations.js b/lib/associations.js index 48aeaab..6d90603 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -57,7 +57,7 @@ Associations.prototype.join = function join(collectionName, criteria, cb) { Associations.prototype.getFetchPlan = function getFetchPlan(collectionName, criteria, fetchPlanLevel) { var self = this; fetchPlanLevel = fetchPlanLevel || 1; - var parentSchema = self.connection.newCollections[collectionName].attributes; + var parentSchema = self.connection.collections[collectionName].attributes; var fetchPlan = ''; var select = []; var associations = []; @@ -152,7 +152,7 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr var options = _.clone(criteria); options.fetchPlan = self.getFetchPlan(collectionName, criteria, fetchPlanLevel); - var parentSchema = self.connection.newCollections[collectionName].attributes; + var parentSchema = self.connection.collections[collectionName].attributes; self.connection.find(collectionName, options, function(err, results){ if(err) @@ -168,7 +168,7 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr expandedResults.forEach(function(record){ criteria.joins.forEach(function(join){ - var childTableSchema = self.connection.newCollections[join.child].attributes; + var childTableSchema = self.connection.collections[join.child].attributes; if(join.parent === collectionName){ if(join.removeParentKey) @@ -331,7 +331,7 @@ Associations.prototype.genericJoin = function genericJoin(collectionName, criter */ $getPK: function (collectionIdentity) { if (!collectionIdentity) return; - var schema = connectionObject.newCollections[collectionIdentity].attributes; + var schema = connectionObject.collections[collectionIdentity].attributes; if(!schema) return 'id'; @@ -358,7 +358,7 @@ Associations.prototype.genericJoin = function genericJoin(collectionName, criter * @api private */ Associations.prototype.getEdgeSides = function getEdgeSides(collectionName) { - var edge = this.connection.newCollections[collectionName]; + var edge = this.connection.collections[collectionName]; return edge.edgeSides; }; @@ -398,7 +398,7 @@ Associations.prototype.isThroughJoin = function isThroughJoin(criteria) { for(var i=0; i < criteria.joins.length; i++){ var join = criteria.joins[i]; - var collectionInstance = self.connection.newCollections[join.parent]; + var collectionInstance = self.connection.collections[join.parent]; if(collectionInstance instanceof Edge) return true; } diff --git a/lib/connection.js b/lib/connection.js index d1c555e..e366866 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -44,9 +44,9 @@ module.exports = (function () { }); // Build up a registry of collections - this.newCollections = {}; // TODO: replace this.collections + this.collections = {}; Object.keys(collections).forEach(function(key) { - self.newCollections[key] = new Collection(collections[key], self, self.dbClasses[key], self.collectionsByIdentity); + self.collections[key] = new Collection(collections[key], self, self.dbClasses[key], self.collectionsByIdentity); }); // aux variables used to figure out when all collections have been synced @@ -124,7 +124,7 @@ module.exports = (function () { } // TODO: Fetch properties from db and generate a schema - var collectionInstance = self.newCollections[collectionName]; + var collectionInstance = self.collections[collectionName]; if(collectionInstance.databaseClass) { cb(null, collectionInstance.schema); if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ @@ -145,7 +145,7 @@ module.exports = (function () { DbHelper.prototype.createCollection = function createCollection(collectionName, definition, cb) { var self = this; - var collection = self.newCollections[collectionName]; + var collection = self.collections[collectionName]; // Create the Collection if (collection.databaseClass) { @@ -206,7 +206,7 @@ module.exports = (function () { self.collectionSync.modifiedCollections.forEach(function(collection){ collection.links.forEach(function(link){ var linkClass = collection.databaseClass; - var linkedClass = self.newCollections[link.linkedClass]; + var linkedClass = self.collections[link.linkedClass]; linkClass.property.update({ name : link.attributeName, @@ -259,7 +259,7 @@ module.exports = (function () { * Retrieves records of class collection that fulfill the criteria in options */ DbHelper.prototype.find = function(collection, options, cb) { - this.newCollections[collection].find(options, cb); + this.collections[collection].find(options, cb); }; @@ -280,7 +280,7 @@ module.exports = (function () { * Creates a new document from a collection */ DbHelper.prototype.create = function(collection, options, cb) { - this.newCollections[collection].insert(options, cb); + this.collections[collection].insert(options, cb); }; @@ -288,7 +288,7 @@ module.exports = (function () { * Updates a document from a collection */ DbHelper.prototype.update = function(collection, options, values, cb) { - this.newCollections[collection].update(options, values, cb); + this.collections[collection].update(options, values, cb); }; @@ -296,7 +296,7 @@ module.exports = (function () { * Deletes a document from a collection */ DbHelper.prototype.destroy = function(collection, options, cb) { - this.newCollections[collection].destroy(options, cb); + this.collections[collection].destroy(options, cb); }; @@ -314,7 +314,7 @@ module.exports = (function () { if(options['@class']){ klass = options['@class']; - schema = this.newCollections[klass] && this.newCollections[klass].schema; + schema = this.collections[klass] && this.collections[klass].schema; } this.db.create('EDGE', klass).from(from).to(to) diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index e2a6f88..beaf516 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -18,7 +18,6 @@ var collections = { var connectionMock = { config: { options: {fetchPlanLevel: 1} }, - collections: collections, collectionsByIdentity: collections }; @@ -31,7 +30,7 @@ Object.keys(collections).forEach(function(key){ newCollections.authored_comment = new Collection.Edge(collections.authored_comment, connectionMock, null, collections); newCollections.comment_parent = new Collection.Edge(collections.comment_parent, connectionMock, null, collections); newCollections.comment_recipe = new Collection.Edge(collections.comment_recipe, connectionMock, null, collections); -connectionMock.newCollections = newCollections; +connectionMock.collections = newCollections; var associations = new Associations(connectionMock); From c92a0383a06680388a01cf914642fca646933e95 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 10:57:47 +0000 Subject: [PATCH 26/81] Moved sequel-waterline-orientdb initialisation and reference to connection Each collection no longer holds a reference to waterline.schema --- lib/adapter.js | 9 ++++++--- lib/collection/document.js | 14 ++++---------- lib/connection.js | 24 ++++++++++++++++++++++-- lib/document.js | 2 +- lib/query.js | 28 +++++----------------------- 5 files changed, 38 insertions(+), 39 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index bb95986..c3cebda 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -4,7 +4,8 @@ */ var Connection = require('./connection'), Associations = require('./associations'), - utils = require('./utils'); + utils = require('./utils'), + log = require('debug-logger')('waterline-orientdb:adapter'); /** * waterline-orientdb @@ -91,8 +92,6 @@ module.exports = (function() { // Waterline only allows populating 1 level below. fetchPlanLevel allows to // to populate further levels below fetchPlanLevel : 1, - // Turns on parameterized queries and it should be enabled, but breaks 2 tests against 1.7.*. - // https://github.com/appscot/waterline-orientdb/issues/20 parameterized : true } }, @@ -109,6 +108,7 @@ module.exports = (function() { * @return {[type]} [description] */ registerConnection : function(connection, collections, cb) { + log.debug('registerConnection:', connection.database); if (!connection.identity) return cb(new Error('Connection is missing an identity.')); @@ -163,6 +163,7 @@ module.exports = (function() { * @param {Function} callback */ describe : function(connection, collection, cb) { + log.debug('describe:', collection); // Add in logic here to describe a collection (e.g. DESCRIBE TABLE // logic) connections[connection].describe(collection, cb); @@ -182,6 +183,7 @@ module.exports = (function() { * @param {Function} cb */ define : function(connection, collection, definition, cb) { + log.debug('define:', collection); // Create the collection and indexes connections[connection].createCollection(collection, definition, cb); @@ -199,6 +201,7 @@ module.exports = (function() { * @param {Function} callback */ drop : function(connection, collection, relations, cb) { + log.debug('drop:', collection); // Add in logic here to delete a collection (e.g. DROP TABLE logic) return connections[connection].drop(collection, relations, cb); diff --git a/lib/collection/document.js b/lib/collection/document.js index f8984e9..9d24169 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -38,9 +38,6 @@ var Document = module.exports = function Document(definition, connection, databa // Hold Schema in OrientDB friendly format this.orientdbSchema = null; - - // Hold the waterline schema, used by query namely waterline-sequel-orientdb - this.waterlineSchema = null; // Hold a reference to an active connection this.connection = connection; @@ -76,7 +73,7 @@ Document.prototype.find = function find(criteria, cb) { var _query, query; try { - _query = new Query(criteria, self.waterlineSchema, self.connection.config); + _query = new Query(criteria, self.connection.sequel); } catch(err) { return cb(err); } try { @@ -156,7 +153,7 @@ Document.prototype.update = function update(criteria, values, cb) { // Catch errors from building query and return to the callback try { - _query = new Query(criteria, self.waterlineSchema, self.connection.config); + _query = new Query(criteria, self.connection.sequel); _document = new Doc(values, self.schema, self.connection); where = _query.getWhereQuery(self.tableName); } catch(e) { @@ -251,10 +248,8 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll // Set the identity var identity = definition.identity ? definition.identity : definition.tableName; this.identity = _.clone(identity); - - this.waterlineSchema = definition.waterline.schema; - - + + // Create orientdbSchema this.orientdbSchema = {}; Object.keys(self.schema).forEach(function(attributeName) { @@ -340,7 +335,6 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll * * @api private */ - Document.prototype._buildIndexes = function _buildIndexes() { var self = this; diff --git a/lib/connection.js b/lib/connection.js index e366866..66a8d9a 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -7,8 +7,18 @@ var Oriento = require('oriento'), utils = require('./utils'), Associations = require('./associations'), log = require('debug-logger')('waterline-orientdb:connection'), - Collection = require('./collection'); - + Collection = require('./collection'), + Sequel = require('waterline-sequel-orientdb'); + +// waterline-sequel-orientdb options +var sqlOptions = { + parameterized : true, + caseSensitive : false, + escapeCharacter : '', + casting : false, + canReturnValues : true, + escapeInserts : true +}; module.exports = (function () { @@ -38,6 +48,16 @@ module.exports = (function () { this.associations = new Associations(this); this.server = server; + // update sqlOptions config + sqlOptions.parameterized = _.isUndefined(this.config.options.parameterized) ? + sqlOptions.parameterized : this.config.options.parameterized; + // Hold the waterline schema, used by query namely waterline-sequel-orientdb + this.waterlineSchema = _.values(collections)[0].waterline.schema; + + // Instantiate a sequel helper + this.sequel = new Sequel(this.waterlineSchema, sqlOptions); + + // Stores existings classes from OrientDB this.dbClasses = {}; classes.forEach(function(klass){ self.dbClasses[klass.name] = klass; diff --git a/lib/document.js b/lib/document.js index f85605c..af6e8ce 100644 --- a/lib/document.js +++ b/lib/document.js @@ -34,7 +34,7 @@ var Document = module.exports = function Document(values, schema, connection) { if(values){ var newValues = this.setValues(values); this.values = newValues.values; - } + } return this; }; diff --git a/lib/query.js b/lib/query.js index 54ce069..232bad1 100644 --- a/lib/query.js +++ b/lib/query.js @@ -5,19 +5,7 @@ var _ = require('lodash'), utils = require('./utils'), hop = utils.object.hop, - Sequel = require('waterline-sequel-orientdb'), RID = require('oriento').RID; - - -// waterline-sequel-orientdb options -var sqlOptions = { - parameterized : true, - caseSensitive : false, - escapeCharacter : '', - casting : false, - canReturnValues : true, - escapeInserts : true -}; /** @@ -28,19 +16,13 @@ var sqlOptions = { * @param {Object} options * @api private */ -var Query = module.exports = function Query(options, schema, adapterConfig) { - // Apply configs - sqlOptions.parameterized = _.isUndefined(adapterConfig.options.parameterized) ? - sqlOptions.parameterized : adapterConfig.options.parameterized; - - // Cache the schema for use in parseTypes - this.schema = schema; - +var Query = module.exports = function Query(options, sequel) { + + // Sequel builder + this.sequel = sequel; + // Normalize Criteria this.criteria = this.normalizeCriteria(options); - - // Instantiate a sequel helper - this.sequel = new Sequel(schema, sqlOptions); return this; }; From e5ded578cbe6ae277e6161ae569b0cada61ac65d Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 11:06:55 +0000 Subject: [PATCH 27/81] connection.collectionsById now hold Collection objects instead of waterline's original collections (for coherence) --- lib/collection/document.js | 5 +++++ lib/connection.js | 6 ++++-- test/unit/associations.test.js | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index 9d24169..1c62e8e 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -38,6 +38,9 @@ var Document = module.exports = function Document(definition, connection, databa // Hold Schema in OrientDB friendly format this.orientdbSchema = null; + + // Hold the primary key from definition + this.primaryKey = ''; // Hold a reference to an active connection this.connection = connection; @@ -240,6 +243,8 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll this.schema = collectionDef.definition; this.attributes = definition.attributes; + + this.primaryKey = _.clone(definition.primaryKey); // Set the tableName var tableName = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); diff --git a/lib/connection.js b/lib/connection.js index 66a8d9a..792f5c1 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -39,7 +39,7 @@ module.exports = (function () { DbHelper = function (db, collections, config, classes) { var self = this; this.db = db; - this.collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ + var collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ accumulator[collection.identity] = collection; return accumulator; }, {}); @@ -65,8 +65,10 @@ module.exports = (function () { // Build up a registry of collections this.collections = {}; + this.collectionsByIdentity = {}; Object.keys(collections).forEach(function(key) { - self.collections[key] = new Collection(collections[key], self, self.dbClasses[key], self.collectionsByIdentity); + self.collections[key] = new Collection(collections[key], self, self.dbClasses[key], collectionsByIdentity); + self.collectionsByIdentity[self.collections[key].identity] = self.collections[key]; }); // aux variables used to figure out when all collections have been synced diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index beaf516..5db2dba 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -17,8 +17,7 @@ var collections = { }; var connectionMock = { - config: { options: {fetchPlanLevel: 1} }, - collectionsByIdentity: collections + config: { options: {fetchPlanLevel: 1} } }; var newCollections = {}; @@ -31,6 +30,7 @@ newCollections.authored_comment = new Collection.Edge(collections.authored_comme newCollections.comment_parent = new Collection.Edge(collections.comment_parent, connectionMock, null, collections); newCollections.comment_recipe = new Collection.Edge(collections.comment_recipe, connectionMock, null, collections); connectionMock.collections = newCollections; +connectionMock.collectionsByIdentity = newCollections; var associations = new Associations(connectionMock); From 23799ddbdd196210b8848712a63152629ad1da0d Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 11:12:33 +0000 Subject: [PATCH 28/81] Refactor: Moved associations.getEdge() to edge._getEdge() --- lib/associations.js | 21 --------------------- lib/collection/edge.js | 22 ++++++++++++++++++++-- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/associations.js b/lib/associations.js index 6d90603..b4a5811 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -363,27 +363,6 @@ Associations.prototype.getEdgeSides = function getEdgeSides(collectionName) { }; - -/** - * Get edge - * - * Normalizes data for edge creation - * - * @param {Object} values - * @return {Object} - * @api private - */ -Associations.prototype.getEdge = function getEdge(collectionName, values) { - var edgeSides = this.getEdgeSides(collectionName); - return { - from : values && values[edgeSides.out.junctionTableColumnName], - to : values && values[edgeSides.in.junctionTableColumnName], - keys: [edgeSides.out.junctionTableColumnName, edgeSides.in.junctionTableColumnName] - }; -}; - - - /** * Is many-to-many through join * diff --git a/lib/collection/edge.js b/lib/collection/edge.js index e265011..72757b0 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -41,7 +41,7 @@ var Edge = module.exports = utils.extend(Document, function() { Edge.prototype.find = function find(criteria, cb) { var self = this; - var edge = self.connection.associations.getEdge(self.tableName, criteria.where); + var edge = self._getEdge(self.tableName, criteria.where); if (edge) { // Replace foreign keys with from and to @@ -67,7 +67,7 @@ Edge.prototype.find = function find(criteria, cb) { Edge.prototype.insert = function insert(values, cb) { var self = this; - var edge = self.connection.associations.getEdge(self.tableName, values); + var edge = self._getEdge(self.tableName, values); if (edge) { // Create edge @@ -173,3 +173,21 @@ Edge.prototype._getEdgeSides = function _getEdgeSides(collectionsByIdentity) { } }; + +/** + * Get edge + * + * Normalizes data for edge creation + * + * @param {Object} values + * @return {Object} + * @api private + */ +Edge.prototype._getEdge = function _getEdge(collectionName, values) { + var self = this; + return { + from : values && values[self.edgeSides.out.junctionTableColumnName], + to : values && values[self.edgeSides.in.junctionTableColumnName], + keys: [self.edgeSides.out.junctionTableColumnName, self.edgeSides.in.junctionTableColumnName] + }; +}; From fa7139892d170df9ab4a6402a4d91f38386d4c2e Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 12:34:44 +0000 Subject: [PATCH 29/81] Delete vertices and edges before dropping the class Moved drop logic from connection to document/edge/vertex No longer dropping collections unsafely. --- lib/collection/document.js | 14 ++++++++++++- lib/collection/edge.js | 16 +++++++++++++++ lib/collection/vertex.js | 27 +++++++++++++++++++++++-- lib/connection.js | 23 +++++++++------------ test/integration-orientdb/bugs/index.js | 5 ++++- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index 1c62e8e..c0350f1 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -204,7 +204,7 @@ Document.prototype.destroy = function destroy(criteria, cb) { if(results.length === 0){ return cb(null, results); } var rids = _.pluck(results, 'id'); - log.debug('deleting rids: ' + rids); + log.debug('destroy: deleting rids: ' + rids); self.connection.db.delete(self.classCommand) .where('@rid in [' + rids.join(',') + ']') @@ -221,6 +221,18 @@ Document.prototype.destroy = function destroy(criteria, cb) { }; +//Deletes a collection from database +Document.prototype.drop = function (relations, cb) { + var self = this; + self.databaseClass = null; + + self.connection.db.class.drop(self.tableName) + .then(function (res) { + cb(null, res); + }) + .error(cb); +}; + ///////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 72757b0..fdedd10 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -81,6 +81,22 @@ Edge.prototype.insert = function insert(values, cb) { }; +//Deletes a collection from database +Edge.prototype.drop = function (relations, cb) { + var self = this; + + // If class doesn't exist don't delete records + if(!self.databaseClass){ + return self.$super.prototype.drop.call(self, relations, cb); + } + + self.connection.db.delete(self.classCommand).one() + .then(function (/*count*/) { + self.$super.prototype.drop.call(self, relations, cb); + }) + .error(cb); +}; + ///////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js index a2f4368..a3109d8 100644 --- a/lib/collection/vertex.js +++ b/lib/collection/vertex.js @@ -10,8 +10,7 @@ var utils = require('../utils'), * @param {Connection} connection * @api public */ -//var Vertex = -module.exports = utils.extend(Document, function() { +var Vertex = module.exports = utils.extend(Document, function() { Document.apply(this, arguments); // Set the orientdb super class ('document' / V / E) for this document @@ -20,3 +19,27 @@ module.exports = utils.extend(Document, function() { // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) this.classCommand = 'VERTEX'; }); + + + +///////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +///////////////////////////////////////////////////////////////////////////////// + + +//Deletes a collection from database +Vertex.prototype.drop = function (relations, cb) { + var self = this; + + // If class doesn't exist don't delete records + if(!self.databaseClass){ + return self.$super.prototype.drop.call(self, relations, cb); + } + + self.connection.db.delete(self.classCommand).one() + .then(function (/*count*/) { + self.$super.prototype.drop.call(self, relations, cb); + }) + .error(cb); +}; + diff --git a/lib/connection.js b/lib/connection.js index 792f5c1..6044f20 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -149,9 +149,9 @@ module.exports = (function () { var collectionInstance = self.collections[collectionName]; if(collectionInstance.databaseClass) { cb(null, collectionInstance.schema); - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ - self.postProcessing(); - } + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ + self.postProcessing(); + } } cb(); }; @@ -285,17 +285,12 @@ module.exports = (function () { }; - //Deletes a collection from database - DbHelper.prototype.drop = function (collection, relations, cb) { - - //return this.db.class.drop(collection) - return this.db.query('DROP CLASS ' + collection + ' UNSAFE') - .then(function (res) { - cb(null, res); - }) - .error(cb); - }; - + /** + * Deletes a collection from database + */ + DbHelper.prototype.drop = function (collectionName, relations, cb) { + this.collections[collectionName].drop(relations, cb); + }; /** diff --git a/test/integration-orientdb/bugs/index.js b/test/integration-orientdb/bugs/index.js index f1bc7bd..800d33a 100644 --- a/test/integration-orientdb/bugs/index.js +++ b/test/integration-orientdb/bugs/index.js @@ -83,7 +83,10 @@ global.DELETE_TEST_WATERLINE = function(dbName, cb){ } async.each(Object.keys(ontology.collections), dropCollection, function(err) { - if(err) return cb(err); + if(err) { + console.log('ERROR dropping collections:', err); + return cb(err); + }; ontology.collections[Object.keys(ontology.collections)[0]].getServer(function(server){ server.drop({ From 9424c9fef6b05e98c48494782245531bd2a5d43f Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 18:08:39 +0000 Subject: [PATCH 30/81] Fixes little nasty bug where dropping one Vertex/Edge would drop all of them --- lib/collection/edge.js | 5 +++-- lib/collection/vertex.js | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/collection/edge.js b/lib/collection/edge.js index fdedd10..b05728e 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -90,8 +90,9 @@ Edge.prototype.drop = function (relations, cb) { return self.$super.prototype.drop.call(self, relations, cb); } - self.connection.db.delete(self.classCommand).one() - .then(function (/*count*/) { + self.connection.db.delete(self.classCommand, self.tableName).one() + .then(function (count) { + log.debug('Drop [' + self.tableName + '], deleted records: ' + count); self.$super.prototype.drop.call(self, relations, cb); }) .error(cb); diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js index a3109d8..9da3faa 100644 --- a/lib/collection/vertex.js +++ b/lib/collection/vertex.js @@ -1,7 +1,8 @@ "use strict"; var utils = require('../utils'), - Document = require('./document'); + Document = require('./document'), + log = require('debug-logger')('waterline-orientdb:vertex'); /** * Manage A Vertex @@ -36,8 +37,9 @@ Vertex.prototype.drop = function (relations, cb) { return self.$super.prototype.drop.call(self, relations, cb); } - self.connection.db.delete(self.classCommand).one() - .then(function (/*count*/) { + self.connection.db.delete(self.classCommand, self.tableName).one() + .then(function (count) { + log.debug('Drop [' + self.tableName + '], deleted records: ' + count); self.$super.prototype.drop.call(self, relations, cb); }) .error(cb); From fa9e4ea67843cfe35ab772c6313d2ed302d266fd Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 19:22:41 +0000 Subject: [PATCH 31/81] Improve debugging output --- lib/adapter.js | 5 +++-- lib/collection/document.js | 17 +++++++++++------ lib/collection/edge.js | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index c3cebda..56b1ec8 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -136,6 +136,7 @@ module.exports = (function() { * @return {[type]} [description] */ teardown : function(conn, cb) { + log.debug('teardown:', conn); if ( typeof conn == 'function') { cb = conn; @@ -168,8 +169,8 @@ module.exports = (function() { // logic) connections[connection].describe(collection, cb); }, - - + + /** * Define * diff --git a/lib/collection/document.js b/lib/collection/document.js index c0350f1..e4db2cd 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -86,21 +86,22 @@ Document.prototype.find = function find(criteria, cb) { return cb(e); } - log.debug('OrientDB query:', query.query[0]); + log.debug('Find query:', query.query[0]); var opts = { params: query.params || {} }; if(query.params){ - log.debug('params:', opts); + log.debug('Find params:', opts); } if(criteria.fetchPlan){ opts.fetchPlan = criteria.fetchPlan.where; - log.debug('opts.fetchPlan:', opts.fetchPlan); + log.debug('Find fetchPlan:', opts.fetchPlan); } self.connection.db .query(query.query[0], opts) .all() .then(function (res) { + log.debug('Find results: ' + (res && res.length)); if (res && criteria.fetchPlan) { //log.debug('res', res); cb(null, utils.rewriteIdsRecursive(res, self.schema)); @@ -127,17 +128,20 @@ Document.prototype.insert = function insert(values, cb) { _document = new Doc(values, self.schema, self.connection); + log.debug('Insert into [' + self.tableName + '] values:', _document.values); + self.connection.db.insert() .into(self.tableName) .set(_document.values) .one() .then(function(res) { + log.debug('Insert result: ' + res['@rid']); cb(null, utils.rewriteIds(res, self.schema)); }) .error(function(err) { - log.error('Failed to create object. DB error.', err); + log.error('Failed to create object in [' + self.tableName + ']. DB error:', err); cb(err); - }); + }); }; /** @@ -204,7 +208,7 @@ Document.prototype.destroy = function destroy(criteria, cb) { if(results.length === 0){ return cb(null, results); } var rids = _.pluck(results, 'id'); - log.debug('destroy: deleting rids: ' + rids); + log.debug('Destroy rids: ' + rids); self.connection.db.delete(self.classCommand) .where('@rid in [' + rids.join(',') + ']') @@ -228,6 +232,7 @@ Document.prototype.drop = function (relations, cb) { self.connection.db.class.drop(self.tableName) .then(function (res) { + log.debug('Dropped [' + self.tableName + ']'); cb(null, res); }) .error(cb); diff --git a/lib/collection/edge.js b/lib/collection/edge.js index b05728e..98b9f3e 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -86,7 +86,7 @@ Edge.prototype.drop = function (relations, cb) { var self = this; // If class doesn't exist don't delete records - if(!self.databaseClass){ + if(!self.databaseClass){ return self.$super.prototype.drop.call(self, relations, cb); } From 55df8469990aec608358221dedfe95ec71a21267 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 19:24:17 +0000 Subject: [PATCH 32/81] Preventing insertions with id/@rid, as these fail silently --- lib/collection/document.js | 2 +- lib/document.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index e4db2cd..f07854f 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -126,7 +126,7 @@ Document.prototype.insert = function insert(values, cb) { var self = this, _document; - _document = new Doc(values, self.schema, self.connection); + _document = new Doc(values, self.schema, self.connection, 'insert'); log.debug('Insert into [' + self.tableName + '] values:', _document.values); diff --git a/lib/document.js b/lib/document.js index af6e8ce..906c601 100644 --- a/lib/document.js +++ b/lib/document.js @@ -19,7 +19,7 @@ var _ = require('lodash'), * @api private */ -var Document = module.exports = function Document(values, schema, connection) { +var Document = module.exports = function Document(values, schema, connection, operation) { // Keep track of the current document's values this.values = {}; @@ -29,6 +29,9 @@ var Document = module.exports = function Document(values, schema, connection) { // Connection this.connection = connection; + + // operation type (create / insert) + this.operation = operation; // If values were passed in, use the setter if(values){ @@ -80,8 +83,10 @@ Document.prototype.normalizeId = function normalizeId(values) { if(_.isString(values.id) && utils.matchRecordId(values.id)) { values.id = new RID(values.id); } - - values['@rid'] = values.id; + + if(!this.operation || this.operation !== 'insert'){ + values['@rid'] = values.id; + } delete values.id; }; From 2e24db702df4575a4381935e4347b55e31c01687 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 19:25:54 +0000 Subject: [PATCH 33/81] Describe: simple implementation, fetches existing properties from database. Doesn't check indexes or required flag --- lib/connection.js | 48 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 6044f20..e8d7d2e 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -119,10 +119,10 @@ module.exports = (function () { useToken : false }; - if (!server) { + if (!server) { log.info('Connecting to database...'); server = new Oriento(orientoConnection); - } + } return ensureDB(connection); }; @@ -145,17 +145,37 @@ module.exports = (function () { delete self.collectionSync.itemsToProcess[collectionName]; } - // TODO: Fetch properties from db and generate a schema - var collectionInstance = self.collections[collectionName]; - if(collectionInstance.databaseClass) { - cb(null, collectionInstance.schema); + var collection = self.collections[collectionName]; + if(!collection.databaseClass) { return cb(); } + + var schema = {}; + + collection.databaseClass.property.list() + .then(function(properties){ + + // TODO: don't copy collection.schema blindly, check mandatory and indices! + _.forEach(properties, function(property){ + if(collection.schema[property.name]){ + schema[property.name] = collection.schema[property.name]; + } + // else { + // // TODO: include properties found in database which are not in collection.schema + // } + }); + + if(collection.schema.id){ + schema.id = collection.schema.id; + } + + cb(null, schema); if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ self.postProcessing(); } - } - cb(); + }); + + // TODO: fetch indexes }; - + /** * Create Collection @@ -204,7 +224,10 @@ module.exports = (function () { if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ self.postProcessing(); } // Create Indexes - self._ensureIndexes(klass, collection.indexes, cb); + self._ensureIndexes(klass, collection.indexes, function(err/*, result*/){ + if(err) { return cb(err); } + cb(null, collection.schema); + }); }); }; @@ -389,10 +412,7 @@ module.exports = (function () { .error(next); } - async.each(indexes, createIndex, function(err/*, indices*/){ - if(err) { return cb(err); } - cb(null, collection); - }); + async.each(indexes, createIndex, cb); }; From 3d7c73e072b785270f7b6c47d0130ef164ec51d4 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 19:26:19 +0000 Subject: [PATCH 34/81] Enabled Migrations (migratable interface) --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c73e567..4aeaeb3 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "istanbul": "~0.3.5", "jshint": "~2.6.0", "mocha": "*", - "waterline": "~0.10.12", + "waterline": "~0.10.18", "waterline-adapter-tests": "~0.10.8" }, "waterlineAdapter": { @@ -59,8 +59,9 @@ "interfaces": [ "semantic", "queryable", - "associations" + "associations", + "migratable" ], - "waterlineVersion": "~0.10.12" + "waterlineVersion": "~0.10.18" } } From 26b05325397d0bf7d27afd93487a77b0960d4d45 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 19:31:51 +0000 Subject: [PATCH 35/81] Dependency: update lodash --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4aeaeb3..e56e7dc 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "async": "~0.9.0", "debug": "~2.1.0", "debug-logger": "~0.2.0", - "lodash": "~3.2.0", + "lodash": "^3.3.0", "oriento": "~1.1.0", "q": "^1.0.1", "waterline-cursor": "~0.0.5", From ee7dc7ee63803bc1d174448c9428ab9a846079e1 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 21:48:44 +0000 Subject: [PATCH 36/81] Fixed document.destroy() method Document DBs are now supported --- lib/collection/document.js | 60 +++++++++++++++++++++++--------------- lib/collection/edge.js | 38 +++++++++++++++++++++++- lib/collection/vertex.js | 37 +++++++++++++++++++++++ 3 files changed, 110 insertions(+), 25 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index f07854f..1bd74f8 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -135,7 +135,7 @@ Document.prototype.insert = function insert(values, cb) { .set(_document.values) .one() .then(function(res) { - log.debug('Insert result: ' + res['@rid']); + log.debug('Insert result id: ' + res['@rid']); cb(null, utils.rewriteIds(res, self.schema)); }) .error(function(err) { @@ -198,30 +198,42 @@ Document.prototype.update = function update(criteria, values, cb) { * @api public */ Document.prototype.destroy = function destroy(criteria, cb) { - var self = this; - cb = cb || _.noop; + var _query, + where, + self = this; + + // Catch errors from building query and return to the callback + try { + _query = new Query(criteria, self.connection.sequel); + where = _query.getWhereQuery(self.tableName); + } catch(e) { + log.error('Destroy [' + self.tableName + ']: failed to compose destroy SQL query.', e); + return cb(e); + } + + var query = self.connection.db.delete() + .from(self.tableName) + .return('BEFORE'); - // TODO: should be in a transaction - self.connection.find(self.tableName, criteria, function(err, results){ - if(err){ return cb(err); } - - if(results.length === 0){ return cb(null, results); } - - var rids = _.pluck(results, 'id'); - log.debug('Destroy rids: ' + rids); - - self.connection.db.delete(self.classCommand) - .where('@rid in [' + rids.join(',') + ']') - .one() - .then(function (count) { - if(parseInt(count) !== rids.length){ - return cb(new Error('Deleted count [' + count + '] does not match rids length [' + rids.length + - '], some vertices may not have been deleted')); - } - cb(null, results); - }) - .error(cb); - }); + if(where.query[0]){ + log.debug('Destroy [' + self.tableName + '] where:', where.query[0]); + query.where(where.query[0]); + if(where.params){ + log.debug('Destroy [' + self.tableName + '] params:', where.params); + query.addParams(where.params); + } + } + + query + .all() + .then(function(res) { + log.debug('Destroy [' + self.tableName + '] deleted records: ' + (res && res.length)); + cb(null, utils.rewriteIds(res, self.schema)); + }) + .error(function(err) { + log.error('Destroy [' + self.tableName + ']: failed to update, error:', err); + cb(err); + }); }; diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 98b9f3e..19d65ba 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -1,6 +1,7 @@ "use strict"; -var utils = require('../utils'), +var _ = require('lodash'), + utils = require('../utils'), Document = require('./document'), log = require('debug-logger')('waterline-orientdb:edge'); @@ -81,6 +82,41 @@ Edge.prototype.insert = function insert(values, cb) { }; +/** + * Destroy Documents + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ +Edge.prototype.destroy = function destroy(criteria, cb) { + var self = this; + cb = cb || _.noop; + + // TODO: should be in a transaction + self.connection.find(self.tableName, criteria, function(err, results){ + if(err){ return cb(err); } + + if(results.length === 0){ return cb(null, results); } + + var rids = _.pluck(results, 'id'); + log.debug('Destroy rids: ' + rids); + + self.connection.db.delete(self.classCommand) + .where('@rid in [' + rids.join(',') + ']') + .one() + .then(function (count) { + if(parseInt(count) !== rids.length){ + return cb(new Error('Deleted count [' + count + '] does not match rids length [' + rids.length + + '], some vertices may not have been deleted')); + } + cb(null, results); + }) + .error(cb); + }); +}; + + //Deletes a collection from database Edge.prototype.drop = function (relations, cb) { var self = this; diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js index 9da3faa..fc4bff6 100644 --- a/lib/collection/vertex.js +++ b/lib/collection/vertex.js @@ -1,6 +1,7 @@ "use strict"; var utils = require('../utils'), + _ = require('lodash'), Document = require('./document'), log = require('debug-logger')('waterline-orientdb:vertex'); @@ -28,6 +29,42 @@ var Vertex = module.exports = utils.extend(Document, function() { ///////////////////////////////////////////////////////////////////////////////// +/** + * Destroy Documents + * + * @param {Object} criteria + * @param {Function} callback + * @api public + */ +Vertex.prototype.destroy = function destroy(criteria, cb) { + var self = this; + cb = cb || _.noop; + + // TODO: should be in a transaction + self.connection.find(self.tableName, criteria, function(err, results){ + if(err){ return cb(err); } + + if(results.length === 0){ return cb(null, results); } + + var rids = _.pluck(results, 'id'); + log.debug('Destroy rids: ' + rids); + + self.connection.db.delete(self.classCommand) + .where('@rid in [' + rids.join(',') + ']') + .one() + .then(function (count) { + if(parseInt(count) !== rids.length){ + return cb(new Error('Deleted count [' + count + '] does not match rids length [' + rids.length + + '], some vertices may not have been deleted')); + } + cb(null, results); + }) + .error(cb); + }); +}; + + + //Deletes a collection from database Vertex.prototype.drop = function (relations, cb) { var self = this; From 55d61483fa3712e772677a2951aad33e69680fa1 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 21:51:04 +0000 Subject: [PATCH 37/81] database type is now configured through config.options.databaseType. The default is set to graph --- lib/adapter.js | 4 +++- lib/collection/index.js | 2 +- lib/connection.js | 8 +++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 56b1ec8..0bd76e9 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -92,7 +92,9 @@ module.exports = (function() { // Waterline only allows populating 1 level below. fetchPlanLevel allows to // to populate further levels below fetchPlanLevel : 1, - parameterized : true + parameterized : true, + databaseType : 'graph', + storage : 'plocal' } }, diff --git a/lib/collection/index.js b/lib/collection/index.js index 51006c4..f52ec98 100644 --- a/lib/collection/index.js +++ b/lib/collection/index.js @@ -3,7 +3,7 @@ var utils = require('../utils'); var Collection = module.exports = function Collection (definition, connection, databaseClass, collectionsByIdentity) { - if(connection.config.databaseType === 'document' || definition.orientdbClass === 'document'){ + if(connection.config.options.databaseType === 'document' || definition.orientdbClass === 'document'){ return new Collection.Document(definition, connection, databaseClass, collectionsByIdentity); } diff --git a/lib/connection.js b/lib/connection.js index e8d7d2e..1eb6fc0 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -30,10 +30,6 @@ module.exports = (function () { parameterized: false } }, - dbDefaults = { - type: 'graph', - storage: 'plocal' - }, server, DbHelper = function (db, collections, config, classes) { @@ -86,7 +82,9 @@ module.exports = (function () { if(connectionProps.options.storage){ dbProps.storage = connectionProps.options.storage; } - dbProps = _.extend({}, dbDefaults, dbProps); + if(connectionProps.options.databaseType){ + dbProps.type = connectionProps.options.databaseType; + } var deferred = Q.defer(); log.debug('Looking for database', connectionProps.database); From b52f7e906121393e72f7054cabd0ca440963f7f9 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Fri, 20 Feb 2015 21:54:44 +0000 Subject: [PATCH 38/81] 'test-integration' will read db type from DATABASE_TYPE env variable. Added makefiile target 'test-integration-documentdb' to run the adapter integration tests against a document db Added makefiile target 'test-all' to run the normal tests plus the adapter integration tests against a document db --- Makefile | 9 ++++++++- test/integration/runner.js | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c61b81f..2290e74 100644 --- a/Makefile +++ b/Makefile @@ -4,16 +4,23 @@ REPORTER = spec DB?=waterline-test-integration test: test-unit test-integration-all +test-all: test-clean test-integration-documentdb test-clean: test-unit test-integration-all clean test-integration-all: test-integration-orientdb test-integration -test-integration: + +test-integration-generic: @echo "\n\nNOTICE: If tests fail, please ensure you've set the correct credentials in test/test-connection.json\n" @echo "Running 'waterline-adapter-tests' integration tests..." + +test-integration: test-integration-generic @NODE_ENV=test node test/integration/runner.js + +test-integration-documentdb: test-integration-generic + @NODE_ENV=test DATABASE_TYPE=document node test/integration/runner.js test-integration-orientdb: @echo "\n\nNOTICE: If tests fail, please ensure you've set the correct credentials in test/test-connection.json\n" diff --git a/test/integration/runner.js b/test/integration/runner.js index 513d39b..7614c04 100644 --- a/test/integration/runner.js +++ b/test/integration/runner.js @@ -22,7 +22,7 @@ var Adapter = require('../../'); var config = require('../test-connection.json'); config.database = 'waterline-test-integration'; // We need different DB's due to https://github.com/orientechnologies/orientdb/issues/3301 - +config.options.databaseType = process.env.DATABASE_TYPE || config.options.databaseType || Adapter.defaults.options.databaseType; // Grab targeted interfaces from this adapter's `package.json` file: var package = {}; @@ -47,6 +47,7 @@ catch (e) { log.info('Testing `' + package.name + '`, a Sails/Waterline adapter.'); log.info('Running `waterline-adapter-tests` against ' + interfaces.length + ' interfaces...'); log.info('( ' + interfaces.join(', ') + ' )'); +log.info('With database type: ' + config.options.databaseType); console.log(); log.info('Latest draft of Waterline adapter interface spec:'); log.info('https://github.com/balderdashy/sails-docs/blob/master/contributing/adapter-specification.md'); @@ -107,3 +108,6 @@ new TestRunner({ // Full interface reference: // https://github.com/balderdashy/sails-docs/blob/master/contributing/adapter-specification.md }); + + + From bbd97688d8d1c34b494501e289562e4f68b2496d Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sat, 21 Feb 2015 01:17:38 +0000 Subject: [PATCH 39/81] Unit tests added for collection plus a few fixes For many-to-many associations waterline-orientdb will use "document" class (for now) --- lib/collection/edge.js | 14 +++- lib/collection/index.js | 6 +- test/unit/associations.test.js | 1 - test/unit/collection.test.js | 119 +++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 test/unit/collection.test.js diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 19d65ba..d7a49f3 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -24,7 +24,7 @@ var Edge = module.exports = utils.extend(Document, function() { // Information about edge's in and out properties this.edgeSides = null; - this._getEdgeSides(arguments[3]); + this._getEdgeSides(arguments[0], arguments[3]); }); @@ -148,7 +148,7 @@ Edge.prototype.drop = function (relations, cb) { * @return {Object} * @api private */ -Edge.prototype._getEdgeSides = function _getEdgeSides(collectionsByIdentity) { +Edge.prototype._getEdgeSides = function _getEdgeSides(definition, collectionsByIdentity) { var self = this, vertexA, vertexB; @@ -198,6 +198,16 @@ Edge.prototype._getEdgeSides = function _getEdgeSides(collectionsByIdentity) { log.error('Too many associations! Unable to process model [' + self.identity + '] attribute [' + key + '].'); }); + if(!vertexA){ + if(definition.junctionTable){ + log.warn('No vertexes found for edge [' + self.tableName + '] and this edge is marked as waterline junction ' + + 'table. Association operations referenced by this edge will probably fail. Please check your schema.'); + } else { + log.info('No vertexes found for edge [' + self.tableName + '].'); + } + return; + } + if(!vertexA.dominant && !vertexB.dominant){ var dominantVertex = (vertexA.junctionTableKey < vertexB.junctionTableKey) ? vertexA : vertexB; dominantVertex.dominant = true; diff --git a/lib/collection/index.js b/lib/collection/index.js index f52ec98..14340c3 100644 --- a/lib/collection/index.js +++ b/lib/collection/index.js @@ -3,7 +3,11 @@ var utils = require('../utils'); var Collection = module.exports = function Collection (definition, connection, databaseClass, collectionsByIdentity) { - if(connection.config.options.databaseType === 'document' || definition.orientdbClass === 'document'){ + if(connection.config.options.databaseType === 'document' || + definition.orientdbClass === '' || + definition.orientdbClass === 'document' || + (definition.junctionTable && !utils.isJunctionTableThrough(definition) && + definition.orientdbClass !== 'V' && definition.orientdbClass !== 'E')){ return new Collection.Document(definition, connection, databaseClass, collectionsByIdentity); } diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index 5db2dba..c1ca43b 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -22,7 +22,6 @@ var connectionMock = { var newCollections = {}; Object.keys(collections).forEach(function(key){ - collections[key].waterline = { schema: {} }; collections[key].definition = collections[key].attributes; newCollections[key] = new Collection(collections[key], connectionMock, null, collections); }); diff --git a/test/unit/collection.test.js b/test/unit/collection.test.js new file mode 100644 index 0000000..2313a06 --- /dev/null +++ b/test/unit/collection.test.js @@ -0,0 +1,119 @@ +/** + * Test dependencies + */ +var assert = require('assert'), + Collection = require('../../lib/collection'), + _ = require('lodash'); + + +describe('collection class', function () { + + var defaultModel = { + identity: 'default', + attributes: { name: 'string' }, + definition: { name: 'string' } + }; + + var collections = {}; + collections.defaultModel = _.defaults({ }, defaultModel); + collections.documentModel1 = _.defaults({ orientdbClass: '' }, defaultModel); + collections.documentModel2 = _.defaults({ orientdbClass: 'document' }, defaultModel); + collections.vertexModel = _.defaults({ orientdbClass: 'V' }, defaultModel); + collections.edgeModel = _.defaults({ orientdbClass: 'E' }, defaultModel); + collections.junctionModelThrough = _.defaults({ junctionTable: true }, defaultModel); + collections.junctionModelThroughD = _.defaults({ orientdbClass: '', junctionTable: true }, defaultModel); + collections.junctionModelThroughV = _.defaults({ orientdbClass: 'V', junctionTable: true }, defaultModel); + collections.junctionModel = _.defaults({ + identity : 'driver_taxis__taxi_drivers', + tableName : 'driver_taxis__taxi_drivers', + junctionTable : true + }, defaultModel); + collections.junctionModelE = _.defaults({ + orientdbClass: 'E', + identity : 'driver_taxis__taxi_drivers', + tableName : 'driver_taxis__taxi_drivers', + junctionTable : true + }, defaultModel); + + junctionTable: true, + + before(function(done){ + done(); + }); + + describe('document database', function () { + + var documentConnectionMock = { config: { options: { databaseType: 'document' } } }; + + it('constructor: should instantiate a document regardless of orientdbClass value', function (done) { + _.values(collections).forEach(function(collection){ + var doc = new Collection(collection, documentConnectionMock, null, null); + assert(doc instanceof Collection.Document); + assert(!(doc instanceof Collection.Vertex)); + assert(!(doc instanceof Collection.Edge)); + }); + done(); + }); + }); + + describe('graph database', function () { + + var graphConnectionMock = { config: { options: { databaseType: 'graph' } } }; + + it('constructor: should instantiate a document if orientdbClass is "" or "document"', function (done) { + var doc = new Collection(collections.documentModel1, graphConnectionMock, null, null); + assert(doc instanceof Collection.Document); + assert(!(doc instanceof Collection.Vertex)); + assert(!(doc instanceof Collection.Edge)); + doc = new Collection(collections.documentModel2, graphConnectionMock, null, null); + assert(doc instanceof Collection.Document); + assert(!(doc instanceof Collection.Vertex)); + assert(!(doc instanceof Collection.Edge)); + doc = new Collection(collections.junctionModelThroughD, graphConnectionMock, null, null); + assert(doc instanceof Collection.Document); + assert(!(doc instanceof Collection.Vertex)); + assert(!(doc instanceof Collection.Edge)); + + done(); + }); + + it('constructor: should instantiate a document if table is junction table for a many-to-many association', function (done) { + var doc = new Collection(collections.junctionModel, graphConnectionMock, null, null); + assert(doc instanceof Collection.Document); + assert(!(doc instanceof Collection.Vertex)); + assert(!(doc instanceof Collection.Edge)); + + done(); + }); + + it('constructor: should instantiate a vertex if orientdbClass is undefined or "V"', function (done) { + var vertex = new Collection(collections.defaultModel, graphConnectionMock, null, null); + assert(vertex instanceof Collection.Document); + assert(vertex instanceof Collection.Vertex); + vertex = new Collection(collections.vertexModel, graphConnectionMock, null, null); + assert(vertex instanceof Collection.Vertex); + vertex = new Collection(collections.junctionModelThroughV, graphConnectionMock, null, null); + assert(vertex instanceof Collection.Vertex); + + done(); + }); + + it('constructor: should instantiate an edge if orientdbClass is "E"', function (done) { + var edge = new Collection(collections.edgeModel, graphConnectionMock, null, null); + assert(edge instanceof Collection.Edge); + edge = new Collection(collections.junctionModelE, graphConnectionMock, null, null); + assert(edge instanceof Collection.Edge); + + done(); + }); + + it('constructor: should instantiate an edge if table is junction table for a many-to-many through association', function (done) { + var edge = new Collection(collections.junctionModelThrough, graphConnectionMock, null, null); + assert(edge instanceof Collection.Edge); + done(); + }); + + }); + + +}); From 857c00b0d44dc4640091fa0bc25619e6393a8cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Sat, 21 Feb 2015 02:09:18 +0000 Subject: [PATCH 40/81] Update README.md --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 699b3b8..4007007 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Build Status](https://travis-ci.org/appscot/waterline-orientdb.svg?branch=master)](https://travis-ci.org/appscot/waterline-orientdb) [![Test Coverage](https://codeclimate.com/github/appscot/waterline-orientdb/badges/coverage.svg)](https://codeclimate.com/github/appscot/waterline-orientdb) [![dependencies](https://david-dm.org/appscot/waterline-orientdb.svg)](https://david-dm.org/appscot/waterline-orientdb) -[![devDependencies](https://david-dm.org/appscot/waterline-orientdb/dev-status.svg)](https://david-dm.org/appscot/waterline-orientdb#info=devDependencies) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/appscot/waterline-orientdb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) # waterline-orientdb @@ -16,7 +15,7 @@ Waterline adapter for OrientDB. [Waterline](https://github.com/balderdashy/water #### Development Status * Waterline-orientdb aims to work with Waterline v0.10.x and OrientDB v1.7.10 and later. While it may work with earlier versions, they are not currently supported, [pull requests are welcome](./CONTRIBUTING.md)! -* From the waterline [adapter interfaces](https://github.com/balderdashy/sails-docs/blob/master/contributing/adapter-specification.md) waterline-orientdb fully supports `Semantic`, `Queryable` and `Associations` interfaces. +* From the waterline [adapter interfaces](https://github.com/balderdashy/sails-docs/blob/master/contributing/adapter-specification.md) waterline-orientdb fully supports `Semantic`, `Queryable`, `Associations` and `Migratable` interfaces. Waterline-orientdb passes all integration tests from [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests). * Many-to-many associations currently use a junction table instead of an edge and this will change at some point ([#29](https://github.com/appscot/waterline-orientdb/issues/29)). @@ -83,27 +82,55 @@ To learn how to create associations with Waterline/Sails.js check the Waterline For One-to-One Associations waterline-orientdb creates a LINK ([OrientDB Types](http://www.orientechnologies.com/docs/last/orientdb.wiki/Types.html)) to associate records. ###### One-to-Many Associations -One-to-Many Associations are represented in OrientDB by a LINKSET. +One-to-Many Associations are also represented by a LINK in OrienDB. ###### Many-to-Many Associations -Many-to-Many Associations are handled by Waterline core, creating a join table holding foreign keys to the associated records. Waterline-orientdb does not change this behaviour for now but we will replace the join table by Edges in a future release ([#29](https://github.com/appscot/waterline-orientdb/issues/29)). Currently it's not deemed a priority. +Many-to-Many Associations are handled by Waterline, creating a join table holding foreign keys to the associated records. Waterline-orientdb does not change this behaviour for now but we will replace the join table by Edges in a future release ([#29](https://github.com/appscot/waterline-orientdb/issues/29)). Currently it's not deemed a priority. ###### Many-to-Many Through Associations -In Many-to-Many Through Association the join table is represented in OrientDB by Edges. Waterline-orientdb automatically creates the edges whenever an association is created. The Edge is named after the property tableName or identity in case tableName is missing. +In a [Many-to-Many Through Association](https://github.com/balderdashy/waterline-docs/blob/master/associations.md#many-to-many-through-associations) ([more info](https://github.com/balderdashy/waterline/issues/705#issuecomment-60945411)) the join table is represented in OrientDB by Edges. Waterline-orientdb automatically creates the edges whenever an association is created. The Edge is named after the property tableName or identity in case tableName is missing. #### sails-orientdb differences ###### Edge creation -The main difference between waterline-orientdb and [sails-orientdb](https://github.com/vjsrinath/sails-orientdb) is the way associations/edges are created. In `sails-orientdb` a special attribute named 'edge' is required while waterline-orientdb tries to adhere to waterline specficiation. +The main difference between waterline-orientdb and [sails-orientdb](https://github.com/vjsrinath/sails-orientdb) is the way associations/edges are created. In `sails-orientdb` a special attribute named 'edge' is required while waterline-orientdb tries to adhere to waterline specification. ###### ID -Waterline-orientdb mimics sails-mongo adapter behaviour and maps the logical `id` attribute to the required `@rid` physical-layer OrientDB Record ID. +Waterline-orientdb mimics sails-mongo adapter behaviour and maps the logical `id` attribute to the required `@rid` physical-layer OrientDB Record ID. Because of this it's not necessary, or advised, to declare an `id` attribute on your model definitions. ## Usage +#### Models + +`waterline-orientdb` uses the standard [waterline model definition](https://github.com/balderdashy/waterline-docs/blob/master/models.md) and extends it in order to accommodate OrientDB feature. + +###### orientdbClass + +It's possible to force the class of a model by adding the property `orientdbClass` to the definition. Generally this is not required as `waterline-orientdb` can determine which is the best class to use, so it should only be used in special cases. Possible values: +* `""` or `"document"` - class will be the default OrientDB document class; +* `"V"`- class will be Vertex; +* `"E"`- class will be Edge. + +Example: +```javascript +{ + identity : 'post', + orientdbClass : 'V' + + attributes : { + name : 'string' + } +} +``` + +Note, when using a document database (through `config.options.databaseType`), orientdbClass class will be ignored and all classes will be documents + + +#### Methods + This adapter adds the following methods: -###### `createEdge(from, to, options, callback)` +###### createEdge(from, to, options, callback) Creates edge between specified two model instances by ID in the form parameters `from` and `to` usage: @@ -114,7 +141,7 @@ usage: }); ``` -###### `deleteEdges(from, to, options, callback)` +###### deleteEdges(from, to, options, callback) Deletes edges between specified two model instances by ID in the form parameters `from` and `to` usage: @@ -125,7 +152,7 @@ usage: }); ``` -###### `query(connection, collection, query, [options], cb)` +###### query(query, [options], cb) Runs a SQL query against the database using Oriento's query method. Will attempt to convert @rid's into ids. usage: @@ -145,7 +172,7 @@ usage: }); ``` -###### `getDB(connection, collection, cb)` +###### getDB(cb) Returns a native Oriento object usage: @@ -156,7 +183,7 @@ usage: }); ``` -###### `getServer(connection, collection, cb)` +###### getServer(cb) Returns a native Oriento connection usage: @@ -166,7 +193,7 @@ usage: }); ``` -###### `removeCircularReferences(connection, collection, object, cb)` +###### removeCircularReferences(connection, collection, object, cb) Convenience method that replaces circular references with `id` when one is available, otherwise it replaces the object with string '[Circular]' usage: From 9e71a1e79d78773d69894327035da74e05638d2e Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 17:59:30 +0000 Subject: [PATCH 41/81] Makefile: new "clean-all" target which is now ran before at the beginning of make test "clean" and "clean-all" never return error for convenience --- Makefile | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 2290e74..196d87a 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,8 @@ MOCHA_OPTS= --check-leaks --timeout 6000 REPORTER = spec DB?=waterline-test-integration -test: test-unit test-integration-all -test-all: test-clean test-integration-documentdb - -test-clean: test-unit test-integration-all clean +test: clean-all test-unit test-integration-all +test-all: test clean test-integration-documentdb test-integration-all: test-integration-orientdb test-integration @@ -52,7 +50,15 @@ coverage: clean: @echo "\n\nDROPPING ALL COLLECTIONS from db: $(DB)" + @echo "NOTICE: If operation fails, please ensure you've set the correct credentials in oriento.opts file" + @echo "Note: you can choose which db to drop by appending 'DB=', e.g. 'make clean DB=waterline-test-orientdb'\n" + ./node_modules/.bin/oriento db drop $(DB) || true + +clean-all: + @echo "\n\nDROPPING DATABASES: waterline-test-integration, waterline-test-orientdb" @echo "NOTICE: If operation fails, please ensure you've set the correct credentials in oriento.opts file\n" - ./node_modules/.bin/oriento db drop $(DB) + ./node_modules/.bin/oriento db drop waterline-test-integration > /dev/null 2>&1 || true + ./node_modules/.bin/oriento db drop waterline-test-orientdb > /dev/null 2>&1 || true + @echo "Done" .PHONY: coverage From 715aed38ce3cc56ea77d35351e260e2d12d3e83f Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 18:03:32 +0000 Subject: [PATCH 42/81] Issue #29: Many-to-many associations using edges --- lib/associations.js | 28 ++++++++++++++---------- lib/collection/edge.js | 39 ++++++++++++++++++++-------------- lib/collection/index.js | 10 +++------ test/unit/associations.test.js | 16 ++++++++++++-- test/unit/collection.test.js | 15 ++++++------- 5 files changed, 63 insertions(+), 45 deletions(-) diff --git a/lib/associations.js b/lib/associations.js index b4a5811..ccb261e 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -37,7 +37,7 @@ var Associations = module.exports = function Associations(connection) { */ Associations.prototype.join = function join(collectionName, criteria, cb) { //TODO: for now we only use fetch plan for many-to-many through associations. Use it for all associations - if(this.isThroughJoin(criteria)) + if(this.isEdgeJoin(criteria)) return this.fetchPlanJoin(collectionName, criteria, cb); return this.genericJoin(collectionName, criteria, cb); @@ -77,7 +77,7 @@ Associations.prototype.getFetchPlan = function getFetchPlan(collectionName, crit return; } - // Many-to-many through associations (edges) + // Edges associations.push(join.parent); var edgeSides = self.getEdgeSides(join.parent); @@ -155,10 +155,11 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr var parentSchema = self.connection.collections[collectionName].attributes; self.connection.find(collectionName, options, function(err, results){ - if(err) + if(err) { return cb(err); - else if(!results || results.length === 0) + } else if(!results || results.length === 0) { return cb(null, results); + } var normalisedResults = []; var keysToDelete = []; @@ -196,8 +197,6 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr } //Process record - //TODO: record may be array - if(!parentSide || !record[parentSide.referencedAttributeEdge]){ return; //it probably has been processed already } @@ -207,7 +206,7 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr delete record[parentSide.referencedAttributeEdge]; record[join.alias] = utils.rewriteIds(record[join.alias], childTableSchema); - + record[join.alias].forEach(function(associatedRecord){ utils.cleanOrientAttributes(associatedRecord, childTableSchema); }); @@ -370,17 +369,24 @@ Associations.prototype.getEdgeSides = function getEdgeSides(collectionName) { * @return {Boolean} * @api public */ -Associations.prototype.isThroughJoin = function isThroughJoin(criteria) { +Associations.prototype.isEdgeJoin = function isThroughJoin(criteria) { var self = this; if(!criteria.joins) return false; + var result = false; + for(var i=0; i < criteria.joins.length; i++){ var join = criteria.joins[i]; var collectionInstance = self.connection.collections[join.parent]; - if(collectionInstance instanceof Edge) - return true; + if(collectionInstance instanceof Collection.Edge){ + result = true; + } else if(collectionInstance instanceof Collection.Vertex){ + continue; + } else if(collectionInstance instanceof Collection.Document){ + return false; + } } - return false; + return result; }; \ No newline at end of file diff --git a/lib/collection/edge.js b/lib/collection/edge.js index d7a49f3..ee4a1f4 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -148,35 +148,42 @@ Edge.prototype.drop = function (relations, cb) { * @return {Object} * @api private */ -Edge.prototype._getEdgeSides = function _getEdgeSides(definition, collectionsByIdentity) { +Edge.prototype._getEdgeSides = function _getEdgeSides(definition) { var self = this, vertexA, vertexB; + log.debug('_getEdgeSides: finding vertexes for' + self.tableName); + Object.keys(self.schema).forEach(function(key) { - var reference = self.schema[key].model || self.schema[key].references; + var reference = self.schema[key].references; if(!reference) return; - var referencedCollection = collectionsByIdentity[reference]; + var referencedCollection = _.find(_.values(self.connection.waterlineSchema), { identity: reference }); var referencedSchema = referencedCollection.attributes; var referencedAttributeKey; Object.keys(referencedSchema).forEach(function(referencedSchemaKey) { var attribute = referencedSchema[referencedSchemaKey]; - if(attribute.through === self.identity && attribute.via === key) - referencedAttributeKey = referencedSchemaKey; + if(self.identity === attribute.collection && (key === attribute.on || key === attribute.via)){ + if(!referencedAttributeKey){ + log.debug('_getEdgeSides: match found for ' + key + ': ' + referencedSchemaKey); + referencedAttributeKey = referencedSchemaKey; + } + else { + // More than one match, let's use via + // Logic for collections with associations to themselves + if(key === attribute.via){ + log.debug('_getEdgeSides: match found for ' + key + ': ' + referencedSchemaKey); + referencedAttributeKey = referencedSchemaKey; + } + } + } }); if(!referencedAttributeKey){ - Object.keys(referencedSchema).forEach(function(referencedSchemaKey) { - var attribute = referencedSchema[referencedSchemaKey]; - // Optimistic attribute assignment... - if(attribute.through === self.identity) - referencedAttributeKey = referencedSchemaKey; - }); - } - if(!referencedAttributeKey) return; + } var referencedAttribute = referencedSchema[referencedAttributeKey]; @@ -198,12 +205,12 @@ Edge.prototype._getEdgeSides = function _getEdgeSides(definition, collectionsByI log.error('Too many associations! Unable to process model [' + self.identity + '] attribute [' + key + '].'); }); - if(!vertexA){ + if(!vertexA || !vertexB){ if(definition.junctionTable){ - log.warn('No vertexes found for edge [' + self.tableName + '] and this edge is marked as waterline junction ' + + log.warn('Vertex(es) missing for edge [' + self.tableName + '] and this edge is marked as waterline junction ' + 'table. Association operations referenced by this edge will probably fail. Please check your schema.'); } else { - log.info('No vertexes found for edge [' + self.tableName + '].'); + log.info('Vertex(es) missing for edge [' + self.tableName + '].'); } return; } diff --git a/lib/collection/index.js b/lib/collection/index.js index 14340c3..af72078 100644 --- a/lib/collection/index.js +++ b/lib/collection/index.js @@ -1,18 +1,14 @@ "use strict"; -var utils = require('../utils'); - var Collection = module.exports = function Collection (definition, connection, databaseClass, collectionsByIdentity) { if(connection.config.options.databaseType === 'document' || - definition.orientdbClass === '' || - definition.orientdbClass === 'document' || - (definition.junctionTable && !utils.isJunctionTableThrough(definition) && - definition.orientdbClass !== 'V' && definition.orientdbClass !== 'E')){ + definition.orientdbClass === '' || + definition.orientdbClass === 'document'){ return new Collection.Document(definition, connection, databaseClass, collectionsByIdentity); } if(definition.orientdbClass === 'E' || - (utils.isJunctionTableThrough(definition) && definition.orientdbClass !== 'V')){ + (definition.junctionTable && definition.orientdbClass !== 'V')){ return new Collection.Edge(definition, connection, databaseClass, collectionsByIdentity); } diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index c1ca43b..1f5da39 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -16,8 +16,20 @@ var collections = { comment_recipe: require('./fixtures/commentRecipe.model') }; -var connectionMock = { - config: { options: {fetchPlanLevel: 1} } +var waterlineSchema = _.cloneDeep(collections); +Object.keys(waterlineSchema).forEach(function(key){ + var collection = waterlineSchema[key]; + Object.keys(collection.attributes).forEach(function(id){ + var attribute = collection.attributes[id]; + if(attribute.through){ + attribute.collection = attribute.through; + } + }); +}); + +var connectionMock = { + config: { options: {fetchPlanLevel: 1} }, + waterlineSchema: waterlineSchema }; var newCollections = {}; diff --git a/test/unit/collection.test.js b/test/unit/collection.test.js index 2313a06..a4a9510 100644 --- a/test/unit/collection.test.js +++ b/test/unit/collection.test.js @@ -74,15 +74,6 @@ describe('collection class', function () { assert(!(doc instanceof Collection.Vertex)); assert(!(doc instanceof Collection.Edge)); - done(); - }); - - it('constructor: should instantiate a document if table is junction table for a many-to-many association', function (done) { - var doc = new Collection(collections.junctionModel, graphConnectionMock, null, null); - assert(doc instanceof Collection.Document); - assert(!(doc instanceof Collection.Vertex)); - assert(!(doc instanceof Collection.Edge)); - done(); }); @@ -107,6 +98,12 @@ describe('collection class', function () { done(); }); + it('constructor: should instantiate an edge if table is junction table for a many-to-many association', function (done) { + var edge = new Collection(collections.junctionModel, graphConnectionMock, null, null); + assert(edge instanceof Collection.Edge); + done(); + }); + it('constructor: should instantiate an edge if table is junction table for a many-to-many through association', function (done) { var edge = new Collection(collections.junctionModelThrough, graphConnectionMock, null, null); assert(edge instanceof Collection.Edge); From 4b02b08e8e01875a4a6d8211a5341edbcb11456c Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 18:03:59 +0000 Subject: [PATCH 43/81] Added waterline-criteria to process populate with where criteria --- lib/adapter.js | 1 + lib/associations.js | 9 +++++++-- package.json | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 0bd76e9..ae42b93 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -58,6 +58,7 @@ module.exports = (function() { // var _dbPools = {}; var adapter = { + identity: 'waterline-orientdb', // Set to true if this adapter supports (or requires) things like data // types, validations, keys, etc. diff --git a/lib/associations.js b/lib/associations.js index ccb261e..c33b525 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -5,8 +5,9 @@ var _ = require('lodash'), _runJoins = require('waterline-cursor'), utils = require('./utils'), - Edge = require('./collection').Edge, - log = require('debug-logger')('waterline-orientdb:associations'); + Collection = require('./collection'), + log = require('debug-logger')('waterline-orientdb:associations'), + wlFilter = require('waterline-criteria'); /** * Associations @@ -210,6 +211,10 @@ Associations.prototype.fetchPlanJoin = function fetchPlanJoin(collectionName, cr record[join.alias].forEach(function(associatedRecord){ utils.cleanOrientAttributes(associatedRecord, childTableSchema); }); + + if (join.criteria){ + record[join.alias] = wlFilter(record[join.alias], join.criteria).results; + } }); utils.cleanOrientAttributes(record, parentSchema); diff --git a/package.json b/package.json index e56e7dc..e1850ac 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "lodash": "^3.3.0", "oriento": "~1.1.0", "q": "^1.0.1", + "waterline-criteria": "~0.11.1", "waterline-cursor": "~0.0.5", "waterline-sequel-orientdb": "~0.0.24" }, From b41810304e15c05932cac993ecbd43c268359e0b Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 18:25:54 +0000 Subject: [PATCH 44/81] Adding "@type" = 'd' when value is type json. Workaround for codemix/oriento#260 --- lib/document.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/document.js b/lib/document.js index 906c601..2e50a0d 100644 --- a/lib/document.js +++ b/lib/document.js @@ -152,9 +152,15 @@ Document.prototype.serializeValues = function serializeValues(values) { values[key] = null; } + else if(type === 'json'){ + if(!values[key]['@type']){ + values[key]['@type'] = 'd'; + } + } + // TODO: should just be "type === 'binary'" but for some reason type never seems to // be equal to 'binary'. Waterline issue? - if ((type === 'binary' || !type) && Buffer.isBuffer(values[key])) { + else if ((type === 'binary' || !type) && Buffer.isBuffer(values[key])) { values[key] = values[key].toString('base64'); } }); From 960ef01913d7f6d2ace64834e7ab1cb8333e11c5 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 18:49:35 +0000 Subject: [PATCH 45/81] Code coverage will now include tests made to the document database --- Makefile | 3 ++- lib/collection/edge.js | 2 +- test/integration/runner.js | 9 +++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 196d87a..c0dd6f4 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ test-unit: $(MOCHA_OPTS) \ test/unit/*.js test/unit/**/*.js -coverage: +coverage: clean-all @echo "\n\nRunning coverage report..." rm -rf coverage ./node_modules/istanbul/lib/cli.js cover --report none --dir coverage/unit \ @@ -46,6 +46,7 @@ coverage: ./node_modules/.bin/_mocha test/integration-orientdb/*.js test/integration-orientdb/tests/**/*.js \ -- --timeout 15000 --globals Associations ./node_modules/istanbul/lib/cli.js cover --report none --dir coverage/integration test/integration/runner.js + ./node_modules/istanbul/lib/cli.js cover --report none --dir coverage/integration-document test/integration/runner.js document ./node_modules/istanbul/lib/cli.js report clean: diff --git a/lib/collection/edge.js b/lib/collection/edge.js index ee4a1f4..e6918b5 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -24,7 +24,7 @@ var Edge = module.exports = utils.extend(Document, function() { // Information about edge's in and out properties this.edgeSides = null; - this._getEdgeSides(arguments[0], arguments[3]); + this._getEdgeSides(arguments[0]); }); diff --git a/test/integration/runner.js b/test/integration/runner.js index 7614c04..6fef91f 100644 --- a/test/integration/runner.js +++ b/test/integration/runner.js @@ -19,10 +19,14 @@ var log = require('debug-logger')('waterline-orientdb:test'); var TestRunner = require('waterline-adapter-tests'); var Adapter = require('../../'); +var argvDatabaseType; +if(process.argv.length > 2){ + argvDatabaseType = process.argv[2]; +} var config = require('../test-connection.json'); config.database = 'waterline-test-integration'; // We need different DB's due to https://github.com/orientechnologies/orientdb/issues/3301 -config.options.databaseType = process.env.DATABASE_TYPE || config.options.databaseType || Adapter.defaults.options.databaseType; +config.options.databaseType = argvDatabaseType || process.env.DATABASE_TYPE || config.options.databaseType || Adapter.defaults.options.databaseType; // Grab targeted interfaces from this adapter's `package.json` file: var package = {}; @@ -41,9 +45,6 @@ catch (e) { } - - - log.info('Testing `' + package.name + '`, a Sails/Waterline adapter.'); log.info('Running `waterline-adapter-tests` against ' + interfaces.length + ' interfaces...'); log.info('( ' + interfaces.join(', ') + ' )'); From 38bb72cda96bdf28e9ceb495fb85ab2ec76ccdbd Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 21:20:45 +0000 Subject: [PATCH 46/81] Added native method to adapter to return a native orientdb collection --- lib/adapter.js | 14 ++++++++++++++ lib/connection.js | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index ae42b93..efe3d57 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -337,6 +337,20 @@ module.exports = (function() { return connections[connection].query(query, options, cb); }, + /** + * Native + * + * Give access to a native orientd collection object for running custom + * queries. + * + * @param {String} connection + * @param {String} collection + * @param {Function} callback + */ + native: function(connection, collection, cb) { + return connections[connection].native(collection, cb); + }, + /** * Get DB * diff --git a/lib/connection.js b/lib/connection.js index 1eb6fc0..b35544d 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -283,16 +283,26 @@ module.exports = (function () { }) .error(cb); }; + + + /** + * returns the oriento collection object + */ + DbHelper.prototype.native = function(collection, cb) { + return cb(this.collections[collection].databaseClass); + }; + /** - * getDB - * * returns the oriento db object */ DbHelper.prototype.getDB = function(cb) { return cb(this.db); }; + /** + * returns the oriento object + */ DbHelper.prototype.getServer = function(cb) { return cb(this.server); }; From 39eced74fb5594282b89a00a30e9814768d87f6d Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 21:57:02 +0000 Subject: [PATCH 47/81] Adds the ability to overwrite the join table name of a Many-to-many association by adding `joinTableNames` to the definition of the dominant side of the association. Example: ``` javascript joinTableNames: { taxis: 'drives' }, ``` Relates to #29 --- lib/associations.js | 9 +- lib/collection/document.js | 25 +++-- lib/collection/edge.js | 27 ++++-- lib/collection/vertex.js | 2 +- lib/connection.js | 7 +- test/integration-orientdb/bootstrap.js | 5 +- .../fixtures/manyToMany.driverHack.fixture.js | 32 +++++++ .../manyToMany.joinTableName.find.js | 92 +++++++++++++++++++ test/unit/associations.test.js | 8 +- test/unit/collection.test.js | 22 ++--- 10 files changed, 194 insertions(+), 35 deletions(-) create mode 100644 test/integration-orientdb/fixtures/manyToMany.driverHack.fixture.js create mode 100644 test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js diff --git a/lib/associations.js b/lib/associations.js index c33b525..31da9a5 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -81,18 +81,19 @@ Associations.prototype.getFetchPlan = function getFetchPlan(collectionName, crit // Edges associations.push(join.parent); var edgeSides = self.getEdgeSides(join.parent); + var joinParentTableName = self.connection.collections[join.parent].tableName; var parentColumnName; if(edgeSides.out.referencedCollectionTableName === join.child && edgeSides.in.referencedAttributeColumnName === join.alias) { - parentColumnName = 'in_' + join.parent; - fetchPlan += parentColumnName + ':1 in_' + join.parent + '.out:' + fetchPlanLevel + ' '; + parentColumnName = 'in_' + joinParentTableName; + fetchPlan += parentColumnName + ':1 in_' + joinParentTableName + '.out:' + fetchPlanLevel + ' '; select.push(parentColumnName); } else if(edgeSides.in.referencedCollectionTableName === join.child && edgeSides.out.referencedAttributeColumnName === join.alias) { - parentColumnName = 'out_' + join.parent; - fetchPlan += parentColumnName + ':1 out_' + join.parent + '.in:' + fetchPlanLevel + ' '; + parentColumnName = 'out_' + joinParentTableName; + fetchPlan += parentColumnName + ':1 out_' + joinParentTableName + '.in:' + fetchPlanLevel + ' '; select.push(parentColumnName); } }); diff --git a/lib/collection/document.js b/lib/collection/document.js index 1bd74f8..bd7bf2c 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -13,19 +13,22 @@ var _ = require('lodash'), * @param {Connection} connection * @api public */ -var Document = module.exports = function Document(definition, connection, databaseClass, collectionsByIdentity) { +var Document = module.exports = function Document(definition, connection, collectionsByIdentity) { // Set a tableName for this document this.tableName = ''; + // Set a new tableName + this.tableNameOriginal = ''; + // Set an identity for this document this.identity = ''; // Set the orientdb super class ('document' / V / E) for this document this.superClass = ''; - // Set the Oriento class object - this.databaseClass = databaseClass; + // Oriento class object, set in Connection constructor + this.databaseClass = undefined; // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) this.classCommand = undefined; @@ -80,7 +83,10 @@ Document.prototype.find = function find(criteria, cb) { } catch(err) { return cb(err); } try { - query = _query.getSelectQuery(self.tableName, self.schema); + query = _query.getSelectQuery(self.tableNameOriginal, self.schema); + if(self.tableNameOriginal !== self.tableName){ + query.query[0] = query.query[0].replace(self.tableNameOriginal, self.tableName); + } } catch(e) { log.error('Failed to compose find SQL query.', e); return cb(e); @@ -162,7 +168,10 @@ Document.prototype.update = function update(criteria, values, cb) { try { _query = new Query(criteria, self.connection.sequel); _document = new Doc(values, self.schema, self.connection); - where = _query.getWhereQuery(self.tableName); + where = _query.getWhereQuery(self.tableNameOriginal); + if(self.tableNameOriginal !== self.tableName){ + where.query[0] = where.query[0].replace(self.tableNameOriginal, self.tableName); + } } catch(e) { log.error('Failed to compose update SQL query.', e); return cb(e); @@ -205,7 +214,10 @@ Document.prototype.destroy = function destroy(criteria, cb) { // Catch errors from building query and return to the callback try { _query = new Query(criteria, self.connection.sequel); - where = _query.getWhereQuery(self.tableName); + where = _query.getWhereQuery(self.tableNameOriginal); + if(self.tableNameOriginal !== self.tableName){ + where.query[0] = where.query[0].replace(self.tableNameOriginal, self.tableName); + } } catch(e) { log.error('Destroy [' + self.tableName + ']: failed to compose destroy SQL query.', e); return cb(e); @@ -278,6 +290,7 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll // Set the tableName var tableName = definition.tableName ? definition.tableName : definition.identity.toLowerCase(); this.tableName = _.clone(tableName); + this.tableNameOriginal = _.clone(tableName); // Set the identity var identity = definition.identity ? definition.identity : definition.tableName; diff --git a/lib/collection/edge.js b/lib/collection/edge.js index e6918b5..44d4f04 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -24,7 +24,7 @@ var Edge = module.exports = utils.extend(Document, function() { // Information about edge's in and out properties this.edgeSides = null; - this._getEdgeSides(arguments[0]); + this._getEdgeSides(arguments[0], arguments[2]); }); @@ -94,7 +94,7 @@ Edge.prototype.destroy = function destroy(criteria, cb) { cb = cb || _.noop; // TODO: should be in a transaction - self.connection.find(self.tableName, criteria, function(err, results){ + self.find(criteria, function(err, results){ if(err){ return cb(err); } if(results.length === 0){ return cb(null, results); } @@ -148,12 +148,13 @@ Edge.prototype.drop = function (relations, cb) { * @return {Object} * @api private */ -Edge.prototype._getEdgeSides = function _getEdgeSides(definition) { +Edge.prototype._getEdgeSides = function _getEdgeSides(definition, collectionsById) { + collectionsById = collectionsById || {}; var self = this, vertexA, vertexB; - log.debug('_getEdgeSides: finding vertexes for' + self.tableName); + log.debug('_getEdgeSides: finding vertexes for: ' + self.tableName); Object.keys(self.schema).forEach(function(key) { var reference = self.schema[key].references; @@ -162,6 +163,8 @@ Edge.prototype._getEdgeSides = function _getEdgeSides(definition) { var referencedCollection = _.find(_.values(self.connection.waterlineSchema), { identity: reference }); var referencedSchema = referencedCollection.attributes; + var referencedDefinition = collectionsById[reference]; + var referencedAttributes = referencedDefinition.attributes; var referencedAttributeKey; Object.keys(referencedSchema).forEach(function(referencedSchemaKey) { @@ -175,7 +178,7 @@ Edge.prototype._getEdgeSides = function _getEdgeSides(definition) { // More than one match, let's use via // Logic for collections with associations to themselves if(key === attribute.via){ - log.debug('_getEdgeSides: match found for ' + key + ': ' + referencedSchemaKey); + log.debug('_getEdgeSides: match found for ' + key + ': ' + referencedSchemaKey); referencedAttributeKey = referencedSchemaKey; } } @@ -187,16 +190,28 @@ Edge.prototype._getEdgeSides = function _getEdgeSides(definition) { var referencedAttribute = referencedSchema[referencedAttributeKey]; + // we need referencedOriginalAttribute because referencedAttribute not always has dominant attribute + var referencedOriginalAttribute = referencedAttributes[referencedAttributeKey]; + var vertex = { referencedCollectionName: reference, referencedCollectionTableName: referencedCollection.tableName || reference, referencedAttributeKey: referencedAttributeKey, referencedAttributeColumnName: referencedAttribute.columnName || referencedAttributeKey, - dominant: referencedAttribute.dominant, + dominant: referencedAttribute.dominant || referencedOriginalAttribute.dominant, junctionTableKey: key, junctionTableColumnName: self.schema[key].columnName || key }; + if(vertex.dominant && (referencedOriginalAttribute.joinTableName || referencedOriginalAttribute.edge)){ + self.tableName = referencedOriginalAttribute.joinTableName || referencedOriginalAttribute.edge; + log.info('Edge [' + self.identity + '] has changed its tableName to [' + self.tableName + ']'); + } else if(vertex.dominant && referencedDefinition.joinTableNames && + referencedDefinition.joinTableNames[referencedAttributeKey]){ + self.tableName = referencedDefinition.joinTableNames[referencedAttributeKey]; + log.info('Edge [' + self.identity + '] has changed its tableName to [' + self.tableName + ']'); + } + if(!vertexA) vertexA = vertex; else if(!vertexB) diff --git a/lib/collection/vertex.js b/lib/collection/vertex.js index fc4bff6..eff6a39 100644 --- a/lib/collection/vertex.js +++ b/lib/collection/vertex.js @@ -41,7 +41,7 @@ Vertex.prototype.destroy = function destroy(criteria, cb) { cb = cb || _.noop; // TODO: should be in a transaction - self.connection.find(self.tableName, criteria, function(err, results){ + self.find(criteria, function(err, results){ if(err){ return cb(err); } if(results.length === 0){ return cb(null, results); } diff --git a/lib/connection.js b/lib/connection.js index b35544d..1a833b8 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -63,10 +63,15 @@ module.exports = (function () { this.collections = {}; this.collectionsByIdentity = {}; Object.keys(collections).forEach(function(key) { - self.collections[key] = new Collection(collections[key], self, self.dbClasses[key], collectionsByIdentity); + self.collections[key] = new Collection(collections[key], self, collectionsByIdentity); self.collectionsByIdentity[self.collections[key].identity] = self.collections[key]; }); + _.values(self.collections).forEach(function(collection) { + // has to run after collection instatiation due to tableName redefinition on edges + collection.databaseClass = self.dbClasses[collection.tableName]; + }); + // aux variables used to figure out when all collections have been synced this.collectionSync = { modifiedCollections: [], diff --git a/test/integration-orientdb/bootstrap.js b/test/integration-orientdb/bootstrap.js index 1b02429..bbcfdc8 100644 --- a/test/integration-orientdb/bootstrap.js +++ b/test/integration-orientdb/bootstrap.js @@ -29,7 +29,8 @@ var fixtures = { //VenueFixture: require(fixturesPath + 'hasManyThrough.venue.fixture'), VenueFixture: require('./fixtures/hasManyThrough.venueHack.fixture'), TaxiFixture: require(fixturesPath + 'manyToMany.taxi.fixture'), - DriverFixture: require(fixturesPath + 'manyToMany.driver.fixture'), + //DriverFixture: require(fixturesPath + 'manyToMany.driver.fixture'), + DriverFixture: require('./fixtures/manyToMany.driverHack.fixture.js'), UserOneFixture: require(fixturesPath + 'oneToOne.fixture').user_resource, ProfileOneFixture: require(fixturesPath + 'oneToOne.fixture').profile, @@ -89,7 +90,7 @@ after(function(done) { // ontology.collections[item].drop(function(err) { // if(err) return next(err); next(); - // }); +// }); } async.each(Object.keys(ontology.collections), dropCollection, function(err) { diff --git a/test/integration-orientdb/fixtures/manyToMany.driverHack.fixture.js b/test/integration-orientdb/fixtures/manyToMany.driverHack.fixture.js new file mode 100644 index 0000000..a48ef7d --- /dev/null +++ b/test/integration-orientdb/fixtures/manyToMany.driverHack.fixture.js @@ -0,0 +1,32 @@ +/** + * Dependencies + */ + +var Waterline = require('waterline'); + +module.exports = Waterline.Collection.extend({ + + tableName: 'driverTable', + identity: 'driver', + connection: 'associations', + joinTableNames: { + taxis: 'drives' + }, + + // migrate: 'drop', + attributes: { + name: 'string', + taxis: { + collection: 'taxi', + via: 'drivers', + //joinTableName: 'drives', + dominant: true + }, + + toJSON: function() { + var obj = this.toObject(); + delete obj.name; + return obj; + } + } +}); diff --git a/test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js b/test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js new file mode 100644 index 0000000..dbbd9c3 --- /dev/null +++ b/test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js @@ -0,0 +1,92 @@ +var assert = require('assert'), + _ = require('lodash'); + +describe('Association Interface', function() { + + describe('n:m association :: tableName attribute', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var driverRecord; + + before(function(done) { + Associations.Driver.create({ name: 'manymany find'}, function(err, driver) { + if(err) return done(err); + + driverRecord = driver; + + var taxis = []; + for(var i=0; i<2; i++) { + driverRecord.taxis.add({ medallion: i }); + } + + driverRecord.save(function(err) { + if(err) return done(err); + done(); + }); + }); + }); + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should return "drives" as join table name', function(done) { + Associations.Driver_taxis__taxi_drivers.native(function(collection){ + assert.equal(collection.name, 'drives'); + done(); + }); + }); + + it('should return taxis when the populate criteria is added', function(done) { + Associations.Driver.find({ name: 'manymany find' }) + .populate('taxis') + .exec(function(err, drivers) { + assert(!err); + + assert(Array.isArray(drivers)); + assert(drivers.length === 1); + assert(Array.isArray(drivers[0].taxis)); + assert(drivers[0].taxis.length === 2); + + done(); + }); + }); + + it('should not return a taxis object when the populate is not added', function(done) { + Associations.Driver.find() + .exec(function(err, drivers) { + assert(!err); + + var obj = drivers[0].toJSON(); + assert(!obj.taxis); + + done(); + }); + }); + + it('should call toJSON on all associated records if available', function(done) { + Associations.Driver.find({ name: 'manymany find' }) + .populate('taxis') + .exec(function(err, drivers) { + assert(!err); + + var obj = drivers[0].toJSON(); + assert(!obj.name); + + assert(Array.isArray(obj.taxis)); + assert(obj.taxis.length === 2); + + assert(obj.taxis[0].hasOwnProperty('createdAt')); + assert(!obj.taxis[0].hasOwnProperty('medallion')); + assert(obj.taxis[1].hasOwnProperty('createdAt')); + assert(!obj.taxis[1].hasOwnProperty('medallion')); + + done(); + }); + }); + + }); +}); diff --git a/test/unit/associations.test.js b/test/unit/associations.test.js index 1f5da39..e7a4362 100644 --- a/test/unit/associations.test.js +++ b/test/unit/associations.test.js @@ -35,11 +35,11 @@ var connectionMock = { var newCollections = {}; Object.keys(collections).forEach(function(key){ collections[key].definition = collections[key].attributes; - newCollections[key] = new Collection(collections[key], connectionMock, null, collections); + newCollections[key] = new Collection(collections[key], connectionMock, collections); }); -newCollections.authored_comment = new Collection.Edge(collections.authored_comment, connectionMock, null, collections); -newCollections.comment_parent = new Collection.Edge(collections.comment_parent, connectionMock, null, collections); -newCollections.comment_recipe = new Collection.Edge(collections.comment_recipe, connectionMock, null, collections); +newCollections.authored_comment = new Collection.Edge(collections.authored_comment, connectionMock, collections); +newCollections.comment_parent = new Collection.Edge(collections.comment_parent, connectionMock, collections); +newCollections.comment_recipe = new Collection.Edge(collections.comment_recipe, connectionMock, collections); connectionMock.collections = newCollections; connectionMock.collectionsByIdentity = newCollections; diff --git a/test/unit/collection.test.js b/test/unit/collection.test.js index a4a9510..a59227a 100644 --- a/test/unit/collection.test.js +++ b/test/unit/collection.test.js @@ -47,7 +47,7 @@ describe('collection class', function () { it('constructor: should instantiate a document regardless of orientdbClass value', function (done) { _.values(collections).forEach(function(collection){ - var doc = new Collection(collection, documentConnectionMock, null, null); + var doc = new Collection(collection, documentConnectionMock, null); assert(doc instanceof Collection.Document); assert(!(doc instanceof Collection.Vertex)); assert(!(doc instanceof Collection.Edge)); @@ -61,15 +61,15 @@ describe('collection class', function () { var graphConnectionMock = { config: { options: { databaseType: 'graph' } } }; it('constructor: should instantiate a document if orientdbClass is "" or "document"', function (done) { - var doc = new Collection(collections.documentModel1, graphConnectionMock, null, null); + var doc = new Collection(collections.documentModel1, graphConnectionMock, null); assert(doc instanceof Collection.Document); assert(!(doc instanceof Collection.Vertex)); assert(!(doc instanceof Collection.Edge)); - doc = new Collection(collections.documentModel2, graphConnectionMock, null, null); + doc = new Collection(collections.documentModel2, graphConnectionMock, null); assert(doc instanceof Collection.Document); assert(!(doc instanceof Collection.Vertex)); assert(!(doc instanceof Collection.Edge)); - doc = new Collection(collections.junctionModelThroughD, graphConnectionMock, null, null); + doc = new Collection(collections.junctionModelThroughD, graphConnectionMock, null); assert(doc instanceof Collection.Document); assert(!(doc instanceof Collection.Vertex)); assert(!(doc instanceof Collection.Edge)); @@ -78,34 +78,34 @@ describe('collection class', function () { }); it('constructor: should instantiate a vertex if orientdbClass is undefined or "V"', function (done) { - var vertex = new Collection(collections.defaultModel, graphConnectionMock, null, null); + var vertex = new Collection(collections.defaultModel, graphConnectionMock, null); assert(vertex instanceof Collection.Document); assert(vertex instanceof Collection.Vertex); - vertex = new Collection(collections.vertexModel, graphConnectionMock, null, null); + vertex = new Collection(collections.vertexModel, graphConnectionMock, null); assert(vertex instanceof Collection.Vertex); - vertex = new Collection(collections.junctionModelThroughV, graphConnectionMock, null, null); + vertex = new Collection(collections.junctionModelThroughV, graphConnectionMock, null); assert(vertex instanceof Collection.Vertex); done(); }); it('constructor: should instantiate an edge if orientdbClass is "E"', function (done) { - var edge = new Collection(collections.edgeModel, graphConnectionMock, null, null); + var edge = new Collection(collections.edgeModel, graphConnectionMock, null); assert(edge instanceof Collection.Edge); - edge = new Collection(collections.junctionModelE, graphConnectionMock, null, null); + edge = new Collection(collections.junctionModelE, graphConnectionMock, null); assert(edge instanceof Collection.Edge); done(); }); it('constructor: should instantiate an edge if table is junction table for a many-to-many association', function (done) { - var edge = new Collection(collections.junctionModel, graphConnectionMock, null, null); + var edge = new Collection(collections.junctionModel, graphConnectionMock, null); assert(edge instanceof Collection.Edge); done(); }); it('constructor: should instantiate an edge if table is junction table for a many-to-many through association', function (done) { - var edge = new Collection(collections.junctionModelThrough, graphConnectionMock, null, null); + var edge = new Collection(collections.junctionModelThrough, graphConnectionMock, null); assert(edge instanceof Collection.Edge); done(); }); From 0be522dbf1bff9f88d9220d233ce23869d6fe5c2 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 21:59:42 +0000 Subject: [PATCH 48/81] Added test for native method --- .../tests/adapterCustomMethods/getDB_Server.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/integration-orientdb/tests/adapterCustomMethods/getDB_Server.js b/test/integration-orientdb/tests/adapterCustomMethods/getDB_Server.js index cf90241..aaf9cac 100644 --- a/test/integration-orientdb/tests/adapterCustomMethods/getDB_Server.js +++ b/test/integration-orientdb/tests/adapterCustomMethods/getDB_Server.js @@ -66,4 +66,21 @@ describe('Adapter Custom Methods', function() { }); }); }); + + + describe('native', function() { + describe('get native oriento collection', function() { + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should return the collection\'s class name', function(done) { + Associations.Friend.native(function(collection){ + assert(collection.name, 'friendTable'); + done(); + }); + }); + }); + }); }); \ No newline at end of file From 3f44cfff584812c9ec2b611c7a7b68b1e15b6b9d Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Sun, 22 Feb 2015 22:54:58 +0000 Subject: [PATCH 49/81] Made connection.postprocessing async --- lib/connection.js | 55 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index 1a833b8..0cd7709 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -170,9 +170,15 @@ module.exports = (function () { schema.id = collection.schema.id; } - cb(null, schema); + // describting last collection and it exists, calling postProcessing now as there won't + // be a subsequent call to define if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ - self.postProcessing(); + self.postProcessing(function(err){ + if(err){ return cb(err); } + cb(null, schema); + }); + } else { + cb(null, schema); } }); @@ -195,8 +201,14 @@ module.exports = (function () { // Create the Collection if (collection.databaseClass) { // TODO: properties may need updating ? - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ self.postProcessing(); } - return cb(null, collection.databaseClass); + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ + return self.postProcessing(function(err){ + if(err){ return cb(err); } + cb(null, collection.schema); + }); + } else { + return cb(null, collection.schema); + } } self.db.class.create(collection.tableName, collection.superClass) @@ -224,12 +236,19 @@ module.exports = (function () { } self.db.registerTransformer(collectionName, transformer); - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ self.postProcessing(); } - // Create Indexes self._ensureIndexes(klass, collection.indexes, function(err/*, result*/){ if(err) { return cb(err); } - cb(null, collection.schema); + + // Post process if all collections have been processed + if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ + self.postProcessing(function(err){ + if(err){ return cb(err); } + cb(null, collection.schema); + }); + } else { + cb(null, collection.schema); + } }); }); }; @@ -240,19 +259,19 @@ module.exports = (function () { * * called after all collections have been created */ - DbHelper.prototype.postProcessing = function postProcessing(){ + DbHelper.prototype.postProcessing = function postProcessing(cb){ var self = this; if(self.collectionSync.postProcessed) { log.debug('Attempted to postprocess twice. This shouln\'t happen, try to improve the logic behind this.'); - return; + return cb(); } self.collectionSync.postProcessed = true; log.info('All classes created, post processing'); - self.collectionSync.modifiedCollections.forEach(function(collection){ - collection.links.forEach(function(link){ + function createLink(collection, complete){ + async.each(collection.links, function(link, next){ var linkClass = collection.databaseClass; var linkedClass = self.collections[link.linkedClass]; @@ -260,12 +279,14 @@ module.exports = (function () { name : link.attributeName, linkedClass : linkedClass.tableName }) - .error(function(err){ - log.error('Failed to create link', err); - }); - }); - }); - + .then(function(dbLink){ + next(null, dbLink); + }) + .error(next); + }, complete); + } + + async.each(self.collectionSync.modifiedCollections, createLink, cb); }; From 0e269ab177c5f26d340d2fc6e017bfbe1ee1a709 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 00:00:04 +0000 Subject: [PATCH 50/81] Adds support schemaless collections by appending wildcard to select queries (when collection is schemaless) Relates to #40 --- lib/collection/document.js | 8 ++ package.json | 2 +- test/integration-orientdb/bootstrap.js | 3 +- .../fixtures/define.indexes.fixture.js | 4 + .../fixtures/define.properties.fixture.js | 1 + .../define.schemalessProperties.fixture.js | 30 +++++++ .../tests/define/properties.js | 21 +++-- .../tests/define/schemalessProperties.js | 79 +++++++++++++++++++ 8 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 test/integration-orientdb/fixtures/define.schemalessProperties.fixture.js create mode 100644 test/integration-orientdb/tests/define/schemalessProperties.js diff --git a/lib/collection/document.js b/lib/collection/document.js index bd7bf2c..d45dc5e 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -42,6 +42,9 @@ var Document = module.exports = function Document(definition, connection, collec // Hold Schema in OrientDB friendly format this.orientdbSchema = null; + // Schemaless mode + this.schemaless = false; + // Hold the primary key from definition this.primaryKey = ''; @@ -79,6 +82,7 @@ Document.prototype.find = function find(criteria, cb) { var _query, query; try { + if(self.schemaless) { criteria.schemaless = true; } _query = new Query(criteria, self.connection.sequel); } catch(err) { return cb(err); } @@ -296,6 +300,10 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll var identity = definition.identity ? definition.identity : definition.tableName; this.identity = _.clone(identity); + if(definition.schema !== undefined){ + this.schemaless = !definition.schema; + } + // Create orientdbSchema this.orientdbSchema = {}; diff --git a/package.json b/package.json index e1850ac..ee0136a 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "q": "^1.0.1", "waterline-criteria": "~0.11.1", "waterline-cursor": "~0.0.5", - "waterline-sequel-orientdb": "~0.0.24" + "waterline-sequel-orientdb": "~0.0.25" }, "devDependencies": { "codeclimate-test-reporter": "~0.0.4", diff --git a/test/integration-orientdb/bootstrap.js b/test/integration-orientdb/bootstrap.js index bbcfdc8..7a1a5b3 100644 --- a/test/integration-orientdb/bootstrap.js +++ b/test/integration-orientdb/bootstrap.js @@ -39,7 +39,8 @@ var fixtures = { OwnsFixture: require('./fixtures/hasManyThrough.owns.fixture'), IndexesFixture: require('./fixtures/define.indexes.fixture'), - PropertiesFixture: require('./fixtures/define.properties.fixture') + PropertiesFixture: require('./fixtures/define.properties.fixture'), + SchemalessPropertiesFixture: require('./fixtures/define.schemalessProperties.fixture'), }; diff --git a/test/integration-orientdb/fixtures/define.indexes.fixture.js b/test/integration-orientdb/fixtures/define.indexes.fixture.js index cebefde..5240b35 100644 --- a/test/integration-orientdb/fixtures/define.indexes.fixture.js +++ b/test/integration-orientdb/fixtures/define.indexes.fixture.js @@ -24,6 +24,10 @@ module.exports = Waterline.Collection.extend({ props: { model: 'properties' + }, + + schemalessProps: { + model: 'schemaless_properties' } } diff --git a/test/integration-orientdb/fixtures/define.properties.fixture.js b/test/integration-orientdb/fixtures/define.properties.fixture.js index 1d1e2b4..e3212cc 100644 --- a/test/integration-orientdb/fixtures/define.properties.fixture.js +++ b/test/integration-orientdb/fixtures/define.properties.fixture.js @@ -17,6 +17,7 @@ module.exports = Waterline.Collection.extend({ textProp : 'string', jsonProp : 'json', floatProp : 'float', + emailProp : 'email', propRequired : { type : 'string', required : true diff --git a/test/integration-orientdb/fixtures/define.schemalessProperties.fixture.js b/test/integration-orientdb/fixtures/define.schemalessProperties.fixture.js new file mode 100644 index 0000000..00ebeac --- /dev/null +++ b/test/integration-orientdb/fixtures/define.schemalessProperties.fixture.js @@ -0,0 +1,30 @@ +/** + * Dependencies + */ + +var Waterline = require('waterline'); + +module.exports = Waterline.Collection.extend({ + + tableName : 'schemalessPropertiesTable', + identity : 'schemaless_properties', + connection : 'associations', + + schema: false, + + attributes : { + schemaProp : 'string', + customColumnProp : { + type: 'string', + columnName: 'customCol' + }, + modelProp : { + model : 'indexes' + }, + collectionProp : { + collection : 'indexes', + via : 'props' + } + } + +}); diff --git a/test/integration-orientdb/tests/define/properties.js b/test/integration-orientdb/tests/define/properties.js index ee2ab01..e616c6d 100644 --- a/test/integration-orientdb/tests/define/properties.js +++ b/test/integration-orientdb/tests/define/properties.js @@ -13,13 +13,9 @@ describe('Define related Operations', function() { var klass; before(function(done) { - Associations.Properties.getDB(function(db) { - db.class.get('propertiesTable') - .then(function(myClass) { - klass = myClass; - done(); - }) - .catch(done); + Associations.Properties.native(function(nativeClass) { + klass = nativeClass; + done(); }); }); @@ -85,6 +81,17 @@ describe('Define related Operations', function() { .error(done); }); + it('should properly create String property from email', function(done) { + klass.property.get('emailProp') + .then(function(property) { + assert.equal(Oriento.types[property.type], 'String'); + done(); + }) + .error(done); + }); + + + // Not sure this can happen seen it's only required a Links exists on the associated table // it('should properly create LinkSet property from collection', function(done) { // klass.property.get('collectionProp') diff --git a/test/integration-orientdb/tests/define/schemalessProperties.js b/test/integration-orientdb/tests/define/schemalessProperties.js new file mode 100644 index 0000000..f7c5a6d --- /dev/null +++ b/test/integration-orientdb/tests/define/schemalessProperties.js @@ -0,0 +1,79 @@ +var assert = require('assert'), + _ = require('lodash'), + Oriento = require('oriento'); + +describe('Define related Operations', function() { + + describe('Property creation schemaless', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var klass; + + before(function(done) { + Associations.Schemaless_properties.native(function(nativeClass) { + klass = nativeClass; + + Associations.Schemaless_properties + .create({ + schemaProp: 'schemaProp', + customColumnProp: 'customColumnProp', + schemaless: 'schemaless' + }).exec(function(err, values){ + if(err) { return done(err); } + done(); + }); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should properly create string property from string', function(done) { + klass.property.get('schemaProp') + .then(function(property) { + assert.equal(property.name, 'schemaProp'); + assert.equal(Oriento.types[property.type], 'String'); + done(); + }) + .error(done); + }); + + it('should properly create property with custom column name', function(done) { + klass.property.get('customCol') + .then(function(property) { + assert.equal(property.name, 'customCol'); + assert.equal(Oriento.types[property.type], 'String'); + done(); + }) + .error(done); + }); + + it('should properly create Link property from model', function(done) { + klass.property.get('modelProp') + .then(function(property) { + assert.equal(Oriento.types[property.type], 'Link'); + assert.equal(property.linkedClass, 'indexesTable'); + done(); + }) + .error(done); + }); + + it('should properly create Link property from email', function(done) { + Associations.Schemaless_properties.findOne({ schemaProp: 'schemaProp' }).exec(function(err, record){ + if(err) { return done(err); } + + assert.equal(record.schemaProp, 'schemaProp'); + assert.equal(record.customColumnProp, 'customColumnProp'); + assert.equal(record.schemaless, 'schemaless'); + done(); + }); + }); + + + }); +}); \ No newline at end of file From 0bf11fc27d6d669c790ec919a31216e45e18ccbd Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 00:01:42 +0000 Subject: [PATCH 51/81] Increased timeout of integration tests from 15s to 20s to avoid travis broken builds due to latency --- Makefile | 2 +- test/integration/runner.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c0dd6f4..cd967fa 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ test-integration-orientdb: @echo "Running waterline-orientdb integration tests..." @NODE_ENV=test ./node_modules/.bin/mocha \ --reporter $(REPORTER) \ - --timeout 15000 --globals Associations,CREATE_TEST_WATERLINE,DELETE_TEST_WATERLINE \ + --timeout 20000 --globals Associations,CREATE_TEST_WATERLINE,DELETE_TEST_WATERLINE \ test/integration-orientdb/*.js test/integration-orientdb/tests/**/*.js \ test/integration-orientdb/bugs/*.js test/integration-orientdb/bugs/**/*.js diff --git a/test/integration/runner.js b/test/integration/runner.js index 6fef91f..d83f888 100644 --- a/test/integration/runner.js +++ b/test/integration/runner.js @@ -79,7 +79,7 @@ new TestRunner({ // Mocha options // reference: https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically mocha: { - timeout: 15000, + timeout: 20000, reporter: 'spec', //grep: 'should return model instances' }, From c84348c356dc179cd3c36ffa3d6cc9366b3137d9 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 10:26:36 +0000 Subject: [PATCH 52/81] Schemaless: only add wildcard to select query if there is no select attribute (wildcard) --- package.json | 2 +- .../fixtures/define.properties.fixture.js | 2 +- .../tests/define/schemalessProperties.js | 38 ++++++++++++++++++- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ee0136a..633ecff 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "q": "^1.0.1", "waterline-criteria": "~0.11.1", "waterline-cursor": "~0.0.5", - "waterline-sequel-orientdb": "~0.0.25" + "waterline-sequel-orientdb": "~0.0.26" }, "devDependencies": { "codeclimate-test-reporter": "~0.0.4", diff --git a/test/integration-orientdb/fixtures/define.properties.fixture.js b/test/integration-orientdb/fixtures/define.properties.fixture.js index e3212cc..f96ba42 100644 --- a/test/integration-orientdb/fixtures/define.properties.fixture.js +++ b/test/integration-orientdb/fixtures/define.properties.fixture.js @@ -14,7 +14,7 @@ module.exports = Waterline.Collection.extend({ stringProp : { type : 'string' }, - textProp : 'string', + textProp : 'text', jsonProp : 'json', floatProp : 'float', emailProp : 'email', diff --git a/test/integration-orientdb/tests/define/schemalessProperties.js b/test/integration-orientdb/tests/define/schemalessProperties.js index f7c5a6d..987737d 100644 --- a/test/integration-orientdb/tests/define/schemalessProperties.js +++ b/test/integration-orientdb/tests/define/schemalessProperties.js @@ -23,7 +23,17 @@ describe('Define related Operations', function() { schemaless: 'schemaless' }).exec(function(err, values){ if(err) { return done(err); } - done(); + + Associations.Properties.create({ + stringProp: 'stringProp', + textProp: 'textProp', + propRequired: 'propRequired' + }).exec(function(err, props){ + if(err) { return done(err); } + + assert.equal(props.textProp, 'textProp'); + done(); + }); }); }); }); @@ -63,7 +73,7 @@ describe('Define related Operations', function() { .error(done); }); - it('should properly create Link property from email', function(done) { + it('should return schemaless properties', function(done) { Associations.Schemaless_properties.findOne({ schemaProp: 'schemaProp' }).exec(function(err, record){ if(err) { return done(err); } @@ -74,6 +84,30 @@ describe('Define related Operations', function() { }); }); + it('should not return schemaless properties with select query', function(done) { + Associations.Schemaless_properties.findOne({ select: ['customColumnProp'], where: { schemaProp: 'schemaProp' } }) + .exec(function(err, record){ + if(err) { return done(err); } + + assert.equal(record.schemaProp, undefined); + assert.equal(record.customColumnProp, 'customColumnProp'); + assert.equal(record.schemaless, undefined); + done(); + }); + }); + + it('schemaful regression test: should not return properties ommitted in projection', function(done) { + Associations.Properties.findOne({ select: ['stringProp'], where: { stringProp: 'stringProp' } }) + .exec(function(err, record){ + if(err) { return done(err); } + + assert.equal(record.stringProp, 'stringProp'); + assert.equal(record.textProp, undefined); + assert.equal(record.propRequired, undefined); + done(); + }); + }); + }); }); \ No newline at end of file From c77abe7e0b28fba5c6202ed7329a9c7ff5502d54 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 12:04:14 +0000 Subject: [PATCH 53/81] Edge, find: Replace junction foreign key columns for in and out insert: process values --- lib/collection/document.js | 2 -- lib/collection/edge.js | 27 ++++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index d45dc5e..d43ed79 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -84,9 +84,7 @@ Document.prototype.find = function find(criteria, cb) { try { if(self.schemaless) { criteria.schemaless = true; } _query = new Query(criteria, self.connection.sequel); - } catch(err) { return cb(err); } - try { query = _query.getSelectQuery(self.tableNameOriginal, self.schema); if(self.tableNameOriginal !== self.tableName){ query.query[0] = query.query[0].replace(self.tableNameOriginal, self.tableName); diff --git a/lib/collection/edge.js b/lib/collection/edge.js index 44d4f04..a110c7c 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -2,7 +2,9 @@ var _ = require('lodash'), utils = require('../utils'), + Query = require('../query'), Document = require('./document'), + Doc = require('../document'), log = require('debug-logger')('waterline-orientdb:edge'); /** @@ -41,9 +43,26 @@ var Edge = module.exports = utils.extend(Document, function() { */ Edge.prototype.find = function find(criteria, cb) { var self = this; + var _query, query; - var edge = self._getEdge(self.tableName, criteria.where); + try { + _query = new Query(criteria, self.connection.sequel); + + // Replace junction foreign key columns for in and out + query = _query.getSelectQuery(self.tableNameOriginal, self.schema); + Object.keys(self.edgeSides).forEach(function(key){ + var junctionColumnName = self.edgeSides[key].junctionTableColumnName; + query.query[0] = query.query[0].replace(junctionColumnName, key + ' AS ' + junctionColumnName); + }); + query.query[0] = query.query[0].replace('SELECT ', ''); + query.query[0] = query.query[0].split('FROM')[0]; + criteria.select = [query.query[0]]; + } catch(e) { + log.error('Failed to compose find SQL query.', e); + return cb(e); + } + var edge = self._getEdge(self.tableName, criteria.where); if (edge) { // Replace foreign keys with from and to if(edge.from) { criteria.where.out = edge.from; } @@ -73,11 +92,13 @@ Edge.prototype.insert = function insert(values, cb) { if (edge) { // Create edge values['@class'] = self.tableName; + var _document = new Doc(values, self.schema, self.connection, 'insert'); edge.keys.forEach(function(refKey) { delete values[refKey]; }); - return self.connection.createEdge(edge.from, edge.to, values, cb); + return self.connection.createEdge(edge.from, edge.to, _document.values, cb); } - // creating an edge without connecting it, probably not useful + // creating an edge without connecting it, probably an edge created with orientdbClass = 'E', + // or some manual operation self.$super.prototype.insert.call(self, values, cb); }; From e6d37f0fa6fba789587ecbfd885268cf50b5dc2e Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 14:19:00 +0000 Subject: [PATCH 54/81] Migrate interface: add AddAttribute method --- lib/adapter.js | 18 ++++++++++++++++++ lib/connection.js | 29 +++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index efe3d57..54b1b4f 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -212,6 +212,24 @@ module.exports = (function() { }, + /** + * AddAttribute + * + * Add a property to a class + * + * @param {String} connection + * @param {String} collection + * @param {String} attrName + * @param {Object} attrDef + * @param {Function} cb + */ + addAttribute: function(connection, collection, attrName, attrDef, cb) { + log.debug('addAttribute: ' + collection + ', attrName:', attrName); + + return connections[connection].addAttribute(collection, attrName, attrDef, cb); + }, + + /** * Find * diff --git a/lib/connection.js b/lib/connection.js index 0cd7709..149336b 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -160,7 +160,7 @@ module.exports = (function () { _.forEach(properties, function(property){ if(collection.schema[property.name]){ schema[property.name] = collection.schema[property.name]; - } + } // else { // // TODO: include properties found in database which are not in collection.schema // } @@ -254,6 +254,32 @@ module.exports = (function () { }; + /** + * Add a property to a class + */ + DbHelper.prototype.addAttribute = function(collectionName, attrName, attrDef, cb) { + var self = this; + + var collection = self.collections[collectionName]; + + var prop; + + if(collection.orientdbSchema[attrName]){ + prop = collection.orientdbSchema[attrName]; + } else { + prop = { + name : attrName, + type : attrDef.type + }; + } + + collection.databaseClass.property.create(prop).then(function(err, property){ + cb(null, property); + }) + .error(cb); + }; + + /** * Post Processing * @@ -290,7 +316,6 @@ module.exports = (function () { }; - /** * query * From 594efe438aec1e05094aa396ee0516a8af65e516 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 18:06:05 +0000 Subject: [PATCH 55/81] Make `waterline-orientdb` more robust against naughty schemas --- lib/collection/document.js | 13 ++++++++++--- test/integration-orientdb/bootstrap.js | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index d43ed79..7ed1914 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -33,7 +33,7 @@ var Document = module.exports = function Document(definition, connection, collec // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) this.classCommand = undefined; - // Hold collection definition + // Hold collection original attributes this.attributes = null; // Hold Schema Information @@ -285,6 +285,7 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll // Hold the Schema this.schema = collectionDef.definition; + // Hold the original attributes this.attributes = definition.attributes; this.primaryKey = _.clone(definition.primaryKey); @@ -319,6 +320,12 @@ Document.prototype._parseDefinition = function _parseDefinition(definition, coll var propertyDefinition = self.schema[attributeName]; + + if (propertyDefinition && propertyDefinition.columnName === '@rid') { + log.warn('Ignoring attribute "' + attributeName + '", waterline-orientdb already maps @rid column to "id".'); + return; + } + // definition.definition doesn't seem to contain everything... using definition.attributes ocasionally var propAttributes = utils.getAttributeAsObject(self.attributes, columnName); @@ -397,8 +404,8 @@ Document.prototype._buildIndexes = function _buildIndexes() { name: self.tableName + '.' + columnName }; - // If index key is `id` ignore it because Mongo will automatically handle this - if(key === 'id') { + // If index key is `id` or columnName is `@rid` ignore it because OrientDB will automatically handle this + if(key === 'id' || columnName === '@rid') { return; } diff --git a/test/integration-orientdb/bootstrap.js b/test/integration-orientdb/bootstrap.js index 7a1a5b3..d369f85 100644 --- a/test/integration-orientdb/bootstrap.js +++ b/test/integration-orientdb/bootstrap.js @@ -69,7 +69,10 @@ before(function(done) { var connections = { associations: _.clone(Connections.test) }; waterline.initialize({ adapters: { wl_tests: Adapter }, connections: connections }, function(err, _ontology) { - if(err) return done(err); + if(err) { + console.log('ERROR:', err); + done(err); + } ontology = _ontology; From 367f973dd82030dbbf109e17380416c671439b5e Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 20:30:25 +0000 Subject: [PATCH 56/81] Adds the ability to decode `id` encoded with `encodeURIComponent()` (useful when used in rest APIs) --- lib/adapter.js | 17 ++- lib/collection/document.js | 6 +- lib/collection/edge.js | 2 +- lib/connection.js | 4 +- lib/query.js | 43 ++++--- test/integration-orientdb/{bugs => }/index.js | 24 ++-- .../decodeURIComponent.test.js | 112 ++++++++++++++++++ 7 files changed, 174 insertions(+), 34 deletions(-) rename test/integration-orientdb/{bugs => }/index.js (78%) create mode 100644 test/integration-orientdb/tests/adapterCustomMethods/decodeURIComponent.test.js diff --git a/lib/adapter.js b/lib/adapter.js index 54b1b4f..94940fe 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -90,12 +90,21 @@ module.exports = (function() { host : 'localhost', port : 2424, options: { - // Waterline only allows populating 1 level below. fetchPlanLevel allows to - // to populate further levels below - fetchPlanLevel : 1, + // Turn parameterized queries on parameterized : true, + + // If `id` is encoded, decode it with `decodeURIComponent()` + decodeURIComponent : true, + + // database type: graph | document databaseType : 'graph', - storage : 'plocal' + + // storage type: memory | plocal + storage : 'plocal', + + // Waterline only allows populating 1 level below. fetchPlanLevel allows to + // to populate further levels below (experimental) + fetchPlanLevel : 1, } }, diff --git a/lib/collection/document.js b/lib/collection/document.js index 7ed1914..c87723d 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -83,7 +83,7 @@ Document.prototype.find = function find(criteria, cb) { try { if(self.schemaless) { criteria.schemaless = true; } - _query = new Query(criteria, self.connection.sequel); + _query = new Query(criteria, self.connection); query = _query.getSelectQuery(self.tableNameOriginal, self.schema); if(self.tableNameOriginal !== self.tableName){ @@ -168,7 +168,7 @@ Document.prototype.update = function update(criteria, values, cb) { // Catch errors from building query and return to the callback try { - _query = new Query(criteria, self.connection.sequel); + _query = new Query(criteria, self.connection); _document = new Doc(values, self.schema, self.connection); where = _query.getWhereQuery(self.tableNameOriginal); if(self.tableNameOriginal !== self.tableName){ @@ -215,7 +215,7 @@ Document.prototype.destroy = function destroy(criteria, cb) { // Catch errors from building query and return to the callback try { - _query = new Query(criteria, self.connection.sequel); + _query = new Query(criteria, self.connection); where = _query.getWhereQuery(self.tableNameOriginal); if(self.tableNameOriginal !== self.tableName){ where.query[0] = where.query[0].replace(self.tableNameOriginal, self.tableName); diff --git a/lib/collection/edge.js b/lib/collection/edge.js index a110c7c..a9c040e 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -46,7 +46,7 @@ Edge.prototype.find = function find(criteria, cb) { var _query, query; try { - _query = new Query(criteria, self.connection.sequel); + _query = new Query(criteria, self.connection); // Replace junction foreign key columns for in and out query = _query.getSelectQuery(self.tableNameOriginal, self.schema); diff --git a/lib/connection.js b/lib/connection.js index 149336b..dd965c2 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -122,10 +122,10 @@ module.exports = (function () { useToken : false }; - if (!server) { + //if (!server) { log.info('Connecting to database...'); server = new Oriento(orientoConnection); - } + //} return ensureDB(connection); }; diff --git a/lib/query.js b/lib/query.js index 232bad1..db0475f 100644 --- a/lib/query.js +++ b/lib/query.js @@ -16,10 +16,13 @@ var _ = require('lodash'), * @param {Object} options * @api private */ -var Query = module.exports = function Query(options, sequel) { +var Query = module.exports = function Query(options, connection) { // Sequel builder - this.sequel = sequel; + this.sequel = connection.sequel; + + // decode + this.decodeURIComponent = connection.config.options.decodeURIComponent; // Normalize Criteria this.criteria = this.normalizeCriteria(options); @@ -132,7 +135,9 @@ Query.prototype.fixId = function fixId(original) { // Normalize `id` key into orientdb `@rid` if (key === 'id' && !hop(this, '@rid')) { key = '@rid'; - obj[key] = self.processIdValue(val); + obj[key] = self.decode(val); + } else if(key === '@rid') { + obj[key] = self.decode(val); } else { obj[key] = val; } @@ -144,25 +149,31 @@ Query.prototype.fixId = function fixId(original) { /** - * Convert ID value to string + * Decodes ID from encoded URI component * * @api private * * @param {Array|Object|String} idValue - * @returns {String} + * @returns {Array|String} */ -Query.prototype.processIdValue = function processIdValue(idValue) { - var newVal = idValue; - if(!_.isArray(idValue)){ - newVal = _.isObject(idValue) ? '#' + idValue.cluster + ':' + idValue.position : idValue; - } else { - newVal = []; - idValue.forEach(function(rid){ - var value = _.isObject(rid) ? '#' + rid.cluster + ':' + rid.position : rid; - newVal.push(value); - }); +Query.prototype.decode = function decode(idValue) { + var self = this; + + if(! idValue || !self.decodeURIComponent) { return idValue; } + + function decodeURI(id){ + var res = id; + if(id.indexOf('%23') === 0){ + res = decodeURIComponent(id); + } + return res; + } + + if(_.isArray(idValue)){ + return _.map(idValue, decodeURI); } - return newVal; + + return decodeURI(idValue); }; diff --git a/test/integration-orientdb/bugs/index.js b/test/integration-orientdb/index.js similarity index 78% rename from test/integration-orientdb/bugs/index.js rename to test/integration-orientdb/index.js index 800d33a..a0cf805 100644 --- a/test/integration-orientdb/bugs/index.js +++ b/test/integration-orientdb/index.js @@ -4,9 +4,9 @@ var Waterline = require('waterline'); var _ = require('lodash'); var async = require('async'); -var Adapter = require('../../../'); +var Adapter = require('../../'); -var config = require('../../test-connection.json'); +var config = require('../test-connection.json'); config.database = 'waterline-test-orientdb'; config.options = config.options || {}; config.options.storage = "memory"; @@ -17,13 +17,19 @@ var instancesMap = {}; // TEST SETUP //////////////////////////////////////////////////// -global.CREATE_TEST_WATERLINE = function(context, dbName, fixtures, cb){ +global.CREATE_TEST_WATERLINE = function(context, testConfig, fixtures, cb){ cb = cb || _.noop; var waterline, ontology; var localConfig = _.cloneDeep(config); - localConfig.database = dbName; + if(testConfig){ + if(_.isString(testConfig)){ + localConfig.database = testConfig; + } else if(_.isObject(testConfig)){ + _.merge(localConfig, testConfig); + } + } // context variable context.collections = {}; @@ -31,7 +37,7 @@ global.CREATE_TEST_WATERLINE = function(context, dbName, fixtures, cb){ waterline = new Waterline(); Object.keys(fixtures).forEach(function(key) { - fixtures[key].connection = dbName; + fixtures[key].connection = localConfig.database; waterline.loadCollection(Waterline.Collection.extend(fixtures[key])); }); @@ -41,7 +47,7 @@ global.CREATE_TEST_WATERLINE = function(context, dbName, fixtures, cb){ Connections.test.adapter = 'wl_tests'; var connections = {}; - connections[dbName] = _.clone(Connections.test); + connections[localConfig.database] = _.clone(Connections.test); waterline.initialize({ adapters: { wl_tests: Adapter }, connections: connections }, function(err, _ontology) { if(err) return cb(err); @@ -53,7 +59,7 @@ global.CREATE_TEST_WATERLINE = function(context, dbName, fixtures, cb){ context.collections[globalName] = _ontology.collections[key]; }); - instancesMap[dbName] = { + instancesMap[localConfig.database] = { waterline: waterline, ontology: ontology, config: localConfig @@ -64,9 +70,11 @@ global.CREATE_TEST_WATERLINE = function(context, dbName, fixtures, cb){ }; -global.DELETE_TEST_WATERLINE = function(dbName, cb){ +global.DELETE_TEST_WATERLINE = function(testConfig, cb){ cb = cb || _.noop; + var dbName = _.isString(testConfig) ? testConfig : testConfig.database; + if(!instancesMap[dbName]) { return cb(new Error('Waterline instance not found for ' + dbName + '! Did you use the correct db name?')); }; var ontology = instancesMap[dbName].ontology; diff --git a/test/integration-orientdb/tests/adapterCustomMethods/decodeURIComponent.test.js b/test/integration-orientdb/tests/adapterCustomMethods/decodeURIComponent.test.js new file mode 100644 index 0000000..dd8eea3 --- /dev/null +++ b/test/integration-orientdb/tests/adapterCustomMethods/decodeURIComponent.test.js @@ -0,0 +1,112 @@ +var assert = require('assert'), + _ = require('lodash'); + +var self = this; + +describe('decodeURIComponent: decode the id', function() { + + var fixtures = { + UserFixture : { + identity : 'user', + + attributes : { + username : 'string', + email : 'email', + } + }, + BlueprintsUserFixture : { + identity : 'blue_user', + + attributes : { + id : { + type : 'string', + primaryKey : true, + columnName : '@rid' + }, + username : 'string', + email : 'email', + } + } + }; + + var config = { + database: 'test_decodeURIComponent', + options: { + decodeURIComponent: true + } + } + + before(function (done) { + CREATE_TEST_WATERLINE(self, config, fixtures, done); + }); + after(function (done) { + DELETE_TEST_WATERLINE(config, done); + }); + + describe('find user', function() { + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + var userRecord, encodedUserId, encodedUser2Id, blueUserRecord, encodedBlueUserId; + + before(function (done) { + self.collections.User.create([{ email: 'user1@example.com' }, { email: 'user2@example.com' }], function(err, users) { + if(err) { return done(err); } + userRecord = users[0]; + encodedUserId = encodeURIComponent(userRecord.id); + encodedUser2Id = encodeURIComponent(users[1].id); + self.collections.Blue_user.create({ email: 'blue@example.com' }, function(err, blueUser) { + if(err) { return done(err); } + blueUserRecord = blueUser; + encodedBlueUserId = encodeURIComponent(blueUserRecord.id); + done(); + }); + }); + }); + + + it('regression test: should retrieve user by id', function(done) { + self.collections.User.findOne(userRecord.id, function(err, user) { + if(err) { return done(err); } + assert.equal(user.email, 'user1@example.com'); + done(); + }); + }); + + it('should retrieve user with encoded id', function(done) { + self.collections.User.findOne(encodedUserId, function(err, user) { + if(err) { return done(err); } + assert.equal(user.email, 'user1@example.com'); + done(); + }); + }); + + it('should retrieve 2 users with encoded id', function(done) { + self.collections.User.find([encodedUserId, encodedUser2Id], function(err, users) { + if(err) { return done(err); } + assert.equal(users[0].email, 'user1@example.com'); + assert.equal(users[1].email, 'user2@example.com'); + done(); + }); + }); + + it('regression test: should retrieve blueprints user by id', function(done) { + self.collections.Blue_user.findOne(blueUserRecord.id, function(err, user) { + if(err) { return done(err); } + assert.equal(user.email, 'blue@example.com'); + done(); + }); + }); + + it('should retrieve blueprints user with encoded id', function(done) { + self.collections.Blue_user.findOne(encodedBlueUserId, function(err, user) { + if(err) { return done(err); } + assert.equal(user.email, 'blue@example.com'); + done(); + }); + }); + + + }); +}); From dff02ec5aaeabdd0c5bdefa4bb0c6803aa102572 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 20:57:48 +0000 Subject: [PATCH 57/81] reorganized configs --- lib/adapter.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 94940fe..91a74f6 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -86,22 +86,31 @@ module.exports = (function() { // Default configuration for connections defaults : { + + // Connection Configuration database : 'waterline', host : 'localhost', port : 2424, + //schema : false, + + // Additional options options: { - // Turn parameterized queries on - parameterized : true, - - // If `id` is encoded, decode it with `decodeURIComponent()` - decodeURIComponent : true, - + + // DB Options + // // database type: graph | document databaseType : 'graph', - // storage type: memory | plocal storage : 'plocal', + // other options + // + // Turn parameterized queries on + parameterized : true, + // + // If `id` is encoded, decode it with `decodeURIComponent()` + decodeURIComponent : true, + // // Waterline only allows populating 1 level below. fetchPlanLevel allows to // to populate further levels below (experimental) fetchPlanLevel : 1, From 8096938b02c55eb5f6e426c0c622e39ad775b8a4 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Mon, 23 Feb 2015 23:28:06 +0000 Subject: [PATCH 58/81] Improved logging and comments --- lib/collection/document.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index c87723d..fa6b437 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -18,19 +18,19 @@ var Document = module.exports = function Document(definition, connection, collec // Set a tableName for this document this.tableName = ''; - // Set a new tableName + // If tableName is changed, this holds the original name this.tableNameOriginal = ''; // Set an identity for this document this.identity = ''; - // Set the orientdb super class ('document' / V / E) for this document + // Set the orientdb super class ("document" / V / E) for this document this.superClass = ''; - // Oriento class object, set in Connection constructor + // Oriento class object, set by Connection constructor this.databaseClass = undefined; - // Set a class command that will be passed to Oriento ( 'undefined' / VERTEX / EDGE) + // Set a class command that will be passed to Oriento ("undefined" / VERTEX / EDGE) this.classCommand = undefined; // Hold collection original attributes @@ -39,7 +39,7 @@ var Document = module.exports = function Document(definition, connection, collec // Hold Schema Information this.schema = null; - // Hold Schema in OrientDB friendly format + // Hold schema in OrientDB friendly format this.orientdbSchema = null; // Schemaless mode @@ -170,12 +170,13 @@ Document.prototype.update = function update(criteria, values, cb) { try { _query = new Query(criteria, self.connection); _document = new Doc(values, self.schema, self.connection); + log.debug('Update [' + self.tableName + '] with values:', _document.values); where = _query.getWhereQuery(self.tableNameOriginal); if(self.tableNameOriginal !== self.tableName){ where.query[0] = where.query[0].replace(self.tableNameOriginal, self.tableName); } } catch(e) { - log.error('Failed to compose update SQL query.', e); + log.error('Failed to compose update SQL query:', e); return cb(e); } @@ -184,15 +185,18 @@ Document.prototype.update = function update(criteria, values, cb) { .return('AFTER'); if(where.query[0]){ - query.where(where.query[0]); + log.debug('Update where query:', where.query[0]); + query = query.where(where.query[0]); if(where.params){ - query.addParams(where.params); + log.debug('Update params:', where.params); + query = query.addParams(where.params); } } query .all() .then(function(res) { + log.debug('Update results: ' + (res && res.length)); cb(null, utils.rewriteIds(res, self.schema)); }) .error(function(err) { From 8644b1bcd50ea0212530a9beb5d4647131e159f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Tue, 24 Feb 2015 00:46:23 +0000 Subject: [PATCH 59/81] Update README.md --- README.md | 142 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 4007007..b0c9e16 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,18 @@ Waterline adapter for OrientDB. [Waterline](https://github.com/balderdashy/water > **Warning** > > `waterline-orientdb` maps the logical `id` attribute to the required `@rid` physical-layer OrientDB Record ID. +> +> +> Migrations +> +> We don't recommend using `migrate: 'alter'` as it has the nasty effect of deleting the data of all edges on a graphDB, leaving only data on the vertexes. +> Either use `'safe'` and migrate manually or use `'drop'` to completely reset the data on your database and create all the classes. In production +> always use `'safe'`. We are currently pushing for a new kind of migration strategy named `'create'`, more about this on [waterline issue #846](https://github.com/balderdashy/waterline/issues/846). -#### Development Status -* Waterline-orientdb aims to work with Waterline v0.10.x and OrientDB v1.7.10 and later. While it may work with earlier versions, they are not currently supported, [pull requests are welcome](./CONTRIBUTING.md)! -* From the waterline [adapter interfaces](https://github.com/balderdashy/sails-docs/blob/master/contributing/adapter-specification.md) waterline-orientdb fully supports `Semantic`, `Queryable`, `Associations` and `Migratable` interfaces. -Waterline-orientdb passes all integration tests from [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests). +Waterline-orientdb aims to work with Waterline v0.10.x and OrientDB v1.7.10 and later. While it may work with earlier versions, they are not currently supported, [pull requests are welcome](./CONTRIBUTING.md)! -* Many-to-many associations currently use a junction table instead of an edge and this will change at some point ([#29](https://github.com/appscot/waterline-orientdb/issues/29)). +From the waterline [adapter interfaces](https://github.com/balderdashy/sails-docs/blob/master/contributing/adapter-specification.md) waterline-orientdb supports `Semantic`, `Queryable`, `Associations` and `Migratable` interfaces. ## Table of Contents @@ -26,9 +30,10 @@ Waterline-orientdb passes all integration tests from [waterline-adapter-tests]( 2. [Waterline Configuration](#waterline-configuration) 3. [Overview](#overview) 4. [Usage](#usage) -5. [Waterline](#waterline) +5. [Testing](#testing) 6. [Contributions](#contributions) -7. [License](#license) +7. [About Waterline](#about-waterline) +8. [License](#license) ## Installation @@ -42,7 +47,7 @@ npm install waterline-orientdb --save ## Waterline Configuration -#### Using with Waterline v0.10.x +### Using with Waterline v0.10.x ```javascript var orientAdapter = require('waterline-orientdb'); @@ -72,41 +77,60 @@ var config = { ## Overview -#### Models -Waterline-orientdb will represent most models in OrientDB as Vertices. The exception being Many-to-Many through join tables which are represented by Edges. +### Models +In a graph db Waterline-orientdb will represent most models in OrientDB as vertexes, the exception being Many-to-Many join tables which are represented by Edges. If using a document db, all models will be represented by documents. -#### Associations +### Associations To learn how to create associations with Waterline/Sails.js check the Waterline Docs [Associations Page](https://github.com/balderdashy/waterline-docs/blob/master/associations.md). Below we go through how waterline-orientdb approaches each kind of associations. -###### One-to-One Associations +#### One-to-One Associations For One-to-One Associations waterline-orientdb creates a LINK ([OrientDB Types](http://www.orientechnologies.com/docs/last/orientdb.wiki/Types.html)) to associate records. -###### One-to-Many Associations -One-to-Many Associations are also represented by a LINK in OrienDB. +#### One-to-Many Associations +One-to-Many Associations are also represented by LINKs in OrientDB. -###### Many-to-Many Associations -Many-to-Many Associations are handled by Waterline, creating a join table holding foreign keys to the associated records. Waterline-orientdb does not change this behaviour for now but we will replace the join table by Edges in a future release ([#29](https://github.com/appscot/waterline-orientdb/issues/29)). Currently it's not deemed a priority. +#### Many-to-Many Associations +In many-to-many associations waterline-orientdb will connect vertexes using edges, hence edges act as join tables. Usually Waterline will create rather long names for join tables (e.g. driver_taxis__taxi_drivers) which are little meaningful from the perspective of a graphDB. Waterline-orientdb allows you to change the name of the edge by adding a property `joinTableNames` to the dominant collection. Example: +```javascript +{ + identity: 'driver', + joinTableNames: { + taxis: 'drives' + }, + + attributes: { + name: 'string', + taxis: { + collection: 'taxi', + via: 'drivers', + dominant: true + } + } +} +``` +In this example the join table name **driver_taxis__taxi_drivers** get converted to **drives**. Complete example of the fixture can be found [here](https://github.com/appscot/waterline-orientdb/tree/master/test/integration-orientdb/fixtures/manyToMany.driverHack.fixture.js). -###### Many-to-Many Through Associations -In a [Many-to-Many Through Association](https://github.com/balderdashy/waterline-docs/blob/master/associations.md#many-to-many-through-associations) ([more info](https://github.com/balderdashy/waterline/issues/705#issuecomment-60945411)) the join table is represented in OrientDB by Edges. Waterline-orientdb automatically creates the edges whenever an association is created. The Edge is named after the property tableName or identity in case tableName is missing. +#### Many-to-Many Through Associations +In a [Many-to-Many Through Association](https://github.com/balderdashy/waterline-docs/blob/master/associations.md#many-to-many-through-associations) ([more info](https://github.com/balderdashy/waterline/issues/705#issuecomment-60945411)) the join table is represented in OrientDB by Edges. Waterline-orientdb automatically creates the edges whenever an association is created. The Edge is named after the property tableName (or identity in case tableName is missing). -#### sails-orientdb differences +### sails-orientdb differences -###### Edge creation +#### Edge creation The main difference between waterline-orientdb and [sails-orientdb](https://github.com/vjsrinath/sails-orientdb) is the way associations/edges are created. In `sails-orientdb` a special attribute named 'edge' is required while waterline-orientdb tries to adhere to waterline specification. -###### ID +#### ID Waterline-orientdb mimics sails-mongo adapter behaviour and maps the logical `id` attribute to the required `@rid` physical-layer OrientDB Record ID. Because of this it's not necessary, or advised, to declare an `id` attribute on your model definitions. ## Usage -#### Models +### Models `waterline-orientdb` uses the standard [waterline model definition](https://github.com/balderdashy/waterline-docs/blob/master/models.md) and extends it in order to accommodate OrientDB feature. -###### orientdbClass +#### orientdbClass It's possible to force the class of a model by adding the property `orientdbClass` to the definition. Generally this is not required as `waterline-orientdb` can determine which is the best class to use, so it should only be used in special cases. Possible values: +* `undefined` - the default and recommended option. The appropriate class will be determined for the model; * `""` or `"document"` - class will be the default OrientDB document class; * `"V"`- class will be Vertex; * `"E"`- class will be Edge. @@ -123,36 +147,36 @@ Example: } ``` -Note, when using a document database (through `config.options.databaseType`), orientdbClass class will be ignored and all classes will be documents +Note, when using a document database (through `config.options.databaseType`), `orientdbClass` class will be ignored and all classes will be documents. -#### Methods +### Methods This adapter adds the following methods: -###### createEdge(from, to, options, callback) +#### .createEdge(from, to, options, callback) Creates edge between specified two model instances by ID in the form parameters `from` and `to` usage: ```javascript //Assume a model named "Post" Post.createEdge('#12:1', '#13:1', { '@class':'Comments' }, function(err, result){ - + console.log('Edges deleted', result); }); ``` -###### deleteEdges(from, to, options, callback) +#### .deleteEdges(from, to, options, callback) Deletes edges between specified two model instances by ID in the form parameters `from` and `to` usage: ```javascript //Assume a model named "Post" Post.deleteEdges('#12:1', '#13:1', null, function(err, result){ - + console.log('Edge created', result); }); ``` -###### query(query, [options], cb) +#### .query(query, [options], cb) Runs a SQL query against the database using Oriento's query method. Will attempt to convert @rid's into ids. usage: @@ -172,39 +196,62 @@ usage: }); ``` -###### getDB(cb) -Returns a native Oriento object +#### native(klass) +Returns a native Oriento class + +usage: + ```javascript + //Assume a model named "Post" + Post.native(function(klass){ + klass.property.list() + .then(function (properties) { + console.log('The class has the following properties:', properties); + } + }); + ``` + +#### .getDB(cb) +Returns a native Oriento database object usage: ```javascript //Assume a model named "Post" Post.getDB(function(db){ - // db.query(... + db.select('foo() as testresult').from('OUser').limit(1).one() + .then(function(res) { + // res contains the result of foo + console.log(res); + }); }); ``` -###### getServer(cb) +#### .getServer(cb) Returns a native Oriento connection usage: ```javascript Post.getServer(function(server){ - // server.list() + server.list() + .then(function (dbs) { + console.log('There are ' + dbs.length + ' databases on the server.'); + }); }); ``` -###### removeCircularReferences(connection, collection, object, cb) +#### .removeCircularReferences(object, cb) Convenience method that replaces circular references with `id` when one is available, otherwise it replaces the object with string '[Circular]' usage: ```javascript //Assume a model named "Post" Post.removeCircularReferences(posts, function(result){ - // JSON.stringify(result); // it's safe to stringify result + console.log(JSON.stringify(result)); // it's safe to stringify result }); ``` -#### Example Model definitions +### Example Model definitions + +Below is an example of a Many-to-many through association. For more examples take a look at [waterline-adapter-tests fixtures](https://github.com/balderdashy/waterline-adapter-tests/tree/master/interfaces/associations/support/fixtures), all these are working examples and frequently tested against. ```javascript /** @@ -304,18 +351,27 @@ An edge named **venueTable** will be created from Team to Stadium model instance See [`FAQ.md`](./FAQ.md). -## Waterline - -[Waterline](https://github.com/balderdashy/waterline) is a new kind of storage and retrieval engine. +## Testing +Test are written with mocha. Integration tests are handled by the [waterline-adapter-tests](https://github.com/balderdashy/waterline-adapter-tests) project, which tests adapter methods against the latest Waterline API. -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 OrientDB, MySQL, LDAP, MongoDB, or Facebook. +To run tests: +```shell +npm test +``` ## Contributions We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](./CONTRIBUTING.md) for the contribution guidelines. -Thanks so much to Srinath Janakiraman ([vjsrinath](http://github.com/vjsrinath)) who built the original `sails-orient` adapter. +Thanks so much to Srinath Janakiraman ([vjsrinath](http://github.com/vjsrinath)) who built the `sails-orientdb` adapter, from which `waterline-orientdb` was forked. + + +## About Waterline + +[Waterline](https://github.com/balderdashy/waterline) is a new kind of storage and retrieval engine. + +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 OrientDB, MySQL, LDAP, MongoDB, or Facebook. ## License From 0c110ab13564b31a00b2fee0ad66b5a6122a8029 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 24 Feb 2015 00:56:10 +0000 Subject: [PATCH 60/81] Removed redundant config defaults --- lib/connection.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/connection.js b/lib/connection.js index dd965c2..0bfa912 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -22,15 +22,7 @@ var sqlOptions = { module.exports = (function () { - var defaults = { - createCustomIndex: false, - idProperty: 'id', - options: { - fetchPlanLevel: 1, - parameterized: false - } - }, - server, + var server, DbHelper = function (db, collections, config, classes) { var self = this; @@ -39,8 +31,7 @@ module.exports = (function () { accumulator[collection.identity] = collection; return accumulator; }, {}); - var auxDefaults = _.merge({}, defaults); - this.config = _.merge(auxDefaults, config); + this.config = config; this.associations = new Associations(this); this.server = server; From dd161eb2306180fc5f16af8dac85e3801ec5735f Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 24 Feb 2015 13:27:05 +0000 Subject: [PATCH 61/81] Moved Associations from adapter to connection --- lib/adapter.js | 17 ++++++++--------- lib/connection.js | 6 ++++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 91a74f6..dc28f5d 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -3,7 +3,6 @@ * Module Dependencies */ var Connection = require('./connection'), - Associations = require('./associations'), utils = require('./utils'), log = require('debug-logger')('waterline-orientdb:adapter'); @@ -100,17 +99,20 @@ module.exports = (function() { // // database type: graph | document databaseType : 'graph', + // // storage type: memory | plocal storage : 'plocal', - // other options + // Useful in REST APIs + // + // If `id` is URI encoded, decode it with `decodeURIComponent()` + decodeURIComponent : true, + + // other // // Turn parameterized queries on parameterized : true, // - // If `id` is encoded, decode it with `decodeURIComponent()` - decodeURIComponent : true, - // // Waterline only allows populating 1 level below. fetchPlanLevel allows to // to populate further levels below (experimental) fetchPlanLevel : 1, @@ -295,10 +297,7 @@ module.exports = (function() { * @return {[type]} [description] */ join : function(connection, collection, options, cb) { - //console.log('\n !!! JOIN, options: ' + require('util').inspect(options)); - - var associations = new Associations(connections[connection]); - return associations.join(collection, options, cb); + return connections[connection].join(collection, options, cb); }, diff --git a/lib/connection.js b/lib/connection.js index 0bfa912..231ce00 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -389,6 +389,12 @@ module.exports = (function () { this.collections[collection].destroy(options, cb); }; + /* + * Peforms a join between 2-3 orientdb collections + */ + DbHelper.prototype.join = function(collection, options, cb) { + this.associations.join(collection, options, cb); + }; /* * Creates edge between two vertices pointed by from and to From bd0316e1b2555a6b4acce3ac6d1624774ba82473 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 24 Feb 2015 13:37:23 +0000 Subject: [PATCH 62/81] Added removeCircularReferences config option --- lib/adapter.js | 5 ++++- lib/connection.js | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index dc28f5d..44b786d 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -105,8 +105,11 @@ module.exports = (function() { // Useful in REST APIs // - // If `id` is URI encoded, decode it with `decodeURIComponent()` + // If `id` is URI encoded, decode it with `decodeURIComponent()` (useful when `id` comes from an URL) decodeURIComponent : true, + // + // Replaces circular references with `id` after populate operations (useful when results will be JSONfied) + removeCircularReferences : true, // other // diff --git a/lib/connection.js b/lib/connection.js index 231ce00..dc8d7d0 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -393,7 +393,15 @@ module.exports = (function () { * Peforms a join between 2-3 orientdb collections */ DbHelper.prototype.join = function(collection, options, cb) { - this.associations.join(collection, options, cb); + var self = this; + + self.associations.join(collection, options, function(err, results){ + if(err) { return cb(err); } + if(self.config.options.removeCircularReferences){ + utils.removeCircularReferences(results); + } + cb(null, results); + }); }; /* From 03594d42142db8de4a5c520742156357509b6d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Tue, 24 Feb 2015 15:02:44 +0000 Subject: [PATCH 63/81] Update README.md --- README.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b0c9e16..ae56b7d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Waterline-orientdb aims to work with Waterline v0.10.x and OrientDB v1.7.10 and From the waterline [adapter interfaces](https://github.com/balderdashy/sails-docs/blob/master/contributing/adapter-specification.md) waterline-orientdb supports `Semantic`, `Queryable`, `Associations` and `Migratable` interfaces. +Waterline-orientb connects to OrientDB using [Oriento](codemix/oriento) (OrientDB's official driver). + ## Table of Contents 1. [Installation](#installation) @@ -81,7 +83,7 @@ var config = { In a graph db Waterline-orientdb will represent most models in OrientDB as vertexes, the exception being Many-to-Many join tables which are represented by Edges. If using a document db, all models will be represented by documents. ### Associations -To learn how to create associations with Waterline/Sails.js check the Waterline Docs [Associations Page](https://github.com/balderdashy/waterline-docs/blob/master/associations.md). Below we go through how waterline-orientdb approaches each kind of associations. +To learn how to create associations with Waterline/Sails.js check the Waterline Docs [Associations Page](https://github.com/balderdashy/waterline-docs/blob/master/associations.md). Below we go through how waterline-orientdb approaches each kind of association. #### One-to-One Associations For One-to-One Associations waterline-orientdb creates a LINK ([OrientDB Types](http://www.orientechnologies.com/docs/last/orientdb.wiki/Types.html)) to associate records. @@ -113,6 +115,9 @@ In this example the join table name **driver_taxis__taxi_drivers** get converted #### Many-to-Many Through Associations In a [Many-to-Many Through Association](https://github.com/balderdashy/waterline-docs/blob/master/associations.md#many-to-many-through-associations) ([more info](https://github.com/balderdashy/waterline/issues/705#issuecomment-60945411)) the join table is represented in OrientDB by Edges. Waterline-orientdb automatically creates the edges whenever an association is created. The Edge is named after the property tableName (or identity in case tableName is missing). +#### Populate queries (joins) +Waterline-orientdb implements its own custom join function so when the user runs `.populate(some_collection)` it will send a single `SELECT` query with a [fetchplan](http://www.orientechnologies.com/docs/last/orientdb.wiki/Fetching-Strategies.html) to OrientDB. This way join operations remain fast and performant by leveraging OrientDB's graphDB features. + ### sails-orientdb differences #### Edge creation @@ -125,7 +130,7 @@ Waterline-orientdb mimics sails-mongo adapter behaviour and maps the logical `id ### Models -`waterline-orientdb` uses the standard [waterline model definition](https://github.com/balderdashy/waterline-docs/blob/master/models.md) and extends it in order to accommodate OrientDB feature. +`waterline-orientdb` uses the standard [waterline model definition](https://github.com/balderdashy/waterline-docs/blob/master/models.md) and extends it in order to accommodate OrientDB features. #### orientdbClass @@ -154,7 +159,7 @@ Note, when using a document database (through `config.options.databaseType`), `o This adapter adds the following methods: -#### .createEdge(from, to, options, callback) +#### .createEdge (from, to, options, callback) Creates edge between specified two model instances by ID in the form parameters `from` and `to` usage: @@ -165,7 +170,7 @@ usage: }); ``` -#### .deleteEdges(from, to, options, callback) +#### .deleteEdges (from, to, options, callback) Deletes edges between specified two model instances by ID in the form parameters `from` and `to` usage: @@ -176,7 +181,7 @@ usage: }); ``` -#### .query(query, [options], cb) +#### .query (query, [options], cb) Runs a SQL query against the database using Oriento's query method. Will attempt to convert @rid's into ids. usage: @@ -196,21 +201,21 @@ usage: }); ``` -#### native(klass) +#### .native (cb) Returns a native Oriento class usage: ```javascript //Assume a model named "Post" - Post.native(function(klass){ - klass.property.list() + Post.native(function(myClass){ + myClass.property.list() .then(function (properties) { console.log('The class has the following properties:', properties); } }); ``` -#### .getDB(cb) +#### .getDB (cb) Returns a native Oriento database object usage: @@ -225,7 +230,7 @@ usage: }); ``` -#### .getServer(cb) +#### .getServer (cb) Returns a native Oriento connection usage: @@ -238,7 +243,7 @@ usage: }); ``` -#### .removeCircularReferences(object, cb) +#### .removeCircularReferences (object, cb) Convenience method that replaces circular references with `id` when one is available, otherwise it replaces the object with string '[Circular]' usage: @@ -251,7 +256,7 @@ usage: ### Example Model definitions -Below is an example of a Many-to-many through association. For more examples take a look at [waterline-adapter-tests fixtures](https://github.com/balderdashy/waterline-adapter-tests/tree/master/interfaces/associations/support/fixtures), all these are working examples and frequently tested against. +For a comprehensive set of examples take a look at [waterline-adapter-tests fixtures](https://github.com/balderdashy/waterline-adapter-tests/tree/master/interfaces/associations/support/fixtures), all of those are working examples and frequently tested. Below is an example of a Many-to-many through association. ```javascript /** From ce83ad9a1298bf739c7a0a3a38b7dd0f3e398be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Tue, 24 Feb 2015 15:49:45 +0000 Subject: [PATCH 64/81] Update README.md --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae56b7d..b263c41 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Waterline adapter for OrientDB. [Waterline](https://github.com/balderdashy/water > Migrations > > We don't recommend using `migrate: 'alter'` as it has the nasty effect of deleting the data of all edges on a graphDB, leaving only data on the vertexes. -> Either use `'safe'` and migrate manually or use `'drop'` to completely reset the data on your database and create all the classes. In production -> always use `'safe'`. We are currently pushing for a new kind of migration strategy named `'create'`, more about this on [waterline issue #846](https://github.com/balderdashy/waterline/issues/846). +> Either use `'safe'` and migrate manually or use `'drop'` to completely reset the data and collections. In production +> always use `'safe'`. We are currently pushing for a new kind of migration strategy named `'create'`, check [waterline issue #846](https://github.com/balderdashy/waterline/issues/846). Waterline-orientdb aims to work with Waterline v0.10.x and OrientDB v1.7.10 and later. While it may work with earlier versions, they are not currently supported, [pull requests are welcome](./CONTRIBUTING.md)! @@ -51,6 +51,8 @@ npm install waterline-orientdb --save ### Using with Waterline v0.10.x +#### Basic Example + ```javascript var orientAdapter = require('waterline-orientdb'); var config = { @@ -76,6 +78,42 @@ var config = { } ``` +#### Connection advanced config example +```javascript + myLocalOrient: { + adapter: 'orient', + host: 'localhost', + port: 2424, + user: 'root', + password: 'root', + database: 'waterline', + + // Additional options + options: { + // DB Options + // + // database type: graph | document + databaseType : 'graph', + // + // storage type: memory | plocal + storage : 'plocal', + + // Useful in REST APIs + // + // If `id` is URI encoded, decode it with `decodeURIComponent()` (useful when `id` comes from an URL) + decodeURIComponent : true, + // + // Replaces circular references with `id` after populate operations (useful when results will be JSONfied) + removeCircularReferences : false, + + // other + // + // Turn parameterized queries on + parameterized : true + } + } +``` +The values stated above represent the default values. For an up to date comprehensive list check [adapter.js](https://github.com/appscot/waterline-orientdb/blob/master/lib/adapter.js#L87). ## Overview From ef0243a8ac70d47534fe8317350dc348f7ee7945 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 24 Feb 2015 17:33:55 +0000 Subject: [PATCH 65/81] Refactor: renamed document -> record as its more meaningful from an OrientDB context and it avoids confusion with collection/document --- lib/collection/document.js | 18 +++++++++--------- lib/collection/edge.js | 6 +++--- lib/{document.js => record.js} | 16 ++++++++-------- test/unit/{document.test.js => record.test.js} | 16 ++++++++-------- 4 files changed, 28 insertions(+), 28 deletions(-) rename lib/{document.js => record.js} (89%) rename test/unit/{document.test.js => record.test.js} (82%) diff --git a/lib/collection/document.js b/lib/collection/document.js index fa6b437..bf168f6 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -3,7 +3,7 @@ var _ = require('lodash'), utils = require('../utils'), Query = require('../query'), - Doc = require('../document'), + Record = require('../record'), log = require('debug-logger')('waterline-orientdb:document'); /** @@ -132,15 +132,15 @@ Document.prototype.find = function find(criteria, cb) { */ Document.prototype.insert = function insert(values, cb) { var self = this, - _document; + record; - _document = new Doc(values, self.schema, self.connection, 'insert'); + record = new Record(values, self.schema, self.connection, 'insert'); - log.debug('Insert into [' + self.tableName + '] values:', _document.values); + log.debug('Insert into [' + self.tableName + '] values:', record.values); self.connection.db.insert() .into(self.tableName) - .set(_document.values) + .set(record.values) .one() .then(function(res) { log.debug('Insert result id: ' + res['@rid']); @@ -162,15 +162,15 @@ Document.prototype.insert = function insert(values, cb) { */ Document.prototype.update = function update(criteria, values, cb) { var _query, - _document, + record, where, self = this; // Catch errors from building query and return to the callback try { _query = new Query(criteria, self.connection); - _document = new Doc(values, self.schema, self.connection); - log.debug('Update [' + self.tableName + '] with values:', _document.values); + record = new Record(values, self.schema, self.connection); + log.debug('Update [' + self.tableName + '] with values:', record.values); where = _query.getWhereQuery(self.tableNameOriginal); if(self.tableNameOriginal !== self.tableName){ where.query[0] = where.query[0].replace(self.tableNameOriginal, self.tableName); @@ -181,7 +181,7 @@ Document.prototype.update = function update(criteria, values, cb) { } var query = self.connection.db.update(self.tableName) - .set(_document.values) + .set(record.values) .return('AFTER'); if(where.query[0]){ diff --git a/lib/collection/edge.js b/lib/collection/edge.js index a9c040e..c660baa 100644 --- a/lib/collection/edge.js +++ b/lib/collection/edge.js @@ -4,7 +4,7 @@ var _ = require('lodash'), utils = require('../utils'), Query = require('../query'), Document = require('./document'), - Doc = require('../document'), + Record = require('../record'), log = require('debug-logger')('waterline-orientdb:edge'); /** @@ -92,9 +92,9 @@ Edge.prototype.insert = function insert(values, cb) { if (edge) { // Create edge values['@class'] = self.tableName; - var _document = new Doc(values, self.schema, self.connection, 'insert'); + var record = new Record(values, self.schema, self.connection, 'insert'); edge.keys.forEach(function(refKey) { delete values[refKey]; }); - return self.connection.createEdge(edge.from, edge.to, _document.values, cb); + return self.connection.createEdge(edge.from, edge.to, record.values, cb); } // creating an edge without connecting it, probably an edge created with orientdbClass = 'E', diff --git a/lib/document.js b/lib/record.js similarity index 89% rename from lib/document.js rename to lib/record.js index cf7073b..ea61057 100644 --- a/lib/document.js +++ b/lib/record.js @@ -6,12 +6,12 @@ var _ = require('lodash'), RID = require('oriento').RID, utils = require('./utils'), hop = utils.object.hop, - log = require('debug-logger')('waterline-orientdb:document'); + log = require('debug-logger')('waterline-orientdb:record'); /** - * Document + * Record * - * Represents a single document in a collection. Responsible for serializing values before + * Represents a single record in a collection. Responsible for serializing values before * writing to a collection. * * @param {Object} values @@ -19,9 +19,9 @@ var _ = require('lodash'), * @api private */ -var Document = module.exports = function Document(values, schema, connection, operation) { +var Record = module.exports = function Record(values, schema, connection, operation) { - // Keep track of the current document's values + // Keep track of the current record's values this.values = {}; // Grab the schema for normalizing values @@ -59,7 +59,7 @@ var Document = module.exports = function Document(values, schema, connection, op * @api private */ -Document.prototype.setValues = function setValues(values) { +Record.prototype.setValues = function setValues(values) { var results = this.serializeValues(values); this.normalizeId(results.values); @@ -75,7 +75,7 @@ Document.prototype.setValues = function setValues(values) { * @param {Object} values * @api private */ -Document.prototype.normalizeId = function normalizeId(values) { +Record.prototype.normalizeId = function normalizeId(values) { if(!values.hasOwnProperty('id') && !values.hasOwnProperty('@rid')) return; @@ -102,7 +102,7 @@ Document.prototype.normalizeId = function normalizeId(values) { * @return {Object} * @api private */ -Document.prototype.serializeValues = function serializeValues(values) { +Record.prototype.serializeValues = function serializeValues(values) { var self = this; var returnResult = {}; diff --git a/test/unit/document.test.js b/test/unit/record.test.js similarity index 82% rename from test/unit/document.test.js rename to test/unit/record.test.js index 13ed28e..8102eb0 100644 --- a/test/unit/document.test.js +++ b/test/unit/record.test.js @@ -2,18 +2,18 @@ * Test dependencies */ var assert = require('assert'), - Document = require('../../lib/document'), + Record = require('../../lib/record'), RID = require('oriento').RID, _ = require('lodash'), util = require('util'); -describe('document helper class', function () { +describe('record helper class', function () { - var doc; + var record; before(function(done){ - doc = new Document(); + record = new Record(); done(); }); @@ -22,7 +22,7 @@ describe('document helper class', function () { name: 'no id collection' }; var testCollection1 = _.clone(collection1); - doc.normalizeId(testCollection1); + record.normalizeId(testCollection1); assert(_.isEqual(testCollection1, collection1)); @@ -30,7 +30,7 @@ describe('document helper class', function () { name: 'id collection', id: '#1:0' }; - doc.normalizeId(testCollection2); + record.normalizeId(testCollection2); assert(_.isUndefined(testCollection2.id)); assert(testCollection2['@rid'] instanceof RID); assert.equal(testCollection2['@rid'].cluster, 1); @@ -41,7 +41,7 @@ describe('document helper class', function () { name: 'id collection', '@rid': new RID('#2:0') }; - doc.normalizeId(testCollection3); + record.normalizeId(testCollection3); assert(_.isUndefined(testCollection3.id)); assert(_.isEqual(testCollection3['@rid'], new RID('#2:0'))); @@ -50,7 +50,7 @@ describe('document helper class', function () { id: '#1:0', '@rid': new RID('#2:0') }; - doc.normalizeId(testCollection4); + record.normalizeId(testCollection4); assert(_.isUndefined(testCollection4.id)); assert(_.isEqual(testCollection4['@rid'], new RID('#1:0'))); From a8e2aed42add634e17df78e535efc565514cd1f6 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 24 Feb 2015 18:13:09 +0000 Subject: [PATCH 66/81] Many-to-many: added test to confirm join table is really an Edge --- lib/associations.js | 1 - .../tests/associations/manyToMany.joinTableName.find.js | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/associations.js b/lib/associations.js index 31da9a5..e49fb35 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -37,7 +37,6 @@ var Associations = module.exports = function Associations(connection) { * @api public */ Associations.prototype.join = function join(collectionName, criteria, cb) { - //TODO: for now we only use fetch plan for many-to-many through associations. Use it for all associations if(this.isEdgeJoin(criteria)) return this.fetchPlanJoin(collectionName, criteria, cb); diff --git a/test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js b/test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js index dbbd9c3..c7c7b3e 100644 --- a/test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js +++ b/test/integration-orientdb/tests/associations/manyToMany.joinTableName.find.js @@ -39,6 +39,13 @@ describe('Association Interface', function() { done(); }); }); + + it('should return "E" (edge) as join table\'s super class', function(done) { + Associations.Driver_taxis__taxi_drivers.native(function(collection){ + assert.equal(collection.superClass, 'E'); + done(); + }); + }); it('should return taxis when the populate criteria is added', function(done) { Associations.Driver.find({ name: 'manymany find' }) From 7e9838c2d5c5cc6fde93fe22108bc181a73b3c3f Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Tue, 24 Feb 2015 23:42:13 +0000 Subject: [PATCH 67/81] Refactor: connection.js overhaul. Now it follows the same pattern as the other classes; - removeCircularReferences changed to false as it may have a small performance penalty. - removed unneeded dependencies --- lib/adapter.js | 19 +- lib/connection.js | 927 ++++++++++++++++++++++++---------------------- package.json | 2 - 3 files changed, 486 insertions(+), 462 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 44b786d..a5cc894 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -90,18 +90,21 @@ module.exports = (function() { database : 'waterline', host : 'localhost', port : 2424, - //schema : false, + //schema : false, // to be consistent with OrientDB we should default to false but breaks 4 tests // Additional options options: { - // DB Options + // DB/Oriento Options // // database type: graph | document databaseType : 'graph', // // storage type: memory | plocal storage : 'plocal', + // + // transport: binary | rest. Currently only binary is supported: https://github.com/codemix/oriento/issues/44 + transport : 'binary', // Useful in REST APIs // @@ -109,7 +112,7 @@ module.exports = (function() { decodeURIComponent : true, // // Replaces circular references with `id` after populate operations (useful when results will be JSONfied) - removeCircularReferences : true, + removeCircularReferences : false, // other // @@ -118,7 +121,7 @@ module.exports = (function() { // // Waterline only allows populating 1 level below. fetchPlanLevel allows to // to populate further levels below (experimental) - fetchPlanLevel : 1, + fetchPlanLevel : 1 } }, @@ -143,12 +146,8 @@ module.exports = (function() { // Add in logic here to initialize connection // e.g. connections[connection.identity] = new Database(connection, // collections); - - Connection.create(connection, collections) - .then(function(helper) { - connections[connection.identity] = helper; - cb(); - }); + + connections[connection.identity] = new Connection(connection, collections, cb); }, diff --git a/lib/connection.js b/lib/connection.js index dc8d7d0..ff31ccd 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -1,7 +1,6 @@ "use strict"; -/*jshint maxlen: 200 */ + var Oriento = require('oriento'), - Q = require('q'), async = require('async'), _ = require('lodash'), utils = require('./utils'), @@ -18,502 +17,530 @@ var sqlOptions = { casting : false, canReturnValues : true, escapeInserts : true -}; +}; + +/** + * Manage a connection to an OrientDB Server + * + * @param {Object} config + * @param {Object} collections + * @param {Function} callback + * @return {Object} + * @api private + */ +var Connection = module.exports = function Connection(config, collections, cb) { + var self = this; + + // holds the adapter config + this.config = config; + + // holds an associations object used for joins + this.associations = new Associations(self); + + // Hold the waterline schema, used by query namely waterline-sequel-orientdb + this.waterlineSchema = _.values(collections)[0].waterline.schema; + + // update sqlOptions config and instantiate a sequel helper + sqlOptions.parameterized = self.config.options.parameterized; + this.sequel = new Sequel(self.waterlineSchema, sqlOptions); + + // holds existing classes from OrientDB + this.dbClasses = {}; + + // Holds a registry of collections (indexed by tableName) + this.collections = {}; + + // Holds a registry of collections (indexed by identity) + this.collectionsByIdentity = {}; + + // hold an instance of oriento + this.server = null; + + // aux variables used to figure out when all collections have been synced + this._collectionSync = { + modifiedCollections: [], + postProcessed: false, + itemsToProcess: _.clone(collections) + }; + + self._init(config, collections, cb); +}; + -module.exports = (function () { +///////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +///////////////////////////////////////////////////////////////////////////////// - var server, - - DbHelper = function (db, collections, config, classes) { - var self = this; - this.db = db; - var collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ - accumulator[collection.identity] = collection; - return accumulator; - }, {}); - this.config = config; - this.associations = new Associations(this); - this.server = server; - - // update sqlOptions config - sqlOptions.parameterized = _.isUndefined(this.config.options.parameterized) ? - sqlOptions.parameterized : this.config.options.parameterized; - // Hold the waterline schema, used by query namely waterline-sequel-orientdb - this.waterlineSchema = _.values(collections)[0].waterline.schema; - - // Instantiate a sequel helper - this.sequel = new Sequel(this.waterlineSchema, sqlOptions); - - // Stores existings classes from OrientDB - this.dbClasses = {}; - classes.forEach(function(klass){ - self.dbClasses[klass.name] = klass; - }); - - // Build up a registry of collections - this.collections = {}; - this.collectionsByIdentity = {}; - Object.keys(collections).forEach(function(key) { - self.collections[key] = new Collection(collections[key], self, collectionsByIdentity); - self.collectionsByIdentity[self.collections[key].identity] = self.collections[key]; - }); - - _.values(self.collections).forEach(function(collection) { - // has to run after collection instatiation due to tableName redefinition on edges - collection.databaseClass = self.dbClasses[collection.tableName]; - }); - - // aux variables used to figure out when all collections have been synced - this.collectionSync = { - modifiedCollections: [], - postProcessed: false, - itemsToProcess: _.clone(collections) - }; - }, - - ensureDB = function (connectionProps) { - var dbProps = (typeof connectionProps.database === 'object') ? connectionProps.database : { name: connectionProps.database }; - dbProps.username = connectionProps.user; - dbProps.password = connectionProps.password; - if(connectionProps.options.storage){ - dbProps.storage = connectionProps.options.storage; + +/** + * Describe + * + * @param {String} collectionName + * @param {Function} callback + */ +Connection.prototype.describe = function describe(collectionName, cb) { + var self = this; + + if(self._collectionSync.itemsToProcess[collectionName]){ + delete self._collectionSync.itemsToProcess[collectionName]; + } + + var collection = self.collections[collectionName]; + if(!collection.databaseClass) { return cb(); } + + var schema = {}; + + collection.databaseClass.property.list() + .then(function(properties){ + + // TODO: don't copy collection.schema blindly, check mandatory and indices! + _.forEach(properties, function(property){ + if(collection.schema[property.name]){ + schema[property.name] = collection.schema[property.name]; + } + // else { + // // TODO: include properties found in database which are not in collection.schema + // } + }); + + if(collection.schema.id){ + schema.id = collection.schema.id; } - if(connectionProps.options.databaseType){ - dbProps.type = connectionProps.options.databaseType; + + // describting last collection and it exists, calling postProcessing now as there won't + // be a subsequent call to define + if(Object.keys(self._collectionSync.itemsToProcess).length === 0){ + self.postProcessing(function(err){ + if(err){ return cb(err); } + cb(null, schema); + }); + } else { + cb(null, schema); } - var deferred = Q.defer(); - log.debug('Looking for database', connectionProps.database); + }); + + // TODO: fetch indexes +}; + - server.list() - .then(function(dbs) { - var dbExists = _.find(dbs, function(db) { - return db.name === dbProps.name; - }); - if (dbExists) { - log.debug('database found.'); - deferred.resolve(server.use(dbProps)); - } else { - log.debug('database not found, will create it.'); - server.create(dbProps).then(function(db) { - deferred.resolve(db); - }); - } +/** + * Create Collection + * + * @param {String} collectionName + * @param {Object} definition + * @param {Function} cb + */ +Connection.prototype.createCollection = function createCollection(collectionName, definition, cb) { + var self = this; + + var collection = self.collections[collectionName]; + + // Create the Collection + if (collection.databaseClass) { + // TODO: properties may need updating ? + if(Object.keys(self._collectionSync.itemsToProcess).length === 0){ + return self.postProcessing(function(err){ + if(err){ return cb(err); } + cb(null, collection.schema); }); + } else { + return cb(null, collection.schema); + } + } - return deferred.promise; - }, - getDb = function (connection) { - var orientoConnection = { - host : connection.host, - port : connection.host, - username : connection.user, - password : connection.password, - transport : connection.transport || 'binary', - enableRIDBags : false, - useToken : false - }; - - //if (!server) { - log.info('Connecting to database...'); - server = new Oriento(orientoConnection); - //} - - return ensureDB(connection); - }; - - DbHelper.prototype.db = null; - DbHelper.prototype.collections = null; - DbHelper.prototype.config = null; + self.db.class.create(collection.tableName, collection.superClass) + .then(function(klass, err) { + if (err) { log.error('db.class.create: ' + err); } - - /** - * Describe - * - * @param {String} collectionName - * @param {Function} callback - */ - DbHelper.prototype.describe = function describe(collectionName, cb) { - var self = this; - - if(self.collectionSync.itemsToProcess[collectionName]){ - delete self.collectionSync.itemsToProcess[collectionName]; - } - - var collection = self.collections[collectionName]; - if(!collection.databaseClass) { return cb(); } - - var schema = {}; - - collection.databaseClass.property.list() - .then(function(properties){ - - // TODO: don't copy collection.schema blindly, check mandatory and indices! - _.forEach(properties, function(property){ - if(collection.schema[property.name]){ - schema[property.name] = collection.schema[property.name]; - } - // else { - // // TODO: include properties found in database which are not in collection.schema - // } - }); - - if(collection.schema.id){ - schema.id = collection.schema.id; + collection.databaseClass = klass; + + self._collectionSync.modifiedCollections.push(collection); + + // Create properties + _.values(collection.orientdbSchema).forEach(function(prop) { + klass.property.create(prop).then(); + }); + + // Create transformations + function transformer(data) { + var newData = {}; + var keys = Object.keys(data), length = keys.length, key, i; + for ( i = 0; i < length; i++) { + key = keys[i]; + newData[key] = data[key]; } + return newData; + } + self.db.registerTransformer(collectionName, transformer); + + // Create Indexes + self._ensureIndexes(klass, collection.indexes, function(err/*, result*/){ + if(err) { return cb(err); } - // describting last collection and it exists, calling postProcessing now as there won't - // be a subsequent call to define - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ + // Post process if all collections have been processed + if(Object.keys(self._collectionSync.itemsToProcess).length === 0){ self.postProcessing(function(err){ if(err){ return cb(err); } - cb(null, schema); + cb(null, collection.schema); }); } else { - cb(null, schema); + cb(null, collection.schema); } }); - - // TODO: fetch indexes - }; - - - /** - * Create Collection - * - * @param {String} collectionName - * @param {Object} definition - * @param {Function} cb - */ - DbHelper.prototype.createCollection = function createCollection(collectionName, definition, cb) { - var self = this; - - var collection = self.collections[collectionName]; - - // Create the Collection - if (collection.databaseClass) { - // TODO: properties may need updating ? - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ - return self.postProcessing(function(err){ - if(err){ return cb(err); } - cb(null, collection.schema); - }); - } else { - return cb(null, collection.schema); - } - } + }); +}; - self.db.class.create(collection.tableName, collection.superClass) - .then(function(klass, err) { - if (err) { log.error('db.class.create: ' + err); } + +/** + * Add a property to a class + */ +Connection.prototype.addAttribute = function(collectionName, attrName, attrDef, cb) { + var self = this; - collection.databaseClass = klass; - - self.collectionSync.modifiedCollections.push(collection); - - // Create properties - _.values(collection.orientdbSchema).forEach(function(prop) { - klass.property.create(prop).then(); - }); - - // Create transformations - function transformer(data) { - var newData = {}; - var keys = Object.keys(data), length = keys.length, key, i; - for ( i = 0; i < length; i++) { - key = keys[i]; - newData[key] = data[key]; - } - return newData; - } - self.db.registerTransformer(collectionName, transformer); - - // Create Indexes - self._ensureIndexes(klass, collection.indexes, function(err/*, result*/){ - if(err) { return cb(err); } - - // Post process if all collections have been processed - if(Object.keys(self.collectionSync.itemsToProcess).length === 0){ - self.postProcessing(function(err){ - if(err){ return cb(err); } - cb(null, collection.schema); - }); - } else { - cb(null, collection.schema); - } - }); - }); - }; + var collection = self.collections[collectionName]; + var prop; - /** - * Add a property to a class - */ - DbHelper.prototype.addAttribute = function(collectionName, attrName, attrDef, cb) { - var self = this; - - var collection = self.collections[collectionName]; - - var prop; - - if(collection.orientdbSchema[attrName]){ - prop = collection.orientdbSchema[attrName]; - } else { - prop = { - name : attrName, - type : attrDef.type - }; - } - - collection.databaseClass.property.create(prop).then(function(err, property){ - cb(null, property); - }) - .error(cb); - }; + if(collection.orientdbSchema[attrName]){ + prop = collection.orientdbSchema[attrName]; + } else { + prop = { + name : attrName, + type : attrDef.type + }; + } + collection.databaseClass.property.create(prop).then(function(err, property){ + cb(null, property); + }) + .error(cb); +}; + + +/** + * Post Processing + * + * called after all collections have been created + */ +Connection.prototype.postProcessing = function postProcessing(cb){ + var self = this; - /** - * Post Processing - * - * called after all collections have been created - */ - DbHelper.prototype.postProcessing = function postProcessing(cb){ - var self = this; - - if(self.collectionSync.postProcessed) { - log.debug('Attempted to postprocess twice. This shouln\'t happen, try to improve the logic behind this.'); - return cb(); - } - self.collectionSync.postProcessed = true; + if(self._collectionSync.postProcessed) { + log.debug('Attempted to postprocess twice. This shouln\'t happen, try to improve the logic behind this.'); + return cb(); + } + self._collectionSync.postProcessed = true; - log.info('All classes created, post processing'); - - function createLink(collection, complete){ - async.each(collection.links, function(link, next){ - var linkClass = collection.databaseClass; - var linkedClass = self.collections[link.linkedClass]; - - linkClass.property.update({ - name : link.attributeName, - linkedClass : linkedClass.tableName - }) - .then(function(dbLink){ - next(null, dbLink); - }) - .error(next); - }, complete); - } - - async.each(self.collectionSync.modifiedCollections, createLink, cb); - }; - - - /** - * query - * - * exposes Oriento's query - */ - DbHelper.prototype.query = function(query, options, cb) { - if (options && !cb) { - cb = options; - options = undefined; - } - - this.db.query(query, options) - .all() - .then(function(res) { - cb(null, utils.rewriteIdsRecursive(res)); - }) - .error(cb); - }; + log.info('All classes created, post processing'); + function createLink(collection, complete){ + async.each(collection.links, function(link, next){ + var linkClass = collection.databaseClass; + var linkedClass = self.collections[link.linkedClass]; + + linkClass.property.update({ + name : link.attributeName, + linkedClass : linkedClass.tableName + }) + .then(function(dbLink){ + next(null, dbLink); + }) + .error(next); + }, complete); + } - /** - * returns the oriento collection object - */ - DbHelper.prototype.native = function(collection, cb) { - return cb(this.collections[collection].databaseClass); - }; - - - /** - * returns the oriento db object - */ - DbHelper.prototype.getDB = function(cb) { - return cb(this.db); - }; - - /** - * returns the oriento object - */ - DbHelper.prototype.getServer = function(cb) { - return cb(this.server); - }; + async.each(self._collectionSync.modifiedCollections, createLink, cb); +}; + + +/** + * query + * + * exposes Oriento's query + */ +Connection.prototype.query = function(query, options, cb) { + if (options && !cb) { + cb = options; + options = undefined; + } + + this.db.query(query, options) + .all() + .then(function(res) { + cb(null, utils.rewriteIdsRecursive(res)); + }) + .error(cb); +}; - - /** - * Retrieves records of class collection that fulfill the criteria in options - */ - DbHelper.prototype.find = function(collection, options, cb) { - this.collections[collection].find(options, cb); - }; +/** + * returns the oriento collection object + */ +Connection.prototype.native = function(collection, cb) { + return cb(this.collections[collection].databaseClass); +}; - /** - * Deletes a collection from database - */ - DbHelper.prototype.drop = function (collectionName, relations, cb) { - this.collections[collectionName].drop(relations, cb); - }; +/** + * returns the oriento db object + */ +Connection.prototype.getDB = function(cb) { + return cb(this.db); +}; - /** - * Creates a new document from a collection - */ - DbHelper.prototype.create = function(collection, options, cb) { - this.collections[collection].insert(options, cb); - }; - +/** + * returns the oriento object + */ +Connection.prototype.getServer = function(cb) { + return cb(this.server); +}; - /** - * Updates a document from a collection - */ - DbHelper.prototype.update = function(collection, options, values, cb) { - this.collections[collection].update(options, values, cb); - }; + +/** + * Retrieves records of class collection that fulfill the criteria in options + */ +Connection.prototype.find = function(collection, options, cb) { + this.collections[collection].find(options, cb); +}; + + +/** + * Deletes a collection from database + */ +Connection.prototype.drop = function (collectionName, relations, cb) { + this.collections[collectionName].drop(relations, cb); +}; + + +/** + * Creates a new document from a collection + */ +Connection.prototype.create = function(collection, options, cb) { + this.collections[collection].insert(options, cb); +}; + +/** + * Updates a document from a collection + */ +Connection.prototype.update = function(collection, options, values, cb) { + this.collections[collection].update(options, values, cb); +}; - /* - * Deletes a document from a collection - */ - DbHelper.prototype.destroy = function(collection, options, cb) { - this.collections[collection].destroy(options, cb); - }; - /* - * Peforms a join between 2-3 orientdb collections - */ - DbHelper.prototype.join = function(collection, options, cb) { - var self = this; - - self.associations.join(collection, options, function(err, results){ - if(err) { return cb(err); } - if(self.config.options.removeCircularReferences){ - utils.removeCircularReferences(results); - } - cb(null, results); - }); - }; +/* + * Deletes a document from a collection + */ +Connection.prototype.destroy = function(collection, options, cb) { + this.collections[collection].destroy(options, cb); +}; - /* - * Creates edge between two vertices pointed by from and to - * Keeps the same interface as described in: - * https://github.com/codemix/oriento/blob/6b8c40e7f1f195b591b510884a8e05c11b53f724/README.md#creating-an-edge-with-properties - * - */ - DbHelper.prototype.createEdge = function(from, to, options, cb) { - var schema, - klass = 'E'; - cb = cb || _.noop; - options = options || {}; - - if(options['@class']){ - klass = options['@class']; - schema = this.collections[klass] && this.collections[klass].schema; +/* + * Peforms a join between 2-3 orientdb collections + */ +Connection.prototype.join = function(collection, options, cb) { + var self = this; + + self.associations.join(collection, options, function(err, results){ + if(err) { return cb(err); } + if(self.config.options.removeCircularReferences){ + utils.removeCircularReferences(results); } - - this.db.create('EDGE', klass).from(from).to(to) - .set(options) - .one() - .then(function(res) { - cb(null, utils.rewriteIds(res, schema)); - }) - .error(cb); - }; + cb(null, results); + }); +}; + +/* + * Creates edge between two vertices pointed by from and to + * Keeps the same interface as described in: + * https://github.com/codemix/oriento/blob/6b8c40e7f1f195b591b510884a8e05c11b53f724/README.md#creating-an-edge-with-properties + * + */ +Connection.prototype.createEdge = function(from, to, options, cb) { + var schema, + klass = 'E'; + cb = cb || _.noop; + options = options || {}; + + if(options['@class']){ + klass = options['@class']; + schema = this.collections[klass] && this.collections[klass].schema; + } + + this.db.create('EDGE', klass).from(from).to(to) + .set(options) + .one() + .then(function(res) { + cb(null, utils.rewriteIds(res, schema)); + }) + .error(cb); +}; - /* - * Removes edges between two vertices pointed by from and to - */ - DbHelper.prototype.deleteEdges = function(from, to, options, cb) { - cb = cb || _.noop; - - if(!options){ - return this.db.delete('EDGE').from(from).to(to).scalar() - .then(function(count) { - cb(null, count); - }); - } - - // temporary workaround for issue: https://github.com/orientechnologies/orientdb/issues/3114 - var className = _.isString(options) ? options : options['@class']; - var command = 'DELETE EDGE FROM ' + from + ' TO ' + to + " where @class = '" + className + "'"; - this.db.query(command) +/* + * Removes edges between two vertices pointed by from and to + */ +Connection.prototype.deleteEdges = function(from, to, options, cb) { + cb = cb || _.noop; + + if(!options){ + return this.db.delete('EDGE').from(from).to(to).scalar() .then(function(count) { - cb(null, count); - }); - }; + cb(null, count); + }); + } + // temporary workaround for issue: https://github.com/orientechnologies/orientdb/issues/3114 + var className = _.isString(options) ? options : options['@class']; + var command = 'DELETE EDGE FROM ' + from + ' TO ' + to + " where @class = '" + className + "'"; + this.db.query(command) + .then(function(count) { + cb(null, count); + }); +}; + - ///////////////////////////////////////////////////////////////////////////////// - // PRIVATE METHODS - ///////////////////////////////////////////////////////////////////////////////// - /** - * Ensure Indexes - * - * @param {Object} oriento class - * @param {Array} indexes - * @param {Function} callback - * @api private - */ +///////////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS +///////////////////////////////////////////////////////////////////////////////// + +/** + * Ensure Indexes + * + * @param {Object} oriento class + * @param {Array} indexes + * @param {Function} callback + * @api private + */ +Connection.prototype._ensureIndexes = function _ensureIndexes(collection, indexes, cb) { + var self = this; + + function createIndex(item, next) { + self.db.index.create(item) + .then(function(index){ next(null, index); }) + .error(next); + } + + async.each(indexes, createIndex, cb); +}; + + +/** + * Initialize a connection + * + * @param {Object} config + * @param {Object} collections + * @param {Object} cb + */ +Connection.prototype._init = function _init(config, collections, cb) { + var self = this; - DbHelper.prototype._ensureIndexes = function _ensureIndexes(collection, indexes, cb) { - var self = this; + this.server = self._getOriento(config); - function createIndex(item, next) { - self.db.index.create(item) - .then(function(index){ next(null, index); }) - .error(next); - } + function ensureDbAndListClasses(done){ + self._ensureDB(config) + .then(function(database){ + self.db = database; + return database.class.list(); + }) + .then(function(classes){ + done(null, classes); + }) + .catch(done); + } - async.each(indexes, createIndex, cb); - }; + function initializeCollections(done){ + self._initializeCollections(collections); + done(); + } - + async.parallel({ + classes : ensureDbAndListClasses, + collections : initializeCollections + }, function(err, results){ + if(err) { return cb(err); } - - var connect = function(connection, collections) { - // if an active connection exists, use - // it instead of tearing the previous - // one down - var d = Q.defer(); - - try { - var database; - getDb(connection, collections) - .then(function(db) { - database = db; - return db.class.list(); - }) - .then(function(classes){ - var helper = new DbHelper(database, collections, connection, classes); - d.resolve(helper); - }); - - } catch (err) { - log.error('An error has occured while trying to connect to OrientDB.', err); - d.reject(err); - throw err; - } - return d.promise; - + results.classes.forEach(function(klass){ + self.dbClasses[klass.name] = klass; + }); + + _.values(self.collections).forEach(function(collection) { + // has to run after collection instatiation due to tableName redefinition on edges + collection.databaseClass = self.dbClasses[collection.tableName]; + }); + + cb(); + + }); +}; + +/** + * Prepares and oriento config and creates a new instance + * + * @param {Object} config + */ +Connection.prototype._getOriento = function _getOriento(config) { + var orientoOptions = { + host : config.host, + port : config.host, + username : config.user, + password : config.password, + transport : config.options.transport, + enableRIDBags : false, + useToken : false }; - return { - create : function(connection, collections) { - return connect(connection, collections); - } - }; + return new Oriento(orientoOptions); +}; +/** + * Check if a database exists and if not, creates one + * + * @param {Object} config + */ +Connection.prototype._ensureDB = function _ensureDB (config) { + var self = this; + + log.info('Connecting to database...'); + + var dbOptions = typeof config.database === 'object' ? config.database : { name: config.database }; + dbOptions.username = config.user; + dbOptions.password = config.password; + dbOptions.storage = config.options.storage; + dbOptions.type = config.options.databaseType; + + return self.server.list() + .then(function(dbs) { + var dbExists = _.find(dbs, function(db) { + return db.name === dbOptions.name; + }); + if (dbExists) { + log.info('Database ' + dbOptions.name + ' found.'); + return self.server.use(dbOptions); + } else { + log.info('Database ' + dbOptions.name + ' not found, will create it.'); + return self.server.create(dbOptions); + } + }); +}; + +/** + * Initializes the database collections + * + * @param {Object} collections + */ +Connection.prototype._initializeCollections = function _initializeCollections(collections) { + var self = this; + + var collectionsByIdentity = _.reduce(collections, function(accumulator, collection){ + accumulator[collection.identity] = collection; + return accumulator; + }, {}); + + Object.keys(collections).forEach(function(key) { + self.collections[key] = new Collection(collections[key], self, collectionsByIdentity); + self.collectionsByIdentity[self.collections[key].identity] = self.collections[key]; + }); +}; -})(); diff --git a/package.json b/package.json index e2b909d..4f8df3b 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,9 @@ "readmeFilename": "README.md", "dependencies": { "async": "~0.9.0", - "debug": "~2.1.0", "debug-logger": "~0.2.0", "lodash": "^3.3.0", "oriento": "~1.1.0", - "q": "^1.0.1", "waterline-criteria": "~0.11.1", "waterline-cursor": "~0.0.5", "waterline-sequel-orientdb": "~0.0.26" From 2955a164578aa55ab9dd08cbf18c1bba1dbd178d Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 00:10:10 +0000 Subject: [PATCH 68/81] ci: make the beforeall timeout of integration tests longer in an attempt to prevent travis from breaking the build; Also made the initial sleep after orientdb startup longer All other timeouts are now shorter --- Makefile | 2 +- ci/initialize-ci.sh | 7 ++++++- test/integration-orientdb/bootstrap.js | 1 + test/integration/runner.js | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index cd967fa..99ef849 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ test-integration-orientdb: @echo "Running waterline-orientdb integration tests..." @NODE_ENV=test ./node_modules/.bin/mocha \ --reporter $(REPORTER) \ - --timeout 20000 --globals Associations,CREATE_TEST_WATERLINE,DELETE_TEST_WATERLINE \ + --timeout 6000 --globals Associations,CREATE_TEST_WATERLINE,DELETE_TEST_WATERLINE \ test/integration-orientdb/*.js test/integration-orientdb/tests/**/*.js \ test/integration-orientdb/bugs/*.js test/integration-orientdb/bugs/**/*.js diff --git a/ci/initialize-ci.sh b/ci/initialize-ci.sh index 9c48a66..395f0cf 100755 --- a/ci/initialize-ci.sh +++ b/ci/initialize-ci.sh @@ -37,5 +37,10 @@ echo "--- Starting an instance of OrientDB ---" sh -c $ODB_LAUNCHER /dev/null & # Wait a bit for OrientDB to finish the initialization phase. -sleep 15 + +if [[ $ODB_VERSION == *"1.7"* ]]; then + sleep 15 +else + sleep 30 +fi printf "\n=== The CI environment has been initialized ===\n" diff --git a/test/integration-orientdb/bootstrap.js b/test/integration-orientdb/bootstrap.js index d369f85..26e1bf0 100644 --- a/test/integration-orientdb/bootstrap.js +++ b/test/integration-orientdb/bootstrap.js @@ -51,6 +51,7 @@ var fixtures = { var waterline, ontology; before(function(done) { + this.timeout(30000); // to prevent travis from breaking the build //globals global.Associations = {}; diff --git a/test/integration/runner.js b/test/integration/runner.js index d83f888..482cb99 100644 --- a/test/integration/runner.js +++ b/test/integration/runner.js @@ -79,7 +79,7 @@ new TestRunner({ // Mocha options // reference: https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically mocha: { - timeout: 20000, + timeout: 6000, reporter: 'spec', //grep: 'should return model instances' }, From 4da9bf3dc59b0ca41dee5c9703b3961c894ea478 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 00:56:14 +0000 Subject: [PATCH 69/81] Issue #43, made test more complex in attempt to reproduce the issue (but no luck) --- .../43-orientdb_requestError.js | 72 +++++++++++++++++-- .../43-orientdb_requestError/image.fixture.js | 6 +- 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js index 242b9f0..21ed7cb 100644 --- a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js @@ -21,13 +21,30 @@ describe('Bug #43: OrientDB.RequestError on update', function() { // TEST SETUP //////////////////////////////////////////////////// - var postRecord; + var postRecord, imageParent; before(function(done) { self.collections.Post.create({ title: 'a post' }, function(err, post) { if(err) { return done(err); } postRecord = post; - done(); + + self.collections.Image.create({ name: 'parent', crops: [ { name: 'crop' } ] }, function(err, img) { + if(err) { return done(err); } + imageParent = img; + + self.collections.Post.findOne(postRecord.id, function(err, thePost){ + assert(!err); + assert(thePost); + + thePost.image = img.id; + + self.collections.Post.update(postRecord.id, thePost, function(err, postUpdated){ + if(err) { return done(err); } + done(); + }); + + }); + }); }); }); @@ -39,14 +56,57 @@ describe('Bug #43: OrientDB.RequestError on update', function() { it('should update a post', function(done) { self.collections.Post.findOne(postRecord.id) .then(function(post){ + assert(post); post.title = 'new title'; - self.collections.Post.update({ id: post.id }, post, done); + self.collections.Post.update(post.id, post, function(err, post2){ + assert(!err, err); + assert.equal(post.title, 'new title'); + done(); + }); }) - .catch(done); + .error(done); }); - - + + it('control test: should have a crop associated', function(done) { + self.collections.Image.findOne(imageParent.id) + .populate('crops') + .exec(function(err, imgParent) { + if(err) { return done(err); } + assert.equal(imgParent.crops[0].name, 'crop'); + done(); + }); + }); + + it('control test: should have a crop associated', function(done) { + self.collections.Image.findOne(imageParent.id) + .exec(function(err, imgParent) { + if(err) { return done(err); } + + imgParent.isCrop = false; + self.collections.Image.update(imageParent.id, imgParent, function(err, res){ + if(err) { return done(err); } + assert.equal(imgParent.isCrop, false); + done(); + }); + }); + }); + + it('control test: should have a crop associated', function(done) { + self.collections.Image.findOne(imageParent.id) + .exec(function(err, imgParent) { + if(err) { return done(err); } + + imgParent.isCrop = false; + self.collections.Image.update(imageParent.id, imgParent, function(err, res){ + if(err) { return done(err); } + assert.equal(imgParent.isCrop, false); + done(); + }); + }); + }); + + }); }); diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js b/test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js index 144f5c5..fb6b6f4 100644 --- a/test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/image.fixture.js @@ -3,10 +3,12 @@ module.exports = { identity: 'image', attributes: { + name: { + type: 'string' + }, file: { type: 'json', - isFile: true, - required: true + isFile: true }, footer: { type: 'string' From 5cb1b4bebaeede4aa192e84c253be1ec1de23f82 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 01:27:48 +0000 Subject: [PATCH 70/81] Issue #43: successfully reproduced stackoverflow user's issue --- .../43-orientdb_requestError.js | 71 ++++++++++++++++++- .../43-orientdb_requestError/user.fixture.js | 33 +++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 test/integration-orientdb/bugs/43-orientdb_requestError/user.fixture.js diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js index 21ed7cb..aa309ab 100644 --- a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js @@ -8,7 +8,8 @@ describe('Bug #43: OrientDB.RequestError on update', function() { before(function (done) { var fixtures = { ImageFixture: require('./image.fixture'), - SubprofileFixture: require('./post.fixture') + SubprofileFixture: require('./post.fixture'), + UserFixture: require('./user.fixture') }; CREATE_TEST_WATERLINE(self, 'test_bug_43', fixtures, done); }); @@ -16,7 +17,7 @@ describe('Bug #43: OrientDB.RequestError on update', function() { DELETE_TEST_WATERLINE('test_bug_43', done); }); - describe('update a created post', function() { + describe('rodrigorn: update a created post', function() { ///////////////////////////////////////////////////// // TEST SETUP //////////////////////////////////////////////////// @@ -42,7 +43,6 @@ describe('Bug #43: OrientDB.RequestError on update', function() { if(err) { return done(err); } done(); }); - }); }); }); @@ -107,6 +107,71 @@ describe('Bug #43: OrientDB.RequestError on update', function() { }); }); + }); + + + describe('stackoverflow issue: update a created user', function() { + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + var userParent, userChild; + + before(function(done) { + self.collections.Dbuser.create({ username: 'parent' }, function(err, user) { + if(err) { return done(err); } + userParent = user; + + self.collections.Dbuser.create({ username: 'child' }, function(err, user2) { + if(err) { return done(err); } + userChild = user2; + + self.collections.Dbuser.findOne(userParent.id, function(err, dbUser){ + if(err) { return done(err); } + dbUser.follows.add(user2.id); + dbUser.save(function(err){ + //ignore the error for now + done(); + }); + }) + + }); + }); + }); + + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('control test: should have created child user', function(done) { + self.collections.Dbuser.findOne({ username: 'child' }) + .populate('followed') + .exec(function(err, user) { + assert(!err, err); + assert.equal(user.username, 'child'); + assert.equal(user.followed[0].username, 'parent'); + done(); + }); + }); + + it('should update user', function(done) { + userParent.token = 'iasbdasgdpsabçefbe'; + self.collections.Dbuser.create(userParent.id, userParent, function(err, user) { + if(err) { return done(err); } + assert(user); + done(); + }); + }); + + it('should create 2 users who reference each other', function(done) { + self.collections.Dbuser.update({ username: 'user1', follows: [ { username: 'user2' } ] }, function(err, user) { + if(err) { return done(err); } + assert(user); + done(); + }); + }); }); + }); diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/user.fixture.js b/test/integration-orientdb/bugs/43-orientdb_requestError/user.fixture.js new file mode 100644 index 0000000..bfb23a7 --- /dev/null +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/user.fixture.js @@ -0,0 +1,33 @@ +module.exports = { + tableName : 'User', + identity : 'dbuser', + schema : true, + attributes : { + id : { + type : 'string', + primaryKey : true, + columnName : '@rid' + }, + username : { + type : 'string', + // required : true, + unique : true + }, + password : { + type : 'string', + // required : false + }, + token : { + type : 'string' + }, + follows : { + collection : 'dbuser', + via : 'followed', + dominant : true + }, + followed : { + collection : 'dbuser', + via : 'follows' + } + } +}; From 2d96f55ba6d2f0667e06914366d40ac68254b323 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 01:34:47 +0000 Subject: [PATCH 71/81] Fixed typo in test 43 --- .../43-orientdb_requestError/43-orientdb_requestError.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js index aa309ab..3b96be3 100644 --- a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js @@ -129,10 +129,7 @@ describe('Bug #43: OrientDB.RequestError on update', function() { self.collections.Dbuser.findOne(userParent.id, function(err, dbUser){ if(err) { return done(err); } dbUser.follows.add(user2.id); - dbUser.save(function(err){ - //ignore the error for now - done(); - }); + dbUser.save(done); }) }); @@ -157,7 +154,7 @@ describe('Bug #43: OrientDB.RequestError on update', function() { it('should update user', function(done) { userParent.token = 'iasbdasgdpsabçefbe'; - self.collections.Dbuser.create(userParent.id, userParent, function(err, user) { + self.collections.Dbuser.update(userParent.id, userParent, function(err, user) { if(err) { return done(err); } assert(user); done(); From ee7a260641bc6bd4872f7eb9f1e1395bd3316c63 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 01:40:07 +0000 Subject: [PATCH 72/81] Ignoring 'should create 2 users referencing each other' as not really relevant for now --- .../bugs/43-orientdb_requestError/43-orientdb_requestError.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js index 3b96be3..092bade 100644 --- a/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js +++ b/test/integration-orientdb/bugs/43-orientdb_requestError/43-orientdb_requestError.js @@ -161,7 +161,7 @@ describe('Bug #43: OrientDB.RequestError on update', function() { }); }); - it('should create 2 users who reference each other', function(done) { + xit('should create 2 users who reference each other', function(done) { self.collections.Dbuser.update({ username: 'user1', follows: [ { username: 'user2' } ] }, function(err, user) { if(err) { return done(err); } assert(user); From 2a611360a179357eb2e31d001963b878a0dd092a Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 01:52:35 +0000 Subject: [PATCH 73/81] Travis timeout (again) --- test/integration/runner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/runner.js b/test/integration/runner.js index 482cb99..38fb7d9 100644 --- a/test/integration/runner.js +++ b/test/integration/runner.js @@ -79,7 +79,7 @@ new TestRunner({ // Mocha options // reference: https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically mocha: { - timeout: 6000, + timeout: 10000, reporter: 'spec', //grep: 'should return model instances' }, From 688d43f1a8628632df6a22d6e36117d9decbee27 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 02:10:55 +0000 Subject: [PATCH 74/81] Travis: Error: timeout of 30000ms exceeded! Increasing timeout to 60s --- test/integration-orientdb/bootstrap.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration-orientdb/bootstrap.js b/test/integration-orientdb/bootstrap.js index 26e1bf0..7899f6a 100644 --- a/test/integration-orientdb/bootstrap.js +++ b/test/integration-orientdb/bootstrap.js @@ -51,7 +51,7 @@ var fixtures = { var waterline, ontology; before(function(done) { - this.timeout(30000); // to prevent travis from breaking the build + this.timeout(60000); // to prevent travis from breaking the build //globals global.Associations = {}; From 65a3a561243bec7e3883e548a841fb2b155bd40e Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 11:29:21 +0000 Subject: [PATCH 75/81] Adds option to have db user and pass separate from connection user/pass: "config.options.databaseUser" "config.options.databasePassword" If these are not supplied connection user/pass will be used --- lib/adapter.js | 6 ++ lib/connection.js | 4 +- .../bugs/47-schema_with_id.js | 9 +- test/integration-orientdb/index.js | 7 +- .../tests/config/database.test.js | 87 +++++++++++++++++++ 5 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 test/integration-orientdb/tests/config/database.test.js diff --git a/lib/adapter.js b/lib/adapter.js index a5cc894..078249d 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -105,6 +105,12 @@ module.exports = (function() { // // transport: binary | rest. Currently only binary is supported: https://github.com/codemix/oriento/issues/44 transport : 'binary', + // + // database username, by default uses connection username set on config + // databaseUser : null, + // + // database password, by default uses connection password set on config + // databasePassword : null, // Useful in REST APIs // diff --git a/lib/connection.js b/lib/connection.js index ff31ccd..38659b6 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -505,8 +505,8 @@ Connection.prototype._ensureDB = function _ensureDB (config) { log.info('Connecting to database...'); var dbOptions = typeof config.database === 'object' ? config.database : { name: config.database }; - dbOptions.username = config.user; - dbOptions.password = config.password; + dbOptions.username = config.options.databaseUser || config.user; + dbOptions.password = config.options.databasePassword || config.password; dbOptions.storage = config.options.storage; dbOptions.type = config.options.databaseType; diff --git a/test/integration-orientdb/bugs/47-schema_with_id.js b/test/integration-orientdb/bugs/47-schema_with_id.js index a6ee6f1..4e0d139 100644 --- a/test/integration-orientdb/bugs/47-schema_with_id.js +++ b/test/integration-orientdb/bugs/47-schema_with_id.js @@ -44,10 +44,11 @@ describe('Bug #47: Schema with id (blueprints like)', function() { }); describe('create user', function() { - + ///////////////////////////////////////////////////// - // TEST METHODS + // TEST SETUP //////////////////////////////////////////////////// + var userRecord, passportRecord, passportNullIdRecord; before(function (done) { @@ -70,6 +71,10 @@ describe('Bug #47: Schema with id (blueprints like)', function() { }); + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + it('should be robust against an insertion with id set', function(done) { // we probably should throw an error... self.collections.User.create({ email: 'email@example.com', id: '#13:1' }, function(err, user) { diff --git a/test/integration-orientdb/index.js b/test/integration-orientdb/index.js index a0cf805..3bc26d2 100644 --- a/test/integration-orientdb/index.js +++ b/test/integration-orientdb/index.js @@ -31,10 +31,11 @@ global.CREATE_TEST_WATERLINE = function(context, testConfig, fixtures, cb){ } } + waterline = new Waterline(); + // context variable context.collections = {}; - - waterline = new Waterline(); + context.waterline = waterline; Object.keys(fixtures).forEach(function(key) { fixtures[key].connection = localConfig.database; @@ -65,7 +66,7 @@ global.CREATE_TEST_WATERLINE = function(context, testConfig, fixtures, cb){ config: localConfig }; - cb(); + cb(null, context.collections); }); }; diff --git a/test/integration-orientdb/tests/config/database.test.js b/test/integration-orientdb/tests/config/database.test.js new file mode 100644 index 0000000..72777a1 --- /dev/null +++ b/test/integration-orientdb/tests/config/database.test.js @@ -0,0 +1,87 @@ +var assert = require('assert'), + _ = require('lodash'); + +var self = this, + fixtures, + config; + +describe('Config tests)', function() { + before(function (done) { + + fixtures = { + UserFixture : { + identity : 'user', + attributes : { + name : 'string' + } + }, + ThingFixture : { + identity : 'thing', + + attributes : { + name : 'string' + } + } + }; + + config = { + user : 'root', + password : 'root', + database : 'test_config_db' + }; + + CREATE_TEST_WATERLINE(self, config, fixtures, done); + }); + after(function (done) { + DELETE_TEST_WATERLINE(config, done); + }); + + describe('database', function() { + + describe('username', function() { + + ///////////////////////////////////////////////////// + // TEST SETUP + //////////////////////////////////////////////////// + + before(function (done) { + // db created, let's close the connection + self.waterline.teardown(done); + }); + + ///////////////////////////////////////////////////// + // TEST METHODS + //////////////////////////////////////////////////// + + it('should be the same as connection username', function(done) { + CREATE_TEST_WATERLINE(self, config, fixtures, function(err){ + if(err) { return done(err); } + self.collections.User.getDB(function(db){ + assert.equal(db.username, 'root'); + done(); + }); + }); + }); + + it('should be the same as databaseUsername', function(done) { + self.waterline.teardown(function(err){ + if(err) { return done(err); } + + config.options = { + databaseUser : 'admin', + databasePassword : 'admin', + }; + + CREATE_TEST_WATERLINE(self, config, fixtures, function(err){ + if(err) { return done(err); } + self.collections.User.getDB(function(db){ + assert.equal(db.username, 'admin'); + done(); + }); + }); + }); + }); + + }); + }); +}); From a4fa9255ef36ea308a2004d0c53095281023bde6 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Wed, 25 Feb 2015 11:47:39 +0000 Subject: [PATCH 76/81] Fixes test error: "OrientDB.RequestError: Server user not authenticated" --- .../tests/config/database.test.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/integration-orientdb/tests/config/database.test.js b/test/integration-orientdb/tests/config/database.test.js index 72777a1..1649096 100644 --- a/test/integration-orientdb/tests/config/database.test.js +++ b/test/integration-orientdb/tests/config/database.test.js @@ -45,10 +45,19 @@ describe('Config tests)', function() { //////////////////////////////////////////////////// before(function (done) { - // db created, let's close the connection + // db created, let's close the connection so we can test logins self.waterline.teardown(done); }); + after(function (done) { + // let's log off last user because it may not have privileges to drop the db later on + self.waterline.teardown(function(err){ + if(err) { return done(err); } + // and now we logon with original config + CREATE_TEST_WATERLINE(self, config, fixtures, done); + }); + }); + ///////////////////////////////////////////////////// // TEST METHODS //////////////////////////////////////////////////// @@ -63,16 +72,18 @@ describe('Config tests)', function() { }); }); - it('should be the same as databaseUsername', function(done) { + it('should be the same as databaseUser', function(done) { self.waterline.teardown(function(err){ if(err) { return done(err); } - config.options = { + var newConfig = _.cloneDeep(config); + + newConfig.options = { databaseUser : 'admin', databasePassword : 'admin', }; - CREATE_TEST_WATERLINE(self, config, fixtures, function(err){ + CREATE_TEST_WATERLINE(self, newConfig, fixtures, function(err){ if(err) { return done(err); } self.collections.User.getDB(function(db){ assert.equal(db.username, 'admin'); From 8c51aa0935f7ed8f1d78566c65140a89ba68c45a Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 26 Feb 2015 01:56:03 +0000 Subject: [PATCH 77/81] Updated debug-logger to v0.3.0 --- lib/collection/document.js | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/collection/document.js b/lib/collection/document.js index bf168f6..ced638b 100644 --- a/lib/collection/document.js +++ b/lib/collection/document.js @@ -109,7 +109,7 @@ Document.prototype.find = function find(criteria, cb) { .query(query.query[0], opts) .all() .then(function (res) { - log.debug('Find results: ' + (res && res.length)); + log.debug('Find results:', res && res.length); if (res && criteria.fetchPlan) { //log.debug('res', res); cb(null, utils.rewriteIdsRecursive(res, self.schema)); @@ -143,7 +143,7 @@ Document.prototype.insert = function insert(values, cb) { .set(record.values) .one() .then(function(res) { - log.debug('Insert result id: ' + res['@rid']); + log.debug('Insert result id:', res['@rid']); cb(null, utils.rewriteIds(res, self.schema)); }) .error(function(err) { @@ -196,7 +196,7 @@ Document.prototype.update = function update(criteria, values, cb) { query .all() .then(function(res) { - log.debug('Update results: ' + (res && res.length)); + log.debug('Update results:', res && res.length); cb(null, utils.rewriteIds(res, self.schema)); }) .error(function(err) { @@ -245,7 +245,7 @@ Document.prototype.destroy = function destroy(criteria, cb) { query .all() .then(function(res) { - log.debug('Destroy [' + self.tableName + '] deleted records: ' + (res && res.length)); + log.debug('Destroy [' + self.tableName + '] deleted records:', res && res.length); cb(null, utils.rewriteIds(res, self.schema)); }) .error(function(err) { diff --git a/package.json b/package.json index 4f8df3b..b0d19af 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "~0.9.0", - "debug-logger": "~0.2.0", + "debug-logger": "~0.3.0", "lodash": "^3.3.0", "oriento": "~1.1.0", "waterline-criteria": "~0.11.1", From 8b2932ca1fe83e17d48728474d524bf0a9c74021 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 26 Feb 2015 16:37:37 +0000 Subject: [PATCH 78/81] Updated debug.logger to 0.3.1 and enabled ensureNewline --- lib/adapter.js | 11 ++++++++--- package.json | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 078249d..8537639 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -2,9 +2,14 @@ /** * Module Dependencies */ -var Connection = require('./connection'), - utils = require('./utils'), - log = require('debug-logger')('waterline-orientdb:adapter'); + +var ensureNewline = process.env.NODE_ENV !== 'production'; +var log = require('debug-logger').config({ ensureNewline: ensureNewline })('waterline-orientdb:adapter'), + Connection = require('./connection'), + utils = require('./utils'); + + + /** * waterline-orientdb diff --git a/package.json b/package.json index b0d19af..709fdc2 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "readmeFilename": "README.md", "dependencies": { "async": "~0.9.0", - "debug-logger": "~0.3.0", + "debug-logger": "^0.3.1", "lodash": "^3.3.0", "oriento": "~1.1.0", "waterline-criteria": "~0.11.1", From 6b824b5e10c02b800e96bb7ca5528b47672e0147 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 26 Feb 2015 18:14:14 +0000 Subject: [PATCH 79/81] Code coverage tuning; Increase test-integration timeouts (travis I'm looking at you) --- lib/adapter.js | 9 ++++----- test/integration/runner.js | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/adapter.js b/lib/adapter.js index 8537639..05b142b 100644 --- a/lib/adapter.js +++ b/lib/adapter.js @@ -8,9 +8,6 @@ var log = require('debug-logger').config({ ensureNewline: ensureNewline })('wate Connection = require('./connection'), utils = require('./utils'); - - - /** * waterline-orientdb * @@ -95,7 +92,7 @@ module.exports = (function() { database : 'waterline', host : 'localhost', port : 2424, - //schema : false, // to be consistent with OrientDB we should default to false but breaks 4 tests + // schema : false, // to be consistent with OrientDB we should default to false but breaks 4 tests // Additional options options: { @@ -173,15 +170,17 @@ module.exports = (function() { */ teardown : function(conn, cb) { log.debug('teardown:', conn); - + /* istanbul ignore if: standard waterline-adapter code */ if ( typeof conn == 'function') { cb = conn; conn = null; } + /* istanbul ignore if: standard waterline-adapter code */ if (!conn) { connections = {}; return cb(); } + /* istanbul ignore if: standard waterline-adapter code */ if (!connections[conn]) return cb(); delete connections[conn]; diff --git a/test/integration/runner.js b/test/integration/runner.js index 38fb7d9..51996bd 100644 --- a/test/integration/runner.js +++ b/test/integration/runner.js @@ -79,7 +79,7 @@ new TestRunner({ // Mocha options // reference: https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically mocha: { - timeout: 10000, + timeout: 12000, reporter: 'spec', //grep: 'should return model instances' }, From 804d1679772746581322827d71337ab7b23530af Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 26 Feb 2015 18:35:37 +0000 Subject: [PATCH 80/81] Readme --- README.md | 9 +++++---- test/integration/runner.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b263c41..e718b08 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ var config = { // Additional options options: { + // DB Options // // database type: graph | document @@ -162,7 +163,7 @@ Waterline-orientdb implements its own custom join function so when the user runs The main difference between waterline-orientdb and [sails-orientdb](https://github.com/vjsrinath/sails-orientdb) is the way associations/edges are created. In `sails-orientdb` a special attribute named 'edge' is required while waterline-orientdb tries to adhere to waterline specification. #### ID -Waterline-orientdb mimics sails-mongo adapter behaviour and maps the logical `id` attribute to the required `@rid` physical-layer OrientDB Record ID. Because of this it's not necessary, or advised, to declare an `id` attribute on your model definitions. +Waterline-orientdb mimics sails-mongo adapter behaviour and maps the logical `id` attribute to the required `@rid` physical-layer OrientDB Record ID. Because of this it's not necessary to declare an `id` attribute on your model definitions. ## Usage @@ -175,9 +176,9 @@ Waterline-orientdb mimics sails-mongo adapter behaviour and maps the logical `id It's possible to force the class of a model by adding the property `orientdbClass` to the definition. Generally this is not required as `waterline-orientdb` can determine which is the best class to use, so it should only be used in special cases. Possible values: * `undefined` - the default and recommended option. The appropriate class will be determined for the model; * `""` or `"document"` - class will be the default OrientDB document class; -* `"V"`- class will be Vertex; -* `"E"`- class will be Edge. - +* `"V"` - class will be Vertex; +* `"E"` - class will be Edge. + Example: ```javascript { diff --git a/test/integration/runner.js b/test/integration/runner.js index 51996bd..6fef91f 100644 --- a/test/integration/runner.js +++ b/test/integration/runner.js @@ -79,7 +79,7 @@ new TestRunner({ // Mocha options // reference: https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically mocha: { - timeout: 12000, + timeout: 15000, reporter: 'spec', //grep: 'should return model instances' }, From 9bdd47f8ebc9e0373e9fb5dd3370a24250feb9d4 Mon Sep 17 00:00:00 2001 From: Dario Marcelino Date: Thu, 26 Feb 2015 18:50:52 +0000 Subject: [PATCH 81/81] prepare v0.10.40 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 709fdc2..690ec55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "waterline-orientdb", - "version": "0.10.33", + "version": "0.10.40", "description": "OrientDB adapter for Waterline / Sails.js ORM", "main": "./lib/adapter.js", "scripts": {