From 360866d3f56e56e6d851c8ef6454b2d7a3f26a75 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Thu, 22 Oct 2015 11:06:38 -0400 Subject: [PATCH 01/16] Fix tests actually running (mocha.start.js wasn't being added, so co-mocha wasn't used, and generator function tests were passing without actually running) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f21779d..a944a8c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "scripts": { "lint": "node_modules/standard/bin/cmd.js src/index.js", "build": "node_modules/webpack/bin/webpack.js --config webpack.config.js --progress --colors", - "mocha": "mocha test/*.spec.js --timeout 20000 --reporter spec", + "mocha": "mocha --timeout 20000 --reporter spec mocha.start.js test/*.spec.js", "test": "npm run lint && npm run build && npm run mocha" }, "standard": { From c678b6b2c2d554f497fb10661411961a5932917a Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Thu, 22 Oct 2015 11:30:10 -0400 Subject: [PATCH 02/16] Allow passing database test configuration using environment variables (DB_HOST, DB_USER, DB_NAME) --- mocha.start.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mocha.start.js b/mocha.start.js index ee83284..35ad722 100644 --- a/mocha.start.js +++ b/mocha.start.js @@ -58,8 +58,9 @@ beforeEach(function () { adapter = new DSSqlAdapter({ client: 'mysql', connection: { - user: process.env.C9_USER || 'ubuntu', - database: process.env.C9_USER ? 'c9' : 'circle_test' + host: process.env.DB_HOST || 'localhost', + user: process.env.DB_USER || process.env.C9_USER || 'ubuntu', + database: process.env.DB_NAME || (process.env.C9_USER ? 'c9' : 'circle_test') //user: 'root', //database: 'test' } From 0eca2c70fcee96772ad15931ea5135b53c219f75 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Thu, 22 Oct 2015 13:23:28 -0400 Subject: [PATCH 03/16] Allow qualified local columns names if joined relations has same column name --- src/index.js | 16 ++++++++++------ test/findAll.spec.js | 10 ++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 8131117..0f74d41 100644 --- a/src/index.js +++ b/src/index.js @@ -64,14 +64,18 @@ function filterQuery (resourceConfig, params) { if (!joinedTables.some(t => t === relationPath.join('.'))) { let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName) - let table = getTable(localResourceConfig) - let localId = `${table}.${relation.localKey}` + if (relation) { + let table = getTable(localResourceConfig) + let localId = `${table}.${relation.localKey}` - let relationTable = getTable(relationResourceConfig) - let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}` + let relationTable = getTable(relationResourceConfig) + let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}` - query = query.join(relationTable, localId, foreignId) - joinedTables.push(relationPath.join('.')) + query = query.join(relationTable, localId, foreignId) + joinedTables.push(relationPath.join('.')) + } else { + // local column + } } localResourceConfig = relationResourceConfig } diff --git a/test/findAll.spec.js b/test/findAll.spec.js index 7c9448d..33f9fa7 100644 --- a/test/findAll.spec.js +++ b/test/findAll.spec.js @@ -182,4 +182,14 @@ describe('DSSqlAdapter#findAll', function () { assert.isUndefined(users[0].email); }); + it('should filter when relations have same column if column is qualified', function* () { + var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); + var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); + + // `id` column must be qualified with `user.` + var users = yield adapter.findAll(User, {'user.id': user1.id, 'profile.email': 'foo@test.com'}); + assert.equal(users.length, 1); + assert.equal(users[0].profileId, profile1.id); + }); + }); From e3cfef42f227ae6563cd8d0f4e9629bc5d0181b7 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Thu, 22 Oct 2015 13:35:20 -0400 Subject: [PATCH 04/16] Stable Version 0.11.3 --- dist/js-data-sql.js | 32 ++++++++++++++++++-------------- package.json | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/dist/js-data-sql.js b/dist/js-data-sql.js index 560d2fa..c85eed5 100644 --- a/dist/js-data-sql.js +++ b/dist/js-data-sql.js @@ -53,10 +53,10 @@ module.exports = function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - var knex = __webpack_require__(1); - var JSData = __webpack_require__(2); - var map = __webpack_require__(3); - var underscore = __webpack_require__(4); + var knex = __webpack_require__(2); + var JSData = __webpack_require__(3); + var map = __webpack_require__(4); + var underscore = __webpack_require__(1); var unique = __webpack_require__(5); var toString = __webpack_require__(6); var DSUtils = JSData.DSUtils; @@ -123,14 +123,18 @@ module.exports = var relation = _localResourceConfig$relationList$filter2[0]; - var _table = getTable(localResourceConfig); - var localId = _table + '.' + relation.localKey; + if (relation) { + var _table = getTable(localResourceConfig); + var localId = _table + '.' + relation.localKey; - var relationTable = getTable(relationResourceConfig); - var foreignId = relationTable + '.' + relationResourceConfig.idAttribute; + var relationTable = getTable(relationResourceConfig); + var foreignId = relationTable + '.' + relationResourceConfig.idAttribute; - query = query.join(relationTable, localId, foreignId); - joinedTables.push(relationPath.join('.')); + query = query.join(relationTable, localId, foreignId); + joinedTables.push(relationPath.join('.')); + } else { + // local column + } } localResourceConfig = relationResourceConfig; }; @@ -471,25 +475,25 @@ module.exports = /* 1 */ /***/ function(module, exports) { - module.exports = require("knex"); + module.exports = require("mout/string/underscore"); /***/ }, /* 2 */ /***/ function(module, exports) { - module.exports = require("js-data"); + module.exports = require("knex"); /***/ }, /* 3 */ /***/ function(module, exports) { - module.exports = require("mout/array/map"); + module.exports = require("js-data"); /***/ }, /* 4 */ /***/ function(module, exports) { - module.exports = require("mout/string/underscore"); + module.exports = require("mout/array/map"); /***/ }, /* 5 */ diff --git a/package.json b/package.json index a944a8c..f1f8598 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "js-data-sql", "description": "Postgres/MySQL/MariaDB/SQLite3 adapter for js-data.", - "version": "0.11.2", + "version": "0.11.3", "homepage": "http://www.js-data.io/docs/dssqladapter", "repository": { "type": "git", From b1ec3de26c1640caeef361dd529d8b9c06afe623 Mon Sep 17 00:00:00 2001 From: Mike Eldridge Date: Thu, 22 Oct 2015 15:20:01 -0500 Subject: [PATCH 05/16] #10 - transaction support via knex --- package.json | 1 + src/index.js | 19 +++++--- test/create_trx.spec.js | 66 ++++++++++++++++++++++++++++ test/destroy_trx.spec.js | 48 +++++++++++++++++++++ test/update_trx.spec.js | 93 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 test/create_trx.spec.js create mode 100644 test/destroy_trx.spec.js create mode 100644 test/update_trx.spec.js diff --git a/package.json b/package.json index f21779d..843fea6 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "babel-loader": "5.3.2", "bluebird": "2.10.2", "chai": "3.3.0", + "co": "^4.6.0", "co-mocha": "1.1.2", "mocha": "2.3.3", "standard": "5.3.1", diff --git a/src/index.js b/src/index.js index 8131117..e639d7b 100644 --- a/src/index.js +++ b/src/index.js @@ -19,9 +19,10 @@ function getTable (resourceConfig) { return resourceConfig.table || underscore(resourceConfig.name) } -function filterQuery (resourceConfig, params) { +function filterQuery (resourceConfig, params, options) { let table = getTable(resourceConfig) - let query = this.query.select(`${table}.*`).from(table) + let query = options && options.transaction || this.query + query = query.select(`${table}.*`).from(table) params = params || {} params.where = params.where || {} params.orderBy = params.orderBy || params.sort @@ -297,7 +298,8 @@ class DSSqlAdapter { let instance options = options || {} options.with = options.with || [] - return this.query + let query = options && options.transaction || this.query + return query .select('*') .from(getTable(resourceConfig)) .where(resourceConfig.idAttribute, toString(id)) @@ -324,7 +326,8 @@ class DSSqlAdapter { create (resourceConfig, attrs, options) { attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])) - return this.query(getTable(resourceConfig)) + let query = options && options.transaction || this.query + return query(getTable(resourceConfig)) .insert(attrs, resourceConfig.idAttribute) .then(ids => { if (attrs[resourceConfig.idAttribute]) { @@ -339,7 +342,8 @@ class DSSqlAdapter { update (resourceConfig, id, attrs, options) { attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])) - return this.query(getTable(resourceConfig)) + let query = options && options.transaction || this.query + return query(getTable(resourceConfig)) .where(resourceConfig.idAttribute, toString(id)) .update(attrs) .then(() => this.find(resourceConfig, id, options)) @@ -360,8 +364,9 @@ class DSSqlAdapter { }) } - destroy (resourceConfig, id) { - return this.query(getTable(resourceConfig)) + destroy (resourceConfig, id, options) { + let query = options && options.transaction || this.query + return query(getTable(resourceConfig)) .where(resourceConfig.idAttribute, toString(id)) .del().then(() => undefined) } diff --git a/test/create_trx.spec.js b/test/create_trx.spec.js new file mode 100644 index 0000000..bd94701 --- /dev/null +++ b/test/create_trx.spec.js @@ -0,0 +1,66 @@ +describe('DSSqlAdapter#create + transaction', function () { + it('commit should persist created user in a sql db', function* () { + var id; + var co = require('co'); + + yield adapter.query.transaction(co.wrap(function * (trx) { + var createUser = yield adapter.create(User, {name: 'Jane'}, {transaction: trx}); + id = createUser.id; + assert.equal(createUser.name, 'Jane'); + assert.isDefined(createUser.id); + + var findUser = yield adapter.find(User, createUser.id, {transaction: trx}); + assert.equal(findUser.name, 'Jane'); + assert.isDefined(findUser.id); + assert.equalObjects(findUser, {id: id, name: 'Jane', age: null, profileId: null}); + + return findUser; + })).then( + function (user) { + assert.isObject(user, 'transaction returned user object'); + assert.equal(user.name, 'Jane'); + assert.isDefined(user.id); + assert.equalObjects(user, {id: id, name: 'Jane', age: null, profileId: null}); + }, + function (err) { + throw new Error('transaction threw exception!'); + } + ); + + try { + var findUser2 = yield adapter.find(User, id); + assert.isObject(findUser2, 'user committed to database'); + } catch(err) { + throw new Error('transaction failed to commit!'); + } + }); + + it('rollback should not persist created user in a sql db', function* () { + var id; + var co = require('co'); + + yield adapter.query.transaction(co.wrap(function * (trx) { + var createUser = yield adapter.create(User, {name: 'John'}, {transaction: trx}); + id = createUser.id; + assert.equal(createUser.name, 'John'); + assert.isDefined(createUser.id); + + var findUser = yield adapter.find(User, createUser.id, {transaction: trx}); + assert.equal(findUser.name, 'John'); + assert.isDefined(findUser.id); + assert.equalObjects(findUser, {id: id, name: 'John', age: null, profileId: null}); + + throw new Error('rollback'); + })).then( + function () { throw new Error('transaction did not throw exception!') }, + function (err) { assert.equal(err.message, 'rollback') } + ); + + try { + var findUser2 = yield adapter.find(User, id); + throw new Error('transaction failed to roll back!'); + } catch(err) { + assert.equal(err.message, 'Not Found!'); + } + }); +}); diff --git a/test/destroy_trx.spec.js b/test/destroy_trx.spec.js new file mode 100644 index 0000000..fd98a0d --- /dev/null +++ b/test/destroy_trx.spec.js @@ -0,0 +1,48 @@ +describe('DSSqlAdapter#destroy + transaction', function () { + it('commit should destroy a user from a Sql db', function* () { + var co = require('co'); + + var createUser = yield adapter.create(User, {name: 'John'}) + var id = createUser.id; + + yield adapter.query.transaction(co.wrap(function * (trx) { + var destroyUser = yield adapter.destroy(User, createUser.id, {transaction: trx}); + assert.isFalse(!!destroyUser); + })); + + try { + var findUser = yield adapter.find(User, id); + throw new Error('Should not have reached here!'); + } catch (err) { + assert.equal(err.message, 'Not Found!'); + } + }); + + it('rollback should not destroy a user from a Sql db', function* () { + var co = require('co'); + + var createUser = yield adapter.create(User, {name: 'John'}) + var id = createUser.id; + + yield adapter.query.transaction(co.wrap(function * (trx) { + var destroyUser = yield adapter.destroy(User, createUser.id, {transaction: trx}); + assert.isFalse(!!destroyUser); + + throw new Error('rollback'); + })).then( + function () { throw new Error('transaction did not throw exception!') }, + function (err) { assert.equal(err.message, 'rollback') } + ); + + try { + var findUser = yield adapter.find(User, id); + assert.isObject(findUser, 'user still exists'); + } catch (err) { + if (err.message == 'Not Found!') { + throw new Error('transaction did not roll back'); + } else { + throw new Error('caught exception trying to locate user'); + } + } + }); +}); diff --git a/test/update_trx.spec.js b/test/update_trx.spec.js new file mode 100644 index 0000000..d63614d --- /dev/null +++ b/test/update_trx.spec.js @@ -0,0 +1,93 @@ +describe('DSSqlAdapter#update + transaction', function () { + it('commit should update a user in a Sql db', function* () { + var co = require('co'); + + var user = yield adapter.create(User, {name: 'John'}) + var id = user.id; + assert.equal(user.name, 'John'); + assert.isDefined(user.id); + + var foundUser = yield adapter.find(User, user.id); + assert.equal(foundUser.name, 'John'); + assert.isDefined(foundUser.id); + assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null}); + + yield adapter.query.transaction(co.wrap(function * (trx) { + var updatedUser = yield adapter.update(User, foundUser.id, {name: 'Johnny'}, {transaction: trx}); + assert.equal(updatedUser.name, 'Johnny'); + assert.isDefined(updatedUser.id); + assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); + + var foundUser2 = yield adapter.find(User, updatedUser.id, {transaction: trx}); + assert.equal(foundUser2.name, 'Johnny'); + assert.isDefined(foundUser2.id); + assert.equalObjects(foundUser2, {id: id, name: 'Johnny', age: null, profileId: null}); + + return foundUser2; + })).then( + function (user) { assert.isObject(user, 'transaction returned user object') }, + function (err) { throw new Error('transaction threw exception!') } + ); + + var foundUser3 = yield adapter.find(User, user.id); + assert.equal(foundUser3.name, 'Johnny'); + assert.isDefined(foundUser3.id); + assert.equalObjects(foundUser3, {id: id, name: 'Johnny', age: null, profileId: null}); + + var destroyUser = yield adapter.destroy(User, foundUser3.id); + assert.isFalse(!!destroyUser); + + try { + yield adapter.find(User, id); + throw new Error('Should not have reached here!'); + } catch (err) { + assert.equal(err.message, 'Not Found!'); + } + }); + + it('rollback should not update a user in a Sql db', function* () { + var co = require('co'); + + var user = yield adapter.create(User, {name: 'John'}) + var id = user.id; + assert.equal(user.name, 'John'); + assert.isDefined(user.id); + + var foundUser = yield adapter.find(User, user.id); + assert.equal(foundUser.name, 'John'); + assert.isDefined(foundUser.id); + assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null}); + + yield adapter.query.transaction(co.wrap(function * (trx) { + var updatedUser = yield adapter.update(User, foundUser.id, {name: 'Johnny'}, {transaction: trx}); + assert.equal(updatedUser.name, 'Johnny'); + assert.isDefined(updatedUser.id); + assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); + + var foundUser2 = yield adapter.find(User, updatedUser.id, {transaction: trx}); + assert.equal(foundUser2.name, 'Johnny'); + assert.isDefined(foundUser2.id); + assert.equalObjects(foundUser2, {id: id, name: 'Johnny', age: null, profileId: null}); + + throw new Error('rollback'); + })).then( + function () { throw new Error('transaction did not throw exception!') }, + function (err) { assert.equal(err.message, 'rollback') } + ); + + var foundUser3 = yield adapter.find(User, user.id); + assert.equal(foundUser3.name, 'John'); + assert.isDefined(foundUser3.id); + assert.equalObjects(foundUser3, {id: id, name: 'John', age: null, profileId: null}); + + var destroyUser = yield adapter.destroy(User, foundUser3.id); + assert.isFalse(!!destroyUser); + + try { + yield adapter.find(User, id); + throw new Error('Should not have reached here!'); + } catch (err) { + assert.equal(err.message, 'Not Found!'); + } + }); +}); From 49bb7cc17d2e31ba8eb5b310d320c611ff800dd6 Mon Sep 17 00:00:00 2001 From: Mike Eldridge Date: Thu, 22 Oct 2015 22:59:26 -0500 Subject: [PATCH 06/16] #10 - move co into mocha.start.js and tidy up the transaction tests --- mocha.start.js | 3 +- test/create_trx.spec.js | 64 +++++++++-------------------- test/destroy_trx.spec.js | 33 +++++---------- test/update_trx.spec.js | 88 +++++++++------------------------------- 4 files changed, 52 insertions(+), 136 deletions(-) diff --git a/mocha.start.js b/mocha.start.js index 35ad722..af0bffa 100644 --- a/mocha.start.js +++ b/mocha.start.js @@ -38,7 +38,8 @@ var globals = module.exports = { }], TYPES_EXCEPT_FUNCTION: ['string', 123, 123.123, null, undefined, {}, [], true, false], assert: assert, - adapter: undefined + adapter: undefined, + co: require('co') }; var test = new mocha(); diff --git a/test/create_trx.spec.js b/test/create_trx.spec.js index bd94701..7808b79 100644 --- a/test/create_trx.spec.js +++ b/test/create_trx.spec.js @@ -1,64 +1,40 @@ describe('DSSqlAdapter#create + transaction', function () { it('commit should persist created user in a sql db', function* () { var id; - var co = require('co'); yield adapter.query.transaction(co.wrap(function * (trx) { var createUser = yield adapter.create(User, {name: 'Jane'}, {transaction: trx}); id = createUser.id; assert.equal(createUser.name, 'Jane'); assert.isDefined(createUser.id); + })); - var findUser = yield adapter.find(User, createUser.id, {transaction: trx}); - assert.equal(findUser.name, 'Jane'); - assert.isDefined(findUser.id); - assert.equalObjects(findUser, {id: id, name: 'Jane', age: null, profileId: null}); - - return findUser; - })).then( - function (user) { - assert.isObject(user, 'transaction returned user object'); - assert.equal(user.name, 'Jane'); - assert.isDefined(user.id); - assert.equalObjects(user, {id: id, name: 'Jane', age: null, profileId: null}); - }, - function (err) { - throw new Error('transaction threw exception!'); - } - ); - - try { - var findUser2 = yield adapter.find(User, id); - assert.isObject(findUser2, 'user committed to database'); - } catch(err) { - throw new Error('transaction failed to commit!'); - } + var findUser = yield adapter.find(User, id); + assert.isObject(findUser, 'user committed to database'); + assert.equal(findUser.name, 'Jane'); + assert.isDefined(findUser.id); + assert.equalObjects(findUser, {id: id, name: 'Jane', age: null, profileId: null}); }); it('rollback should not persist created user in a sql db', function* () { var id; - var co = require('co'); - - yield adapter.query.transaction(co.wrap(function * (trx) { - var createUser = yield adapter.create(User, {name: 'John'}, {transaction: trx}); - id = createUser.id; - assert.equal(createUser.name, 'John'); - assert.isDefined(createUser.id); - var findUser = yield adapter.find(User, createUser.id, {transaction: trx}); - assert.equal(findUser.name, 'John'); - assert.isDefined(findUser.id); - assert.equalObjects(findUser, {id: id, name: 'John', age: null, profileId: null}); - - throw new Error('rollback'); - })).then( - function () { throw new Error('transaction did not throw exception!') }, - function (err) { assert.equal(err.message, 'rollback') } - ); + try { + yield adapter.query.transaction(co.wrap(function * (trx) { + var createUser = yield adapter.create(User, {name: 'John'}, {transaction: trx}); + id = createUser.id; + assert.equal(createUser.name, 'John'); + assert.isDefined(createUser.id); + + throw new Error('rollback'); + })); + } catch (err) { + assert.equal(err.message, 'rollback'); + } try { - var findUser2 = yield adapter.find(User, id); - throw new Error('transaction failed to roll back!'); + var findUser = yield adapter.find(User, id); + throw new Error('user committed to database'); } catch(err) { assert.equal(err.message, 'Not Found!'); } diff --git a/test/destroy_trx.spec.js b/test/destroy_trx.spec.js index fd98a0d..2d7b47c 100644 --- a/test/destroy_trx.spec.js +++ b/test/destroy_trx.spec.js @@ -1,13 +1,10 @@ describe('DSSqlAdapter#destroy + transaction', function () { it('commit should destroy a user from a Sql db', function* () { - var co = require('co'); - var createUser = yield adapter.create(User, {name: 'John'}) var id = createUser.id; yield adapter.query.transaction(co.wrap(function * (trx) { - var destroyUser = yield adapter.destroy(User, createUser.id, {transaction: trx}); - assert.isFalse(!!destroyUser); + return adapter.destroy(User, id, {transaction: trx}); })); try { @@ -19,30 +16,20 @@ describe('DSSqlAdapter#destroy + transaction', function () { }); it('rollback should not destroy a user from a Sql db', function* () { - var co = require('co'); - var createUser = yield adapter.create(User, {name: 'John'}) var id = createUser.id; - yield adapter.query.transaction(co.wrap(function * (trx) { - var destroyUser = yield adapter.destroy(User, createUser.id, {transaction: trx}); - assert.isFalse(!!destroyUser); - - throw new Error('rollback'); - })).then( - function () { throw new Error('transaction did not throw exception!') }, - function (err) { assert.equal(err.message, 'rollback') } - ); - try { - var findUser = yield adapter.find(User, id); - assert.isObject(findUser, 'user still exists'); + yield adapter.query.transaction(co.wrap(function * (trx) { + yield adapter.destroy(User, createUser.id, {transaction: trx}); + + throw new Error('rollback'); + })); } catch (err) { - if (err.message == 'Not Found!') { - throw new Error('transaction did not roll back'); - } else { - throw new Error('caught exception trying to locate user'); - } + assert.equal(err.message, 'rollback'); } + + var findUser = yield adapter.find(User, id); + assert.isObject(findUser, 'user still exists'); }); }); diff --git a/test/update_trx.spec.js b/test/update_trx.spec.js index d63614d..fe16b0c 100644 --- a/test/update_trx.spec.js +++ b/test/update_trx.spec.js @@ -1,93 +1,45 @@ describe('DSSqlAdapter#update + transaction', function () { it('commit should update a user in a Sql db', function* () { - var co = require('co'); - var user = yield adapter.create(User, {name: 'John'}) var id = user.id; assert.equal(user.name, 'John'); assert.isDefined(user.id); - var foundUser = yield adapter.find(User, user.id); - assert.equal(foundUser.name, 'John'); - assert.isDefined(foundUser.id); - assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null}); - yield adapter.query.transaction(co.wrap(function * (trx) { - var updatedUser = yield adapter.update(User, foundUser.id, {name: 'Johnny'}, {transaction: trx}); + var updatedUser = yield adapter.update(User, id, {name: 'Johnny'}, {transaction: trx}); assert.equal(updatedUser.name, 'Johnny'); assert.isDefined(updatedUser.id); assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); + })); - var foundUser2 = yield adapter.find(User, updatedUser.id, {transaction: trx}); - assert.equal(foundUser2.name, 'Johnny'); - assert.isDefined(foundUser2.id); - assert.equalObjects(foundUser2, {id: id, name: 'Johnny', age: null, profileId: null}); - - return foundUser2; - })).then( - function (user) { assert.isObject(user, 'transaction returned user object') }, - function (err) { throw new Error('transaction threw exception!') } - ); - - var foundUser3 = yield adapter.find(User, user.id); - assert.equal(foundUser3.name, 'Johnny'); - assert.isDefined(foundUser3.id); - assert.equalObjects(foundUser3, {id: id, name: 'Johnny', age: null, profileId: null}); - - var destroyUser = yield adapter.destroy(User, foundUser3.id); - assert.isFalse(!!destroyUser); - - try { - yield adapter.find(User, id); - throw new Error('Should not have reached here!'); - } catch (err) { - assert.equal(err.message, 'Not Found!'); - } + var foundUser = yield adapter.find(User, id); + assert.equal(foundUser.name, 'Johnny'); + assert.isDefined(foundUser.id); + assert.equalObjects(foundUser, {id: id, name: 'Johnny', age: null, profileId: null}); }); it('rollback should not update a user in a Sql db', function* () { - var co = require('co'); - var user = yield adapter.create(User, {name: 'John'}) var id = user.id; assert.equal(user.name, 'John'); assert.isDefined(user.id); - var foundUser = yield adapter.find(User, user.id); - assert.equal(foundUser.name, 'John'); - assert.isDefined(foundUser.id); - assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null}); - - yield adapter.query.transaction(co.wrap(function * (trx) { - var updatedUser = yield adapter.update(User, foundUser.id, {name: 'Johnny'}, {transaction: trx}); - assert.equal(updatedUser.name, 'Johnny'); - assert.isDefined(updatedUser.id); - assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); - - var foundUser2 = yield adapter.find(User, updatedUser.id, {transaction: trx}); - assert.equal(foundUser2.name, 'Johnny'); - assert.isDefined(foundUser2.id); - assert.equalObjects(foundUser2, {id: id, name: 'Johnny', age: null, profileId: null}); - - throw new Error('rollback'); - })).then( - function () { throw new Error('transaction did not throw exception!') }, - function (err) { assert.equal(err.message, 'rollback') } - ); - - var foundUser3 = yield adapter.find(User, user.id); - assert.equal(foundUser3.name, 'John'); - assert.isDefined(foundUser3.id); - assert.equalObjects(foundUser3, {id: id, name: 'John', age: null, profileId: null}); - - var destroyUser = yield adapter.destroy(User, foundUser3.id); - assert.isFalse(!!destroyUser); - try { - yield adapter.find(User, id); - throw new Error('Should not have reached here!'); + yield adapter.query.transaction(co.wrap(function * (trx) { + var updatedUser = yield adapter.update(User, id, {name: 'Johnny'}, {transaction: trx}); + assert.equal(updatedUser.name, 'Johnny'); + assert.isDefined(updatedUser.id); + assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); + + throw new Error('rollback'); + })); } catch (err) { - assert.equal(err.message, 'Not Found!'); + assert.equal(err.message, 'rollback'); } + + var foundUser = yield adapter.find(User, id); + assert.equal(foundUser.name, 'John'); + assert.isDefined(foundUser.id); + assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null}); }); }); From 7d66910749e463509636a6cff47c13a5abc666a7 Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Fri, 23 Oct 2015 00:31:24 -0400 Subject: [PATCH 07/16] Stable Version 0.11.4 --- dist/js-data-sql.js | 19 ++++++++++++------- package.json | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/dist/js-data-sql.js b/dist/js-data-sql.js index c85eed5..6e69428 100644 --- a/dist/js-data-sql.js +++ b/dist/js-data-sql.js @@ -67,9 +67,10 @@ module.exports = return resourceConfig.table || underscore(resourceConfig.name); } - function filterQuery(resourceConfig, params) { + function filterQuery(resourceConfig, params, options) { var table = getTable(resourceConfig); - var query = this.query.select(table + '.*').from(table); + var query = options && options.transaction || this.query; + query = query.select(table + '.*').from(table); params = params || {}; params.where = params.where || {}; params.orderBy = params.orderBy || params.sort; @@ -378,7 +379,8 @@ module.exports = var instance = undefined; options = options || {}; options['with'] = options['with'] || []; - return this.query.select('*').from(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).then(function (rows) { + var query = options && options.transaction || this.query; + return query.select('*').from(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).then(function (rows) { if (!rows.length) { return DSUtils.Promise.reject(new Error('Not Found!')); } else { @@ -410,7 +412,8 @@ module.exports = var _this4 = this; attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); - return this.query(getTable(resourceConfig)).insert(attrs, resourceConfig.idAttribute).then(function (ids) { + var query = options && options.transaction || this.query; + return query(getTable(resourceConfig)).insert(attrs, resourceConfig.idAttribute).then(function (ids) { if (attrs[resourceConfig.idAttribute]) { return _this4.find(resourceConfig, attrs[resourceConfig.idAttribute], options); } else if (ids.length) { @@ -426,7 +429,8 @@ module.exports = var _this5 = this; attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); - return this.query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).update(attrs).then(function () { + var query = options && options.transaction || this.query; + return query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).update(attrs).then(function () { return _this5.find(resourceConfig, id, options); }); } @@ -452,8 +456,9 @@ module.exports = } }, { key: 'destroy', - value: function destroy(resourceConfig, id) { - return this.query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).del().then(function () { + value: function destroy(resourceConfig, id, options) { + var query = options && options.transaction || this.query; + return query(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).del().then(function () { return undefined; }); } diff --git a/package.json b/package.json index 499f7f9..548244a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "js-data-sql", "description": "Postgres/MySQL/MariaDB/SQLite3 adapter for js-data.", - "version": "0.11.3", + "version": "0.11.4", "homepage": "http://www.js-data.io/docs/dssqladapter", "repository": { "type": "git", From 4a43e5c62d5f823111a6a6f203f44cca0232cace Mon Sep 17 00:00:00 2001 From: Nathan Vecchiarelli Date: Fri, 23 Oct 2015 17:04:32 -0400 Subject: [PATCH 08/16] exposed filterQuery on the API for all to (ab)use --- src/index.js | 288 +++++++++++++++++++++++++-------------------------- 1 file changed, 144 insertions(+), 144 deletions(-) diff --git a/src/index.js b/src/index.js index 410d0aa..e0e7642 100644 --- a/src/index.js +++ b/src/index.js @@ -19,145 +19,6 @@ function getTable (resourceConfig) { return resourceConfig.table || underscore(resourceConfig.name) } -function filterQuery (resourceConfig, params, options) { - let table = getTable(resourceConfig) - let query = options && options.transaction || this.query - query = query.select(`${table}.*`).from(table) - params = params || {} - params.where = params.where || {} - params.orderBy = params.orderBy || params.sort - params.skip = params.skip || params.offset - - let joinedTables = [] - - DSUtils.forEach(DSUtils.keys(params), k => { - let v = params[k] - if (!DSUtils.contains(reserved, k)) { - if (DSUtils.isObject(v)) { - params.where[k] = v - } else { - params.where[k] = { - '==': v - } - } - delete params[k] - } - }) - - if (!DSUtils.isEmpty(params.where)) { - DSUtils.forOwn(params.where, (criteria, field) => { - if (!DSUtils.isObject(criteria)) { - params.where[field] = { - '==': criteria - } - } - - DSUtils.forOwn(criteria, (v, op) => { - if (DSUtils.contains(field, '.')) { - let parts = field.split('.') - let localResourceConfig = resourceConfig - - let relationPath = [] - while (parts.length >= 2) { - let relationName = parts.shift() - let relationResourceConfig = resourceConfig.getResource(relationName) - relationPath.push(relationName) - - if (!joinedTables.some(t => t === relationPath.join('.'))) { - let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName) - if (relation) { - let table = getTable(localResourceConfig) - let localId = `${table}.${relation.localKey}` - - let relationTable = getTable(relationResourceConfig) - let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}` - - query = query.join(relationTable, localId, foreignId) - joinedTables.push(relationPath.join('.')) - } else { - // local column - } - } - localResourceConfig = relationResourceConfig - } - - field = `${getTable(localResourceConfig)}.${parts[0]}` - } - - if (op === '==' || op === '===') { - query = query.where(field, v) - } else if (op === '!=' || op === '!==') { - query = query.where(field, '!=', v) - } else if (op === '>') { - query = query.where(field, '>', v) - } else if (op === '>=') { - query = query.where(field, '>=', v) - } else if (op === '<') { - query = query.where(field, '<', v) - } else if (op === '<=') { - query = query.where(field, '<=', v) - // } else if (op === 'isectEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) - // } else if (op === 'isectNotEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) - } else if (op === 'in') { - query = query.where(field, 'in', v) - } else if (op === 'notIn') { - query = query.whereNotIn(field, v) - } else if (op === 'like') { - query = query.where(field, 'like', v) - } else if (op === '|==' || op === '|===') { - query = query.orWhere(field, v) - } else if (op === '|!=' || op === '|!==') { - query = query.orWhere(field, '!=', v) - } else if (op === '|>') { - query = query.orWhere(field, '>', v) - } else if (op === '|>=') { - query = query.orWhere(field, '>=', v) - } else if (op === '|<') { - query = query.orWhere(field, '<', v) - } else if (op === '|<=') { - query = query.orWhere(field, '<=', v) - // } else if (op === '|isectEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) - // } else if (op === '|isectNotEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) - } else if (op === '|in') { - query = query.orWhere(field, 'in', v) - } else if (op === '|notIn') { - query = query.orWhereNotIn(field, v) - } else { - throw new Error('Operator not found') - } - }) - }) - } - - if (params.orderBy) { - if (DSUtils.isString(params.orderBy)) { - params.orderBy = [ - [params.orderBy, 'asc'] - ] - } - for (var i = 0; i < params.orderBy.length; i++) { - if (DSUtils.isString(params.orderBy[i])) { - params.orderBy[i] = [params.orderBy[i], 'asc'] - } - query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc') - } - } - - if (params.skip) { - query = query.offset(+params.offset) - } - - if (params.limit) { - query = query.limit(+params.limit) - } - - return query -} - function loadWithRelations (items, resourceConfig, options) { let tasks = [] let instance = Array.isArray(items) ? null : items @@ -322,7 +183,7 @@ class DSSqlAdapter { let items = null options = options || {} options.with = options.with || [] - return filterQuery.call(this, resourceConfig, params, options).then(_items => { + return this.filterQuery(resourceConfig, params, options).then(_items => { items = _items return loadWithRelations.call(this, _items, resourceConfig, options) }).then(() => items) @@ -355,15 +216,15 @@ class DSSqlAdapter { updateAll (resourceConfig, attrs, params, options) { attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])) - return filterQuery.call(this, resourceConfig, params, options).then(items => { + return this.filterQuery(resourceConfig, params, options).then(items => { return map(items, item => item[resourceConfig.idAttribute]) }).then(ids => { - return filterQuery.call(this, resourceConfig, params, options).update(attrs).then(() => { + return this.filterQuery(resourceConfig, params, options).update(attrs).then(() => { let _params = {where: {}} _params.where[resourceConfig.idAttribute] = { 'in': ids } - return filterQuery.call(this, resourceConfig, _params, options) + return this.filterQuery(resourceConfig, _params, options) }) }) } @@ -376,7 +237,146 @@ class DSSqlAdapter { } destroyAll (resourceConfig, params, options) { - return filterQuery.call(this, resourceConfig, params, options).del().then(() => undefined) + return this.filterQuery(resourceConfig, params, options).del().then(() => undefined) + } + + filterQuery (resourceConfig, params, options) { + let table = getTable(resourceConfig) + let query = options && options.transaction || this.query + query = query.select(`${table}.*`).from(table) + params = params || {} + params.where = params.where || {} + params.orderBy = params.orderBy || params.sort + params.skip = params.skip || params.offset + + let joinedTables = [] + + DSUtils.forEach(DSUtils.keys(params), k => { + let v = params[k] + if (!DSUtils.contains(reserved, k)) { + if (DSUtils.isObject(v)) { + params.where[k] = v + } else { + params.where[k] = { + '==': v + } + } + delete params[k] + } + }) + + if (!DSUtils.isEmpty(params.where)) { + DSUtils.forOwn(params.where, (criteria, field) => { + if (!DSUtils.isObject(criteria)) { + params.where[field] = { + '==': criteria + } + } + + DSUtils.forOwn(criteria, (v, op) => { + if (DSUtils.contains(field, '.')) { + let parts = field.split('.') + let localResourceConfig = resourceConfig + + let relationPath = [] + while (parts.length >= 2) { + let relationName = parts.shift() + let relationResourceConfig = resourceConfig.getResource(relationName) + relationPath.push(relationName) + + if (!joinedTables.some(t => t === relationPath.join('.'))) { + let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName) + if (relation) { + let table = getTable(localResourceConfig) + let localId = `${table}.${relation.localKey}` + + let relationTable = getTable(relationResourceConfig) + let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}` + + query = query.join(relationTable, localId, foreignId) + joinedTables.push(relationPath.join('.')) + } else { + // local column + } + } + localResourceConfig = relationResourceConfig + } + + field = `${getTable(localResourceConfig)}.${parts[0]}` + } + + if (op === '==' || op === '===') { + query = query.where(field, v) + } else if (op === '!=' || op === '!==') { + query = query.where(field, '!=', v) + } else if (op === '>') { + query = query.where(field, '>', v) + } else if (op === '>=') { + query = query.where(field, '>=', v) + } else if (op === '<') { + query = query.where(field, '<', v) + } else if (op === '<=') { + query = query.where(field, '<=', v) + // } else if (op === 'isectEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === 'isectNotEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) + } else if (op === 'in') { + query = query.where(field, 'in', v) + } else if (op === 'notIn') { + query = query.whereNotIn(field, v) + } else if (op === 'like') { + query = query.where(field, 'like', v) + } else if (op === '|==' || op === '|===') { + query = query.orWhere(field, v) + } else if (op === '|!=' || op === '|!==') { + query = query.orWhere(field, '!=', v) + } else if (op === '|>') { + query = query.orWhere(field, '>', v) + } else if (op === '|>=') { + query = query.orWhere(field, '>=', v) + } else if (op === '|<') { + query = query.orWhere(field, '<', v) + } else if (op === '|<=') { + query = query.orWhere(field, '<=', v) + // } else if (op === '|isectEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === '|isectNotEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) + } else if (op === '|in') { + query = query.orWhere(field, 'in', v) + } else if (op === '|notIn') { + query = query.orWhereNotIn(field, v) + } else { + throw new Error('Operator not found') + } + }) + }) + } + + if (params.orderBy) { + if (DSUtils.isString(params.orderBy)) { + params.orderBy = [ + [params.orderBy, 'asc'] + ] + } + for (var i = 0; i < params.orderBy.length; i++) { + if (DSUtils.isString(params.orderBy[i])) { + params.orderBy[i] = [params.orderBy[i], 'asc'] + } + query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc') + } + } + + if (params.skip) { + query = query.offset(+params.offset) + } + + if (params.limit) { + query = query.limit(+params.limit) + } + + return query } } From dc81294dd1f68e3c79add77783bd66e4672ae7ca Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Sat, 24 Oct 2015 03:02:12 +0000 Subject: [PATCH 09/16] Added code coverage --- .gitignore | 1 + .npmignore | 36 ++++++++++++++++++++++++++++++++++++ README.md | 41 +++++++++++++++++++++++++++++++---------- circle.yml | 3 +++ package.json | 15 ++++++++++----- 5 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 .npmignore diff --git a/.gitignore b/.gitignore index 5a0bf29..645ffd9 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ node_modules .idea/ *.iml +coverage/ \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0e75e56 --- /dev/null +++ b/.npmignore @@ -0,0 +1,36 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Users Environment Variables +.lock-wscript + +.idea/ +*.iml +coverage/ +src/ +test/ +mocha.start.js +webpack.config.js \ No newline at end of file diff --git a/README.md b/README.md index c921b61..b32ae0f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ js-data logo -## js-data-sql [![npm version](https://img.shields.io/npm/v/js-data-sql.svg?style=flat-square)](https://www.npmjs.org/package/js-data-sql) [![Circle CI](https://img.shields.io/circleci/project/js-data/js-data-sql/master.svg?style=flat-square)](https://circleci.com/gh/js-data/js-data-sql/tree/master) [![npm downloads](https://img.shields.io/npm/dm/js-data-sql.svg?style=flat-square)](https://www.npmjs.org/package/js-data-sql) +## js-data-sql [![Slack Status][sl_b]][sl_l] [![npm version][npm_b]][npm_l] [![Circle CI][circle_b]][circle_l] [![npm downloads][dn_b]][dn_l] [![Coverage Status][cov_b]][cov_l] [![Codacy][cod_b]][cod_l] Postgres/MySQL/MariaDB/SQLite3 adapter for [js-data](http://www.js-data.io/). @@ -8,7 +8,7 @@ Postgres/MySQL/MariaDB/SQLite3 adapter for [js-data](http://www.js-data.io/). [DSSqlAdapter](http://www.js-data.io/docs/dssqladapter) ### Quick Start -`npm install --save js-data js-data-sql`. +`npm install --save knex js-data js-data-sql`. You also need to install the driver for the database you want to connect to. @@ -39,7 +39,7 @@ Read about using [JSData on the Server](http://www.js-data.io/docs/jsdata-on-the [CHANGELOG.md](https://github.com/js-data/js-data-sql/blob/master/CHANGELOG.md) ### Community -- [Gitter Channel](https://gitter.im/js-data/js-data) - Better than IRC! +- [Slack Channel](http://slack.js-data.io) - Better than IRC! - [Announcements](http://www.js-data.io/blog) - [Mailing List](https://groups.io/org/groupsio/jsdata) - Ask your questions! - [Issues](https://github.com/js-data/js-data-sql/issues) - Found a bug? Feature request? Submit an issue! @@ -48,19 +48,26 @@ Read about using [JSData on the Server](http://www.js-data.io/docs/jsdata-on-the ### Contributing -First, support is handled via the [Gitter Channel](https://gitter.im/js-data/js-data) and the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions there. +First, support is handled via the [Slack Channel](http://slack.js-data.io) and +the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions +there. -When submitting issues on GitHub, please include as much detail as possible to make debugging quick and easy. +When submitting issues on GitHub, please include as much detail as possible to +make debugging quick and easy. -- good - Your versions of js-data, js-data-sql, etc., relevant console logs/error, code examples that revealed the issue -- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or [bin](http://jsbin.com/?html,output) that demonstrates the issue -- best - A Pull Request that fixes the issue, including test coverage for the issue and the fix +- good - Your versions of js-data, js-data-sql, etc., relevant console +logs/error, code examples that revealed the issue +- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or +[bin](http://jsbin.com/?html,output) that demonstrates the issue +- best - A Pull Request that fixes the issue, including test coverage for the +issue and the fix [Github Issues](https://github.com/js-data/js-data-sql/issues). #### Submitting Pull Requests -1. Contribute to the issue/discussion that is the reason you'll be developing in the first place +1. Contribute to the issue/discussion that is the reason you'll be developing in +the first place 1. Fork js-data-sql 1. `git clone git@github.com:/js-data-sql.git` 1. `cd js-data-sql; npm install;` @@ -68,7 +75,8 @@ When submitting issues on GitHub, please include as much detail as possible to m 1. Run `npm test` (build and test) - You need io.js or Node 4.x that includes generator support without a flag 1. Your code will be linted and checked for formatting, the tests will be run -1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They will be committed when a release is cut. +1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They +will be committed when a release is cut. 1. Submit your PR and we'll review! 1. Thanks! @@ -109,3 +117,16 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +[sl_b]: http://slack.js-data.io/badge.svg +[sl_l]: http://slack.js-data.io +[npm_b]: https://img.shields.io/npm/v/js-data-sql.svg?style=flat +[npm_l]: https://www.npmjs.org/package/js-data-sql +[circle_b]: https://img.shields.io/circleci/project/js-data/js-data-sql/master.svg?style=flat +[circle_l]: https://circleci.com/gh/js-data/js-data-sql/tree/master +[dn_b]: https://img.shields.io/npm/dm/js-data-sql.svg?style=flat +[dn_l]: https://www.npmjs.org/package/js-data-sql +[cov_b]: https://img.shields.io/coveralls/js-data/js-data-sql/master.svg?style=flat +[cov_l]: https://coveralls.io/github/js-data/js-data-sql?branch=master +[cod_b]: https://img.shields.io/codacy/307c2e9399394fdaa5354cda7329516d.svg +[cod_l]: https://www.codacy.com/app/jasondobry/js-data-sql/dashboard diff --git a/circle.yml b/circle.yml index 8bcf7f9..c2da8a5 100644 --- a/circle.yml +++ b/circle.yml @@ -5,3 +5,6 @@ machine: database: override: - mysql -u ubuntu circle_test < test/setup.sql +test: + override: + - npm run ci diff --git a/package.json b/package.json index 548244a..6083fed 100644 --- a/package.json +++ b/package.json @@ -31,17 +31,22 @@ "babel-loader": "5.3.2", "bluebird": "2.10.2", "chai": "3.3.0", - "co": "^4.6.0", + "co": "4.6.0", "co-mocha": "1.1.2", + "codacy-coverage": "1.1.3", + "coveralls": "2.11.4", + "istanbul": "0.4.0", "mocha": "2.3.3", "standard": "5.3.1", "webpack": "1.12.2" }, "scripts": { - "lint": "node_modules/standard/bin/cmd.js src/index.js", - "build": "node_modules/webpack/bin/webpack.js --config webpack.config.js --progress --colors", - "mocha": "mocha --timeout 20000 --reporter spec mocha.start.js test/*.spec.js", - "test": "npm run lint && npm run build && npm run mocha" + "lint": "standard src/index.js", + "build": "webpack --config webpack.config.js --progress --colors", + "mocha": "mocha --timeout 20000 --reporter dot mocha.start.js test/*.spec.js", + "cover": "istanbul cover --hook-run-in-context node_modules/mocha/bin/_mocha -- --timeout 20000 --reporter dot mocha.start.js test/*.spec.js", + "test": "npm run lint && npm run build && npm run cover", + "ci": "npm run test && cat coverage/lcov.info | coveralls || true && cat ./coverage/lcov.info | codacy-coverage || true" }, "standard": { "parser": "babel-eslint" From 04a7961851617b5ecefad99a68738540f1ce9054 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Sat, 24 Oct 2015 04:46:08 +0000 Subject: [PATCH 10/16] Add support for custom queries. --- src/index.js | 14 ++++++++++++-- test/filterQuery.spec.js | 37 +++++++++++++++++++++++++++++++++++++ test/find.spec.js | 1 - test/findAll.spec.js | 1 - 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 test/filterQuery.spec.js diff --git a/src/index.js b/src/index.js index e0e7642..da03cc9 100644 --- a/src/index.js +++ b/src/index.js @@ -242,8 +242,18 @@ class DSSqlAdapter { filterQuery (resourceConfig, params, options) { let table = getTable(resourceConfig) - let query = options && options.transaction || this.query - query = query.select(`${table}.*`).from(table) + let query + + if (params instanceof Object.getPrototypeOf(this.query.client).QueryBuilder) { + query = params + params = {} + } else if (options && options.query) { + query = options.query || this.query + } else { + query = options && options.transaction || this.query + query = query.select(`${table}.*`).from(table) + } + params = params || {} params.where = params.where || {} params.orderBy = params.orderBy || params.sort diff --git a/test/filterQuery.spec.js b/test/filterQuery.spec.js new file mode 100644 index 0000000..2018313 --- /dev/null +++ b/test/filterQuery.spec.js @@ -0,0 +1,37 @@ +describe('DSSqlAdapter#filterQuery', function () { + + it('should use built-in query if no custom query provided', function* () { + var filterQuery = adapter.filterQuery(User); + assert.equal(filterQuery.toString(), 'select `user`.* from `user`') + }); + + it('should use custom query if passed as params (second parameter)', function* () { + var query = adapter.query.from('test'); + var filterQuery = adapter.filterQuery(User, query); + assert.equal(filterQuery.toString(), 'select * from `test`') + }); + + it('should use custom query if passed as options.query', function* () { + var query = adapter.query.from('test'); + var filterQuery = adapter.filterQuery(User, null, { query }); + assert.equal(filterQuery.toString(), 'select * from `test`') + }); + + it('should apply where from params to custom query', function* () { + var query = adapter.query.from('test'); + var filterQuery = adapter.filterQuery(User, { name: 'Sean' }, { query }); + assert.equal(filterQuery.toString(), 'select * from `test` where `name` = \'Sean\'') + }); + + it('should apply limit from params to custom query', function* () { + var query = adapter.query.from('test'); + var filterQuery = adapter.filterQuery(User, { limit: 2 }, { query }); + assert.equal(filterQuery.toString(), 'select * from `test` limit 2') + }); + + it('should apply order from params to custom query', function* () { + var query = adapter.query.from('test'); + var filterQuery = adapter.filterQuery(User, { orderBy: 'name' }, { query }); + assert.equal(filterQuery.toString(), 'select * from `test` order by `name` asc') + }); +}); diff --git a/test/find.spec.js b/test/find.spec.js index 30ca726..a961a06 100644 --- a/test/find.spec.js +++ b/test/find.spec.js @@ -1,4 +1,3 @@ -var Promise = require('bluebird'); describe('DSSqlAdapter#find', function () { it('should find a user in a Sql db', function* () { var user = yield adapter.create(User, {name: 'John'}); diff --git a/test/findAll.spec.js b/test/findAll.spec.js index 33f9fa7..550534f 100644 --- a/test/findAll.spec.js +++ b/test/findAll.spec.js @@ -1,4 +1,3 @@ -var Promise = require('bluebird'); describe('DSSqlAdapter#findAll', function () { it('should filter users', function* () { var users = yield adapter.findAll(User, { age: 30 }); From 254de72a6537a8effb49579db2b50708db0f9aca Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Mon, 26 Oct 2015 15:12:36 -0400 Subject: [PATCH 11/16] Stable Version 0.11.5 --- dist/js-data-sql.js | 343 +++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 178 insertions(+), 167 deletions(-) diff --git a/dist/js-data-sql.js b/dist/js-data-sql.js index 6e69428..257543f 100644 --- a/dist/js-data-sql.js +++ b/dist/js-data-sql.js @@ -53,10 +53,10 @@ module.exports = function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - var knex = __webpack_require__(2); - var JSData = __webpack_require__(3); - var map = __webpack_require__(4); - var underscore = __webpack_require__(1); + var knex = __webpack_require__(1); + var JSData = __webpack_require__(2); + var map = __webpack_require__(3); + var underscore = __webpack_require__(4); var unique = __webpack_require__(5); var toString = __webpack_require__(6); var DSUtils = JSData.DSUtils; @@ -67,159 +67,6 @@ module.exports = return resourceConfig.table || underscore(resourceConfig.name); } - function filterQuery(resourceConfig, params, options) { - var table = getTable(resourceConfig); - var query = options && options.transaction || this.query; - query = query.select(table + '.*').from(table); - params = params || {}; - params.where = params.where || {}; - params.orderBy = params.orderBy || params.sort; - params.skip = params.skip || params.offset; - - var joinedTables = []; - - DSUtils.forEach(DSUtils.keys(params), function (k) { - var v = params[k]; - if (!DSUtils.contains(reserved, k)) { - if (DSUtils.isObject(v)) { - params.where[k] = v; - } else { - params.where[k] = { - '==': v - }; - } - delete params[k]; - } - }); - - if (!DSUtils.isEmpty(params.where)) { - DSUtils.forOwn(params.where, function (criteria, field) { - if (!DSUtils.isObject(criteria)) { - params.where[field] = { - '==': criteria - }; - } - - DSUtils.forOwn(criteria, function (v, op) { - if (DSUtils.contains(field, '.')) { - (function () { - var parts = field.split('.'); - var localResourceConfig = resourceConfig; - - var relationPath = []; - - var _loop = function () { - var relationName = parts.shift(); - var relationResourceConfig = resourceConfig.getResource(relationName); - relationPath.push(relationName); - - if (!joinedTables.some(function (t) { - return t === relationPath.join('.'); - })) { - var _localResourceConfig$relationList$filter = localResourceConfig.relationList.filter(function (r) { - return r.relation === relationName; - }); - - var _localResourceConfig$relationList$filter2 = _slicedToArray(_localResourceConfig$relationList$filter, 1); - - var relation = _localResourceConfig$relationList$filter2[0]; - - if (relation) { - var _table = getTable(localResourceConfig); - var localId = _table + '.' + relation.localKey; - - var relationTable = getTable(relationResourceConfig); - var foreignId = relationTable + '.' + relationResourceConfig.idAttribute; - - query = query.join(relationTable, localId, foreignId); - joinedTables.push(relationPath.join('.')); - } else { - // local column - } - } - localResourceConfig = relationResourceConfig; - }; - - while (parts.length >= 2) { - _loop(); - } - - field = getTable(localResourceConfig) + '.' + parts[0]; - })(); - } - - if (op === '==' || op === '===') { - query = query.where(field, v); - } else if (op === '!=' || op === '!==') { - query = query.where(field, '!=', v); - } else if (op === '>') { - query = query.where(field, '>', v); - } else if (op === '>=') { - query = query.where(field, '>=', v); - } else if (op === '<') { - query = query.where(field, '<', v); - } else if (op === '<=') { - query = query.where(field, '<=', v); - // } else if (op === 'isectEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) - // } else if (op === 'isectNotEmpty') { - // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) - } else if (op === 'in') { - query = query.where(field, 'in', v); - } else if (op === 'notIn') { - query = query.whereNotIn(field, v); - } else if (op === 'like') { - query = query.where(field, 'like', v); - } else if (op === '|==' || op === '|===') { - query = query.orWhere(field, v); - } else if (op === '|!=' || op === '|!==') { - query = query.orWhere(field, '!=', v); - } else if (op === '|>') { - query = query.orWhere(field, '>', v); - } else if (op === '|>=') { - query = query.orWhere(field, '>=', v); - } else if (op === '|<') { - query = query.orWhere(field, '<', v); - } else if (op === '|<=') { - query = query.orWhere(field, '<=', v); - // } else if (op === '|isectEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) - // } else if (op === '|isectNotEmpty') { - // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) - } else if (op === '|in') { - query = query.orWhere(field, 'in', v); - } else if (op === '|notIn') { - query = query.orWhereNotIn(field, v); - } else { - throw new Error('Operator not found'); - } - }); - }); - } - - if (params.orderBy) { - if (DSUtils.isString(params.orderBy)) { - params.orderBy = [[params.orderBy, 'asc']]; - } - for (var i = 0; i < params.orderBy.length; i++) { - if (DSUtils.isString(params.orderBy[i])) { - params.orderBy[i] = [params.orderBy[i], 'asc']; - } - query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc'); - } - } - - if (params.skip) { - query = query.offset(+params.offset); - } - - if (params.limit) { - query = query.limit(+params.limit); - } - - return query; - } - function loadWithRelations(items, resourceConfig, options) { var _this = this; @@ -399,7 +246,7 @@ module.exports = var items = null; options = options || {}; options['with'] = options['with'] || []; - return filterQuery.call(this, resourceConfig, params, options).then(function (_items) { + return this.filterQuery(resourceConfig, params, options).then(function (_items) { items = _items; return loadWithRelations.call(_this3, _items, resourceConfig, options); }).then(function () { @@ -440,17 +287,17 @@ module.exports = var _this6 = this; attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); - return filterQuery.call(this, resourceConfig, params, options).then(function (items) { + return this.filterQuery(resourceConfig, params, options).then(function (items) { return map(items, function (item) { return item[resourceConfig.idAttribute]; }); }).then(function (ids) { - return filterQuery.call(_this6, resourceConfig, params, options).update(attrs).then(function () { + return _this6.filterQuery(resourceConfig, params, options).update(attrs).then(function () { var _params = { where: {} }; _params.where[resourceConfig.idAttribute] = { 'in': ids }; - return filterQuery.call(_this6, resourceConfig, _params, options); + return _this6.filterQuery(resourceConfig, _params, options); }); }); } @@ -465,10 +312,174 @@ module.exports = }, { key: 'destroyAll', value: function destroyAll(resourceConfig, params, options) { - return filterQuery.call(this, resourceConfig, params, options).del().then(function () { + return this.filterQuery(resourceConfig, params, options).del().then(function () { return undefined; }); } + }, { + key: 'filterQuery', + value: function filterQuery(resourceConfig, params, options) { + var table = getTable(resourceConfig); + var query = undefined; + + if (params instanceof Object.getPrototypeOf(this.query.client).QueryBuilder) { + query = params; + params = {}; + } else if (options && options.query) { + query = options.query || this.query; + } else { + query = options && options.transaction || this.query; + query = query.select(table + '.*').from(table); + } + + params = params || {}; + params.where = params.where || {}; + params.orderBy = params.orderBy || params.sort; + params.skip = params.skip || params.offset; + + var joinedTables = []; + + DSUtils.forEach(DSUtils.keys(params), function (k) { + var v = params[k]; + if (!DSUtils.contains(reserved, k)) { + if (DSUtils.isObject(v)) { + params.where[k] = v; + } else { + params.where[k] = { + '==': v + }; + } + delete params[k]; + } + }); + + if (!DSUtils.isEmpty(params.where)) { + DSUtils.forOwn(params.where, function (criteria, field) { + if (!DSUtils.isObject(criteria)) { + params.where[field] = { + '==': criteria + }; + } + + DSUtils.forOwn(criteria, function (v, op) { + if (DSUtils.contains(field, '.')) { + (function () { + var parts = field.split('.'); + var localResourceConfig = resourceConfig; + + var relationPath = []; + + var _loop = function () { + var relationName = parts.shift(); + var relationResourceConfig = resourceConfig.getResource(relationName); + relationPath.push(relationName); + + if (!joinedTables.some(function (t) { + return t === relationPath.join('.'); + })) { + var _localResourceConfig$relationList$filter = localResourceConfig.relationList.filter(function (r) { + return r.relation === relationName; + }); + + var _localResourceConfig$relationList$filter2 = _slicedToArray(_localResourceConfig$relationList$filter, 1); + + var relation = _localResourceConfig$relationList$filter2[0]; + + if (relation) { + var _table = getTable(localResourceConfig); + var localId = _table + '.' + relation.localKey; + + var relationTable = getTable(relationResourceConfig); + var foreignId = relationTable + '.' + relationResourceConfig.idAttribute; + + query = query.join(relationTable, localId, foreignId); + joinedTables.push(relationPath.join('.')); + } else { + // local column + } + } + localResourceConfig = relationResourceConfig; + }; + + while (parts.length >= 2) { + _loop(); + } + + field = getTable(localResourceConfig) + '.' + parts[0]; + })(); + } + + if (op === '==' || op === '===') { + query = query.where(field, v); + } else if (op === '!=' || op === '!==') { + query = query.where(field, '!=', v); + } else if (op === '>') { + query = query.where(field, '>', v); + } else if (op === '>=') { + query = query.where(field, '>=', v); + } else if (op === '<') { + query = query.where(field, '<', v); + } else if (op === '<=') { + query = query.where(field, '<=', v); + // } else if (op === 'isectEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === 'isectNotEmpty') { + // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) + } else if (op === 'in') { + query = query.where(field, 'in', v); + } else if (op === 'notIn') { + query = query.whereNotIn(field, v); + } else if (op === 'like') { + query = query.where(field, 'like', v); + } else if (op === '|==' || op === '|===') { + query = query.orWhere(field, v); + } else if (op === '|!=' || op === '|!==') { + query = query.orWhere(field, '!=', v); + } else if (op === '|>') { + query = query.orWhere(field, '>', v); + } else if (op === '|>=') { + query = query.orWhere(field, '>=', v); + } else if (op === '|<') { + query = query.orWhere(field, '<', v); + } else if (op === '|<=') { + query = query.orWhere(field, '<=', v); + // } else if (op === '|isectEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0) + // } else if (op === '|isectNotEmpty') { + // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0) + } else if (op === '|in') { + query = query.orWhere(field, 'in', v); + } else if (op === '|notIn') { + query = query.orWhereNotIn(field, v); + } else { + throw new Error('Operator not found'); + } + }); + }); + } + + if (params.orderBy) { + if (DSUtils.isString(params.orderBy)) { + params.orderBy = [[params.orderBy, 'asc']]; + } + for (var i = 0; i < params.orderBy.length; i++) { + if (DSUtils.isString(params.orderBy[i])) { + params.orderBy[i] = [params.orderBy[i], 'asc']; + } + query = DSUtils.upperCase(params.orderBy[i][1]) === 'DESC' ? query.orderBy(params.orderBy[i][0], 'desc') : query.orderBy(params.orderBy[i][0], 'asc'); + } + } + + if (params.skip) { + query = query.offset(+params.offset); + } + + if (params.limit) { + query = query.limit(+params.limit); + } + + return query; + } }]); return DSSqlAdapter; @@ -480,25 +491,25 @@ module.exports = /* 1 */ /***/ function(module, exports) { - module.exports = require("mout/string/underscore"); + module.exports = require("knex"); /***/ }, /* 2 */ /***/ function(module, exports) { - module.exports = require("knex"); + module.exports = require("js-data"); /***/ }, /* 3 */ /***/ function(module, exports) { - module.exports = require("js-data"); + module.exports = require("mout/array/map"); /***/ }, /* 4 */ /***/ function(module, exports) { - module.exports = require("mout/array/map"); + module.exports = require("mout/string/underscore"); /***/ }, /* 5 */ diff --git a/package.json b/package.json index 6083fed..a63c603 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "js-data-sql", "description": "Postgres/MySQL/MariaDB/SQLite3 adapter for js-data.", - "version": "0.11.4", + "version": "0.11.5", "homepage": "http://www.js-data.io/docs/dssqladapter", "repository": { "type": "git", From fddee68764a3ca1202785e754f48e44df2c66a96 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Tue, 10 Nov 2015 00:58:21 +0000 Subject: [PATCH 12/16] Update CONTRIBUTING.md --- CONTRIBUTING.md | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75c08b4..545f551 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,18 +1,25 @@ # Contributing Guide -First, support is handled via the [Gitter Channel](https://gitter.im/js-data/js-data) and the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions there. +First, support is handled via the [Slack Channel](http://slack.js-data.io) and +the [Mailing List](https://groups.io/org/groupsio/jsdata). Ask your questions +there. -When submitting issues on GitHub, please include as much detail as possible to make debugging quick and easy. +When submitting issues on GitHub, please include as much detail as possible to +make debugging quick and easy. -- good - Your versions of js-data, js-data-sql, etc., relevant console logs/error, code examples that revealed the issue -- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or [bin](http://jsbin.com/?html,output) that demonstrates the issue -- best - A Pull Request that fixes the issue, including test coverage for the issue and the fix +- good - Your versions of js-data, js-data-sql, etc., relevant console +logs/error, code examples that revealed the issue +- better - A [plnkr](http://plnkr.co/), [fiddle](http://jsfiddle.net/), or +[bin](http://jsbin.com/?html,output) that demonstrates the issue +- best - A Pull Request that fixes the issue, including test coverage for the +issue and the fix [Github Issues](https://github.com/js-data/js-data-sql/issues). #### Submitting Pull Requests -1. Contribute to the issue/discussion that is the reason you'll be developing in the first place +1. Contribute to the issue/discussion that is the reason you'll be developing in +the first place 1. Fork js-data-sql 1. `git clone git@github.com:/js-data-sql.git` 1. `cd js-data-sql; npm install;` @@ -20,7 +27,8 @@ When submitting issues on GitHub, please include as much detail as possible to m 1. Run `npm test` (build and test) - You need io.js or Node 4.x that includes generator support without a flag 1. Your code will be linted and checked for formatting, the tests will be run -1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They will be committed when a release is cut. +1. The `dist/` folder & files will be generated, do NOT commit `dist/*`! They +will be committed when a release is cut. 1. Submit your PR and we'll review! 1. Thanks! @@ -30,10 +38,13 @@ Here's how to make a release on the `master` branch: 1. Bump `package.json` to the appropriate version. 1. `npm test` must succeed. -1. This time, the built `dist/js-data-sql.js` file _will_ be committed, so stage its changes. +1. This time, the built `dist/js-data-sql.js` file _will_ be committed, so stage +its changes. 1. Mention the release version in the commit message, e.g. `Stable Version 1.2.3` 1. Push to master. 1. Create a git tag. Name it the version of the release, e.g. `1.2.3` - - Easiest way is to just create a GitHub Release, which will create the tag for you. Name the Release and the git tag the same thing. -1. `git fetch origin` if you tagged it via GitHub Release, so you can get the tag on your local machine. + - Easiest way is to just create a GitHub Release, which will create the tag +for you. Name the Release and the git tag the same thing. +1. `git fetch origin` if you tagged it via GitHub Release, so you can get the +tag on your local machine. 1. `npm publish .` (Make sure you got the version bumped correctly!) \ No newline at end of file From 2428892fb378510c6dfce0b96593d6c906d445c2 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Tue, 10 Nov 2015 04:50:43 +0000 Subject: [PATCH 13/16] Refactored core adapter tests into a re-usable, standalone repo. --- mocha.start.js | 142 ++++++----------------------- package.json | 5 +- test/create.spec.js | 23 ----- test/destroy.spec.js | 16 ---- test/destroyAll.spec.js | 14 --- test/find.spec.js | 83 ----------------- test/findAll.spec.js | 194 ---------------------------------------- test/update.spec.js | 33 ------- test/updateAll.spec.js | 43 --------- 9 files changed, 30 insertions(+), 523 deletions(-) delete mode 100644 test/create.spec.js delete mode 100644 test/destroy.spec.js delete mode 100644 test/destroyAll.spec.js delete mode 100644 test/find.spec.js delete mode 100644 test/findAll.spec.js delete mode 100644 test/update.spec.js delete mode 100644 test/updateAll.spec.js diff --git a/mocha.start.js b/mocha.start.js index af0bffa..9954e70 100644 --- a/mocha.start.js +++ b/mocha.start.js @@ -1,44 +1,20 @@ /*global assert:true */ 'use strict'; -var assert = require('chai').assert; -assert.equalObjects = function (a, b, m) { - assert.deepEqual(JSON.parse(JSON.stringify(a)), JSON.parse(JSON.stringify(b)), m || 'Objects should be equal!'); -}; +var JSData = require('js-data'); +var TestRunner = require('js-data-adapter-tests'); + var mocha = require('mocha'); var coMocha = require('co-mocha'); + coMocha(mocha); -var JSData = require('js-data'); JSData.DSUtils.Promise = require('bluebird'); -var DSSqlAdapter = require('./'); -var adapter, store, DSUtils, DSErrors, Profile, User, Post, Comment; +var DSSqlAdapter = require('./'); var globals = module.exports = { - fail: function (msg) { - assert.equal('should not reach this!: ' + msg, 'failure'); - }, - TYPES_EXCEPT_STRING: [123, 123.123, null, undefined, {}, [], true, false, function () { - }], - TYPES_EXCEPT_STRING_OR_ARRAY: [123, 123.123, null, undefined, {}, true, false, function () { - }], - TYPES_EXCEPT_STRING_OR_NUMBER: [null, undefined, {}, [], true, false, function () { - }], - TYPES_EXCEPT_STRING_OR_OBJECT: [123, 123.123, null, undefined, [], true, false, function () { - }], - TYPES_EXCEPT_STRING_OR_NUMBER_OBJECT: [null, undefined, [], true, false, function () { - }], - TYPES_EXCEPT_STRING_OR_ARRAY_OR_NUMBER: [null, undefined, {}, true, false, function () { - }], - TYPES_EXCEPT_NUMBER: ['string', null, undefined, {}, [], true, false, function () { - }], - TYPES_EXCEPT_OBJECT: ['string', 123, 123.123, null, undefined, true, false, function () { - }], - TYPES_EXCEPT_BOOLEAN: ['string', 123, 123.123, null, undefined, {}, [], function () { - }], - TYPES_EXCEPT_FUNCTION: ['string', 123, 123.123, null, undefined, {}, [], true, false], - assert: assert, - adapter: undefined, + TestRunner: TestRunner, + assert: TestRunner.assert, co: require('co') }; @@ -52,91 +28,27 @@ for (var key in globals) { } test.globals(testGlobals); -beforeEach(function () { - store = new JSData.DS({ - log: false - }); - adapter = new DSSqlAdapter({ - client: 'mysql', - connection: { - host: process.env.DB_HOST || 'localhost', - user: process.env.DB_USER || process.env.C9_USER || 'ubuntu', - database: process.env.DB_NAME || (process.env.C9_USER ? 'c9' : 'circle_test') - //user: 'root', - //database: 'test' - } - }); - DSUtils = JSData.DSUtils; - DSErrors = JSData.DSErrors; - globals.Profile = global.Profile = Profile = store.defineResource({ - name: 'profile' - }); - globals.User = global.User = User = store.defineResource({ - name: 'user', - relations: { - hasMany: { - post: { - localField: 'posts', - foreignKey: 'post' - } - }, - hasOne: { - profile: { - localField: 'profile', - localKey: 'profileId' - } - } - } - }); - globals.Post = global.Post = Post = store.defineResource({ - name: 'post', - relations: { - belongsTo: { - user: { - localField: 'user', - localKey: 'userId' - } - }, - hasMany: { - comment: { - localField: 'comments', - foreignKey: 'postId' - } - } - } - }); - globals.Comment = global.Comment = Comment = store.defineResource({ - name: 'comment', - relations: { - belongsTo: { - post: { - localField: 'post', - localKey: 'postId' - }, - user: { - localField: 'user', - localKey: 'userId' - } - } - } - }); - - globals.adapter = adapter; - global.adapter = globals.adapter; - - globals.DSUtils = DSUtils; - global.DSUtils = globals.DSUtils; +var config = { + client: 'mysql', + connection: { + host: process.env.DB_HOST || 'localhost', + user: process.env.DB_USER || process.env.C9_USER || 'ubuntu', + database: process.env.DB_NAME || (process.env.C9_USER ? 'c9' : 'circle_test') + } +}; - globals.DSErrors = DSErrors; - global.DSErrors = globals.DSErrors; +TestRunner.init({ + DS: JSData.DS, + Adapter: DSSqlAdapter, + adapterConfig: config }); -afterEach(function* () { - globals.adapter = null; - global.adapter = null; - - yield adapter.destroyAll(Comment); - yield adapter.destroyAll(Post); - yield adapter.destroyAll(User); - yield adapter.destroyAll(Profile); +beforeEach(function () { + globals.DSUtils = global.DSUtils = this.$$DSUtils + globals.DSErrors = global.DSErrors = this.$$DSErrors + globals.adapter = global.adapter = this.$$adapter + globals.User = global.User = this.$$User + globals.Profile = global.Profile = this.$$Profile; + globals.Post = global.Post = this.$$Post; + globals.Comment = global.Comment = this.$$Comment; }); diff --git a/package.json b/package.json index a63c603..eee2978 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,13 @@ "babel-eslint": "4.1.3", "babel-loader": "5.3.2", "bluebird": "2.10.2", - "chai": "3.3.0", + "chai": "3.4.1", "co": "4.6.0", "co-mocha": "1.1.2", "codacy-coverage": "1.1.3", "coveralls": "2.11.4", "istanbul": "0.4.0", + "js-data-adapter-tests": "~1.x", "mocha": "2.3.3", "standard": "5.3.1", "webpack": "1.12.2" @@ -55,7 +56,7 @@ "mout": "0.11.0" }, "peerDependencies": { - "js-data": ">=2.0.0", + "js-data": "~2.x", "knex": ">=0.7.4" } } diff --git a/test/create.spec.js b/test/create.spec.js deleted file mode 100644 index e9fc6b0..0000000 --- a/test/create.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -describe('DSSqlAdapter#create', function () { - it('should create a user in a sql db', function* () { - var createUser = yield adapter.create(User, {name: 'John'}); - var id = createUser.id; - assert.equal(createUser.name, 'John'); - assert.isDefined(createUser.id); - - var findUser = yield adapter.find(User, createUser.id); - assert.equal(findUser.name, 'John'); - assert.isDefined(findUser.id); - assert.equalObjects(findUser, {id: id, name: 'John', age: null, profileId: null}); - - var destoryUser = yield adapter.destroy(User, findUser.id); - assert.isFalse(!!destoryUser); - - try { - var findUser2 = yield adapter.find(User, id); - throw new Error('Should not have reached here!'); - } catch(err) { - assert.equal(err.message, 'Not Found!'); - } - }); -}); diff --git a/test/destroy.spec.js b/test/destroy.spec.js deleted file mode 100644 index 91c75d4..0000000 --- a/test/destroy.spec.js +++ /dev/null @@ -1,16 +0,0 @@ -describe('DSSqlAdapter#destroy', function () { - it('should destroy a user from a Sql db', function* () { - var createUser = yield adapter.create(User, {name: 'John'}) - var id = createUser.id; - - var destroyUser = yield adapter.destroy(User, createUser.id); - assert.isFalse(!!destroyUser); - - try { - var findUser = yield adapter.find(User, id); - throw new Error('Should not have reached here!'); - } catch (err) { - assert.equal(err.message, 'Not Found!'); - } - }); -}); diff --git a/test/destroyAll.spec.js b/test/destroyAll.spec.js deleted file mode 100644 index 171dcf1..0000000 --- a/test/destroyAll.spec.js +++ /dev/null @@ -1,14 +0,0 @@ -describe('DSSqlAdapter#destroyAll', function () { - it('should destroy all items', function* () { - var createUser = yield adapter.create(User, {name: 'John'}); - var id = createUser.id; - - var findUsers = yield adapter.findAll(User, { name: 'John' }); - assert.equal(findUsers.length, 1); - assert.equalObjects(findUsers[0], {id: id, name: 'John', age: null, profileId: null}); - - var destroyUser = yield adapter.destroyAll(User, { name: 'John' }); - var findUsers2 = yield adapter.findAll(User, { name: 'John' }); - assert.equal(findUsers2.length, 0); - }); -}); diff --git a/test/find.spec.js b/test/find.spec.js deleted file mode 100644 index a961a06..0000000 --- a/test/find.spec.js +++ /dev/null @@ -1,83 +0,0 @@ -describe('DSSqlAdapter#find', function () { - it('should find a user in a Sql db', function* () { - var user = yield adapter.create(User, {name: 'John'}); - var userId = user.id; - assert.equal(user.name, 'John'); - assert.isDefined(user.id); - - var user2 = yield adapter.find(User, user.id); - assert.equal(user2.name, 'John'); - assert.isDefined(user2.id); - assert.equalObjects(user2, {id: userId, name: 'John', age: null, profileId: null}); - - var post = yield adapter.create(Post, { content: 'test', userId: userId }); - var postId = post.id; - assert.equal(post.content, 'test'); - assert.isDefined(post.id); - assert.isDefined(post.userId); - - var comments = yield [ - adapter.create(Comment, { - content: 'test2', - postId: post.id, - userId: user.id - }), - adapter.create(Comment, { - content: 'test3', - postId: post.id, - userId: user.id - }) - ]; - - comments.sort(function (a, b) { - return a.content > b.content; - }); - - var findPost = yield adapter.find(Post, postId, {with: ['user', 'comment']}); - findPost.comments.sort(function (a, b) { - return a.content > b.content; - }); - assert.equalObjects(findPost.user, user); - assert.equalObjects(findPost.comments, comments); - - yield adapter.destroyAll(Comment); - yield adapter.destroy(Post, postId); - var destroyUser = yield adapter.destroy(User, userId); - assert.isFalse(!!destroyUser); - - try { - yield adapter.find(User, userId); - throw new Error('Should not have reached here!'); - } catch (err) { - assert.equal(err.message, 'Not Found!'); - } - }); - - it('should load belongsTo relations', function* () { - var profile = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user = yield adapter.create(User, {name: 'John', profileId: profile.id}); - var post = yield adapter.create(Post, {content: 'foo', userId: user.id}); - var comment = yield adapter.create(Comment, { content: 'test2', postId: post.id, userId: post.userId }); - - var comment = yield adapter.find(Comment, comment.id, {'with': ['user', 'user.profile', 'post', 'post.user']}); - assert.isDefined(comment); - assert.isDefined(comment.post); - assert.isDefined(comment.post.user); - assert.isDefined(comment.user); - assert.isDefined(comment.user.profile); - }); - - it('should load hasMany and belongsTo relations', function* () { - var profile = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user = yield adapter.create(User, {name: 'John', profileId: profile.id}); - var post = yield adapter.create(Post, {content: 'foo', userId: user.id}); - var comment = yield adapter.create(Comment, { content: 'test2', postId: post.id, userId: post.userId }); - - var foundPost = yield adapter.find(Post, post.id, {'with': ['user', 'comment', 'comment.user', 'comment.user.profile']}); - assert.isDefined(foundPost.comments); - assert.isDefined(foundPost.comments[0].user); - assert.isDefined(foundPost.comments[0].user.profile); - assert.isDefined(foundPost.user); - }); - -}); diff --git a/test/findAll.spec.js b/test/findAll.spec.js deleted file mode 100644 index 550534f..0000000 --- a/test/findAll.spec.js +++ /dev/null @@ -1,194 +0,0 @@ -describe('DSSqlAdapter#findAll', function () { - it('should filter users', function* () { - var users = yield adapter.findAll(User, { age: 30 }); - assert.equal(users.length, 0); - - var user = yield adapter.create(User, {name: 'John'}); - var id = user.id; - - var users2 = yield adapter.findAll(User, { name: 'John' }); - assert.equal(users2.length, 1); - assert.equalObjects(users2[0], {id: id, name: 'John', age: null, profileId: null}); - - var destroyedUser = yield adapter.destroy(User, id); - assert.isFalse(!!destroyedUser); - }); - - it('should filter users using the "in" operator', function* () { - var users = yield adapter.findAll(User, { - where: { - age: { - 'in': [30] - } - } - }); - assert.equal(users.length, 0); - - var user = yield adapter.create(User, {name: 'John'}); - var id = user.id; - - var users2 = yield adapter.findAll(User, { name: 'John' }); - assert.equal(users2.length, 1); - assert.equalObjects(users2[0], {id: id, name: 'John', age: null, profileId: null}); - - var destroyedUser = yield adapter.destroy(User, id); - assert.isFalse(!!destroyedUser); - }); - - it('should filter users using the "like" operator', function* () { - var users = yield adapter.findAll(User, { - where: { - name: { - 'like': '%J%' - } - } - }); - assert.equal(users.length, 0); - - var user = yield adapter.create(User, {name: 'John'}); - var id = user.id; - - var users2 = yield adapter.findAll(User, { - where: { - name: { - 'like': '%J%' - } - } - }); - assert.equal(users2.length, 1); - assert.deepEqual(users2[0], {id: id, name: 'John', age: null, profileId: null}); - - var destroyedUser = yield adapter.destroy(User, id); - assert.isFalse(!!destroyedUser); - }); - - it('should throw "Operator not found" error', function* () { - var op = '>=<'; - - assert.throw(function () { - return adapter.findAll(User, { - where: { - name: { - op: 'John' - } - } - }); - } - , Error, 'Operator not found'); - }); - - it('should load belongsTo relations', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); - var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id}); - var comment1 = yield adapter.create(Comment, { content: 'test2', postId: post1.id, userId: post1.userId }); - - var user2 = yield adapter.create(User, {name: 'Sally'}); - var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id}); - var comment2 = yield adapter.create(Comment, { content: 'test3', postId: post2.id, userId: post2.userId }); - - var comments = yield adapter.findAll(Comment, {}, {'with': ['user', 'user.profile', 'post', 'post.user']}); - assert.isDefined(comments[0].post); - assert.isDefined(comments[0].post.user); - assert.isDefined(comments[0].user); - assert.isDefined(comments[0].user.profile || comments[1].user.profile); - assert.isDefined(comments[1].post); - assert.isDefined(comments[1].post.user); - assert.isDefined(comments[1].user); - }); - - it('should load hasMany and belongsTo relations', function* () { - var profile = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile.id}); - var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id}); - var comment1 = yield adapter.create(Comment, { content: 'test2', postId: post1.id, userId: post1.userId }); - - var user2 = yield adapter.create(User, {name: 'Sally'}); - var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id}); - var comment2 = yield adapter.create(Comment, { content: 'test3', postId: post2.id, userId: post2.userId }); - - var posts = yield adapter.findAll(Post, {}, {'with': ['user', 'comment', 'comment.user', 'comment.user.profile']}); - assert.isDefined(posts[0].comments); - assert.isDefined(posts[0].comments[0].user); - assert.isDefined(posts[0].comments[0].user.profile || posts[1].comments[0].user.profile); - assert.isDefined(posts[0].user); - assert.isDefined(posts[1].comments); - assert.isDefined(posts[1].comments[0].user); - assert.isDefined(posts[1].user); - }); - - it('should filter using belongsTo relation', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); - var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id}); - var comment1 = yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId}); - - var user2 = yield adapter.create(User, {name: 'Sally'}); - var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id}); - var comment2 = yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId}); - - var users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'}); - assert.equal(users.length, 1); - assert.equal(users[0].profileId, profile1.id); - assert.equal(users[0].name, 'John'); - }); - - it('should filter through multiple hasOne/belongsTo relations', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); - var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id}); - var comment1 = yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId}); - - var profile2 = yield adapter.create(Profile, { email: 'bar@test.com' }); - var user2 = yield adapter.create(User, {name: 'Sally', profileId: profile2.id}); - var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id}); - var comment2 = yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId}); - - var comments = yield adapter.findAll(Comment, { 'user.profile.email': 'foo@test.com' }) - assert.equal(comments.length, 1); - assert.equal(comments[0].userId, user1.id); - assert.equal(comments[0].content, 'test1'); - }); - - it('should filter using multiple hasOne/belongsTo relations', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); - var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id}); - var comment1 = yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId}); - - var profile2 = yield adapter.create(Profile, { email: 'bar@test.com' }); - var user2 = yield adapter.create(User, {name: 'Sally', profileId: profile2.id}); - var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id}); - var comment2 = yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId}); - - var comments = yield adapter.findAll(Comment, { 'user.name': 'John', 'user.profile.email': 'foo@test.com' }) - assert.equal(comments.length, 1); - assert.equal(comments[0].userId, user1.id); - assert.equal(comments[0].content, 'test1'); - }); - - it('should allow passing limit and offset as strings', function* () { - var user = yield adapter.findAll(User, {limit: '10', offset: '20'}); - }); - - it('should not return relation columns on parent', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); - - var users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'}); - assert.equal(users.length, 1); - assert.equal(users[0].profileId, profile1.id); - assert.isUndefined(users[0].email); - }); - - it('should filter when relations have same column if column is qualified', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); - - // `id` column must be qualified with `user.` - var users = yield adapter.findAll(User, {'user.id': user1.id, 'profile.email': 'foo@test.com'}); - assert.equal(users.length, 1); - assert.equal(users[0].profileId, profile1.id); - }); - -}); diff --git a/test/update.spec.js b/test/update.spec.js deleted file mode 100644 index 03e185d..0000000 --- a/test/update.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -describe('DSSqlAdapter#update', function () { - it('should update a user in a Sql db', function* () { - var user = yield adapter.create(User, {name: 'John'}) - var id = user.id; - assert.equal(user.name, 'John'); - assert.isDefined(user.id); - - var foundUser = yield adapter.find(User, user.id); - assert.equal(foundUser.name, 'John'); - assert.isDefined(foundUser.id); - assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null}); - - var updatedUser = yield adapter.update(User, foundUser.id, {name: 'Johnny'}); - assert.equal(updatedUser.name, 'Johnny'); - assert.isDefined(updatedUser.id); - assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); - - var foundUser2 = yield adapter.find(User, updatedUser.id); - assert.equal(foundUser2.name, 'Johnny'); - assert.isDefined(foundUser2.id); - assert.equalObjects(foundUser2, {id: id, name: 'Johnny', age: null, profileId: null}); - - var destroyUser = yield adapter.destroy(User, foundUser2.id); - assert.isFalse(!!destroyUser); - - try { - yield adapter.find(User, id); - throw new Error('Should not have reached here!'); - } catch (err) { - assert.equal(err.message, 'Not Found!'); - } - }); -}); diff --git a/test/updateAll.spec.js b/test/updateAll.spec.js deleted file mode 100644 index cda2ad1..0000000 --- a/test/updateAll.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -describe('DSSqlAdapter#updateAll', function () { - it('should update all items', function* () { - var user1 = yield adapter.create(User, {name: 'John', age: 20}) - var userId1 = user1.id; - - var user2 = yield adapter.create(User, {name: 'John', age: 30}); - var userId2 = user2.id; - - var users = yield adapter.findAll(User, { name: 'John' }); - users.sort(function (a, b) { - return a.age - b.age; - }); - assert.equalObjects(users, [ - {id: userId1, name: 'John', age: 20, profileId: null}, - {id: userId2, name: 'John', age: 30, profileId: null} - ]); - - var users2 = yield adapter.updateAll(User, { name: 'Johnny' }, { name: 'John' }); - users2.sort(function (a, b) { - return a.age - b.age; - }); - assert.equalObjects(users2, [ - {id: userId1, name: 'Johnny', age: 20, profileId: null}, - {id: userId2, name: 'Johnny', age: 30, profileId: null} - ]); - - var users3 = yield adapter.findAll(User, { name: 'John' }); - assert.equalObjects(users3, []); - assert.equal(users3.length, 0); - - var users4 = yield adapter.findAll(User, { name: 'Johnny' }); - users4.sort(function (a, b) { - return a.age - b.age; - }); - assert.equalObjects(users4, [ - {id: userId1, name: 'Johnny', age: 20, profileId: null}, - {id: userId2, name: 'Johnny', age: 30, profileId: null} - ]); - - var destroyedUser = yield adapter.destroyAll(User); - assert.isFalse(!!destroyedUser); - }); -}); From ceac75d2577632fd1ce82afcce42caa902af6f2d Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Thu, 12 Nov 2015 03:25:40 +0000 Subject: [PATCH 14/16] Added missing semicolons. --- mocha.start.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mocha.start.js b/mocha.start.js index 9954e70..362df27 100644 --- a/mocha.start.js +++ b/mocha.start.js @@ -44,10 +44,11 @@ TestRunner.init({ }); beforeEach(function () { - globals.DSUtils = global.DSUtils = this.$$DSUtils - globals.DSErrors = global.DSErrors = this.$$DSErrors - globals.adapter = global.adapter = this.$$adapter - globals.User = global.User = this.$$User + globals.DSUtils = global.DSUtils = this.$$DSUtils; + globals.DSErrors = global.DSErrors = this.$$DSErrors; + globals.adapter = global.adapter = this.$$adapter; + globals.store = global.store = this.$$store; + globals.User = global.User = this.$$User; globals.Profile = global.Profile = this.$$Profile; globals.Post = global.Post = this.$$Post; globals.Comment = global.Comment = this.$$Comment; From 002f2af8beeb19e0af3c21b566b6cc87f69a4242 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Thu, 12 Nov 2015 05:38:48 +0000 Subject: [PATCH 15/16] Added back two tests. --- dist/js-data-sql.js | 49 ++++++++++++++++++++------------------------ package.json | 35 ++++++++++++++++--------------- src/index.js | 7 +++---- test/findAll.spec.js | 21 +++++++++++++++++++ webpack.config.js | 19 +++++++++-------- 5 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 test/findAll.spec.js diff --git a/dist/js-data-sql.js b/dist/js-data-sql.js index 257543f..f2d846e 100644 --- a/dist/js-data-sql.js +++ b/dist/js-data-sql.js @@ -45,20 +45,21 @@ module.exports = /* 0 */ /***/ function(module, exports, __webpack_require__) { - var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); + 'use strict'; - var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })(); - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var knex = __webpack_require__(1); var JSData = __webpack_require__(2); - var map = __webpack_require__(3); - var underscore = __webpack_require__(4); - var unique = __webpack_require__(5); - var toString = __webpack_require__(6); + var underscore = __webpack_require__(3); + var unique = __webpack_require__(4); + var toString = __webpack_require__(5); var DSUtils = JSData.DSUtils; var reserved = ['orderBy', 'sort', 'limit', 'offset', 'skip', 'where']; @@ -78,9 +79,9 @@ module.exports = var relationDef = resourceConfig.getResource(relationName); var containedName = null; - if (DSUtils.contains(options['with'], relationName)) { + if (DSUtils.contains(options.with, relationName)) { containedName = relationName; - } else if (DSUtils.contains(options['with'], def.localField)) { + } else if (DSUtils.contains(options.with, def.localField)) { containedName = def.localField; } else { return; @@ -89,7 +90,7 @@ module.exports = var __options = DSUtils.deepMixIn({}, options.orig ? options.orig() : options); // Filter to only properties under current relation - __options['with'] = options['with'].filter(function (relation) { + __options.with = options.with.filter(function (relation) { return relation !== containedName && relation.indexOf(containedName) === 0 && relation.length >= containedName.length && relation[containedName.length] === '.'; }).map(function (relation) { return relation.substr(containedName.length + 1); @@ -102,7 +103,7 @@ module.exports = if (instance) { foreignKeyFilter = { '==': instance[resourceConfig.idAttribute] }; } else { - foreignKeyFilter = { 'in': map(items, function (item) { + foreignKeyFilter = { 'in': items.map(function (item) { return item[resourceConfig.idAttribute]; }) }; } @@ -178,7 +179,7 @@ module.exports = } else { task = _this.findAll(resourceConfig.getResource(relationName), { where: _defineProperty({}, relationDef.idAttribute, { - 'in': DSUtils.filter(map(items, function (item) { + 'in': DSUtils.filter(items.map(function (item) { return DSUtils.get(item, def.localKey); }), function (x) { return x; @@ -225,7 +226,7 @@ module.exports = var instance = undefined; options = options || {}; - options['with'] = options['with'] || []; + options.with = options.with || []; var query = options && options.transaction || this.query; return query.select('*').from(getTable(resourceConfig)).where(resourceConfig.idAttribute, toString(id)).then(function (rows) { if (!rows.length) { @@ -245,7 +246,7 @@ module.exports = var items = null; options = options || {}; - options['with'] = options['with'] || []; + options.with = options.with || []; return this.filterQuery(resourceConfig, params, options).then(function (_items) { items = _items; return loadWithRelations.call(_this3, _items, resourceConfig, options); @@ -288,7 +289,7 @@ module.exports = attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])); return this.filterQuery(resourceConfig, params, options).then(function (items) { - return map(items, function (item) { + return items.map(function (item) { return item[resourceConfig.idAttribute]; }); }).then(function (ids) { @@ -369,7 +370,7 @@ module.exports = var relationPath = []; - var _loop = function () { + var _loop = function _loop() { var relationName = parts.shift(); var relationResourceConfig = resourceConfig.getResource(relationName); relationPath.push(relationName); @@ -377,13 +378,13 @@ module.exports = if (!joinedTables.some(function (t) { return t === relationPath.join('.'); })) { - var _localResourceConfig$relationList$filter = localResourceConfig.relationList.filter(function (r) { + var _localResourceConfig$ = localResourceConfig.relationList.filter(function (r) { return r.relation === relationName; }); - var _localResourceConfig$relationList$filter2 = _slicedToArray(_localResourceConfig$relationList$filter, 1); + var _localResourceConfig$2 = _slicedToArray(_localResourceConfig$, 1); - var relation = _localResourceConfig$relationList$filter2[0]; + var relation = _localResourceConfig$2[0]; if (relation) { var _table = getTable(localResourceConfig); @@ -501,24 +502,18 @@ module.exports = /***/ }, /* 3 */ -/***/ function(module, exports) { - - module.exports = require("mout/array/map"); - -/***/ }, -/* 4 */ /***/ function(module, exports) { module.exports = require("mout/string/underscore"); /***/ }, -/* 5 */ +/* 4 */ /***/ function(module, exports) { module.exports = require("mout/array/unique"); /***/ }, -/* 6 */ +/* 5 */ /***/ function(module, exports) { module.exports = require("mout/lang/toString"); diff --git a/package.json b/package.json index eee2978..3bdbb22 100644 --- a/package.json +++ b/package.json @@ -25,22 +25,6 @@ "mariadb", "sqlite" ], - "devDependencies": { - "babel-core": "5.8.25", - "babel-eslint": "4.1.3", - "babel-loader": "5.3.2", - "bluebird": "2.10.2", - "chai": "3.4.1", - "co": "4.6.0", - "co-mocha": "1.1.2", - "codacy-coverage": "1.1.3", - "coveralls": "2.11.4", - "istanbul": "0.4.0", - "js-data-adapter-tests": "~1.x", - "mocha": "2.3.3", - "standard": "5.3.1", - "webpack": "1.12.2" - }, "scripts": { "lint": "standard src/index.js", "build": "webpack --config webpack.config.js --progress --colors", @@ -53,10 +37,27 @@ "parser": "babel-eslint" }, "dependencies": { - "mout": "0.11.0" + "mout": "0.11.1" }, "peerDependencies": { "js-data": "~2.x", "knex": ">=0.7.4" + }, + "devDependencies": { + "babel-core": "6.1.4", + "babel-eslint": "4.1.5", + "babel-loader": "6.1.0", + "babel-preset-es2015": "6.1.2", + "bluebird": "2.10.2", + "chai": "3.4.1", + "co": "4.6.0", + "co-mocha": "1.1.2", + "codacy-coverage": "1.1.3", + "coveralls": "2.11.4", + "istanbul": "0.4.0", + "js-data-adapter-tests": "~1.x", + "mocha": "2.3.3", + "standard": "5.3.1", + "webpack": "1.12.2" } } diff --git a/src/index.js b/src/index.js index da03cc9..37df697 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,5 @@ let knex = require('knex') let JSData = require('js-data') -let map = require('mout/array/map') let underscore = require('mout/string/underscore') let unique = require('mout/array/unique') let toString = require('mout/lang/toString') @@ -53,7 +52,7 @@ function loadWithRelations (items, resourceConfig, options) { if (instance) { foreignKeyFilter = { '==': instance[resourceConfig.idAttribute] } } else { - foreignKeyFilter = { 'in': map(items, item => item[resourceConfig.idAttribute]) } + foreignKeyFilter = { 'in': items.map(function (item) { return item[resourceConfig.idAttribute] }) } } task = this.findAll(resourceConfig.getResource(relationName), { where: { @@ -124,7 +123,7 @@ function loadWithRelations (items, resourceConfig, options) { task = this.findAll(resourceConfig.getResource(relationName), { where: { [relationDef.idAttribute]: { - 'in': DSUtils.filter(map(items, item => DSUtils.get(item, def.localKey)), x => x) + 'in': DSUtils.filter(items.map(function (item) { return DSUtils.get(item, def.localKey) }), x => x) } } }, __options).then(relatedItems => { @@ -217,7 +216,7 @@ class DSSqlAdapter { updateAll (resourceConfig, attrs, params, options) { attrs = DSUtils.removeCircular(DSUtils.omit(attrs, resourceConfig.relationFields || [])) return this.filterQuery(resourceConfig, params, options).then(items => { - return map(items, item => item[resourceConfig.idAttribute]) + return items.map(function (item) { return item[resourceConfig.idAttribute] }) }).then(ids => { return this.filterQuery(resourceConfig, params, options).update(attrs).then(() => { let _params = {where: {}} diff --git a/test/findAll.spec.js b/test/findAll.spec.js new file mode 100644 index 0000000..58882a1 --- /dev/null +++ b/test/findAll.spec.js @@ -0,0 +1,21 @@ +describe('DSSqlAdapter#findAll', function () { + it('should not return relation columns on parent', function* () { + var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); + var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); + + var users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'}); + assert.equal(users.length, 1); + assert.equal(users[0].profileId, profile1.id); + assert.isUndefined(users[0].email); + }); + + it('should filter when relations have same column if column is qualified', function* () { + var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); + var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); + + // `id` column must be qualified with `user.` + var users = yield adapter.findAll(User, {'user.id': user1.id, 'profile.email': 'foo@test.com'}); + assert.equal(users.length, 1); + assert.equal(users[0].profileId, profile1.id); + }); +}); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 0391ee0..7c89b6d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,4 @@ module.exports = { - debug: true, entry: './src/index.js', output: { filename: './dist/js-data-sql.js', @@ -7,18 +6,22 @@ module.exports = { library: 'js-data-sql' }, externals: [ - 'mout/array/map', - 'mout/lang/toString', 'mout/string/underscore', + 'mout/lang/toString', 'mout/array/unique', 'js-data', 'knex' ], module: { - loaders: [{ - test: /(src)(.+)\.js$/, - exclude: /node_modules/, - loader: 'babel-loader?blacklist=useStrict' - }] + loaders: [ + { + test: /(src)(.+)\.js$/, + exclude: /node_modules/, + loader: 'babel', + query: { + presets: ['es2015'] + } + } + ] } }; From 94a0061a979e76d0b7feb911f0da864f3e8eeb2e Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Mon, 16 Nov 2015 15:04:52 -0500 Subject: [PATCH 16/16] Add support for "near" filtering, closes #41 --- mocha.start.js | 1 + src/index.js | 118 ++++++++++++++++++++------- test/create_trx.spec.js | 2 +- test/findAll.spec.js | 174 ++++++++++++++++++++++++++++++++++++++-- test/setup.sql | 19 ++++- test/update_trx.spec.js | 8 +- 6 files changed, 280 insertions(+), 42 deletions(-) diff --git a/mocha.start.js b/mocha.start.js index 362df27..9c42f3f 100644 --- a/mocha.start.js +++ b/mocha.start.js @@ -50,6 +50,7 @@ beforeEach(function () { globals.store = global.store = this.$$store; globals.User = global.User = this.$$User; globals.Profile = global.Profile = this.$$Profile; + globals.Address = global.Address = this.$$Address; globals.Post = global.Post = this.$$Post; globals.Comment = global.Comment = this.$$Comment; }); diff --git a/src/index.js b/src/index.js index 37df697..efff30b 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,50 @@ function getTable (resourceConfig) { return resourceConfig.table || underscore(resourceConfig.name) } +/** + * Lookup and apply table joins to query if field contains a `.` + * @param {string} field - Field defined in where filter + * @param {object} query - knex query to modify + * @param {object} resourceConfig - Resource of primary query/table + * @param {string[]} existingJoins - Array of fully qualitifed field names for + * any existing table joins for query + * @returns {string} - field updated to perspective of applied joins + */ +function applyTableJoins (field, query, resourceConfig, existingJoins) { + if (DSUtils.contains(field, '.')) { + let parts = field.split('.') + let localResourceConfig = resourceConfig + + let relationPath = [] + while (parts.length >= 2) { + let relationName = parts.shift() + let relationResourceConfig = resourceConfig.getResource(relationName) + relationPath.push(relationName) + + if (!existingJoins.some(t => t === relationPath.join('.'))) { + let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName) + if (relation) { + let table = getTable(localResourceConfig) + let localId = `${table}.${relation.localKey}` + + let relationTable = getTable(relationResourceConfig) + let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}` + + query.join(relationTable, localId, foreignId) + existingJoins.push(relationPath.join('.')) + } else { + // hopefully a qualified local column + } + } + localResourceConfig = relationResourceConfig + } + + field = `${getTable(localResourceConfig)}.${parts[0]}` + } + + return field; +} + function loadWithRelations (items, resourceConfig, options) { let tasks = [] let instance = Array.isArray(items) ? null : items @@ -283,35 +327,12 @@ class DSSqlAdapter { } DSUtils.forOwn(criteria, (v, op) => { - if (DSUtils.contains(field, '.')) { - let parts = field.split('.') - let localResourceConfig = resourceConfig - - let relationPath = [] - while (parts.length >= 2) { - let relationName = parts.shift() - let relationResourceConfig = resourceConfig.getResource(relationName) - relationPath.push(relationName) - - if (!joinedTables.some(t => t === relationPath.join('.'))) { - let [relation] = localResourceConfig.relationList.filter(r => r.relation === relationName) - if (relation) { - let table = getTable(localResourceConfig) - let localId = `${table}.${relation.localKey}` - - let relationTable = getTable(relationResourceConfig) - let foreignId = `${relationTable}.${relationResourceConfig.idAttribute}` - - query = query.join(relationTable, localId, foreignId) - joinedTables.push(relationPath.join('.')) - } else { - // local column - } - } - localResourceConfig = relationResourceConfig - } - - field = `${getTable(localResourceConfig)}.${parts[0]}` + // Apply table joins (if needed) + if (DSUtils.contains(field, ',')) { + let splitFields = field.split(',').map(c => c.trim()) + field = splitFields.map(splitField => applyTableJoins(splitField, query, resourceConfig, joinedTables)).join(','); + } else { + field = applyTableJoins(field, query, resourceConfig, joinedTables); } if (op === '==' || op === '===') { @@ -334,6 +355,45 @@ class DSSqlAdapter { query = query.where(field, 'in', v) } else if (op === 'notIn') { query = query.whereNotIn(field, v) + } else if (op === 'near') { + const milesRegex = /(\d+(\.\d+)?)\s*(m|M)iles$/; + const kilometersRegex = /(\d+(\.\d+)?)\s*(k|K)$/; + + let radius; + let unitsPerDegree; + if (typeof v.radius === 'number' || milesRegex.test(v.radius)) { + radius = typeof v.radius === 'number' ? v.radius : v.radius.match(milesRegex)[1] + unitsPerDegree = 69.0; // miles per degree + } else if (kilometersRegex.test(v.radius)) { + radius = v.radius.match(kilometersRegex)[1] + unitsPerDegree = 111.045; // kilometers per degree; + } else { + throw new Error('Unknown radius distance units') + } + + let [latitudeColumn, longitudeColumn] = field.split(',').map(c => c.trim()) + let [latitude, longitude] = v.center; + + // Uses indexes on `latitudeColumn` / `longitudeColumn` if available + query = query + .whereBetween(latitudeColumn, [ + latitude - (radius / unitsPerDegree), + latitude + (radius / unitsPerDegree) + ]) + .whereBetween(longitudeColumn, [ + longitude - (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180)))), + longitude + (radius / (unitsPerDegree * Math.cos(latitude * (Math.PI / 180)))) + ]) + + if (v.calculateDistance) { + let distanceColumn = (typeof v.calculateDistance === 'string') ? v.calculateDistance : 'distance' + query = query.select(knex.raw(` + ${unitsPerDegree} * DEGREES(ACOS( + COS(RADIANS(?)) * COS(RADIANS(${latitudeColumn})) * + COS(RADIANS(${longitudeColumn}) - RADIANS(?)) + + SIN(RADIANS(?)) * SIN(RADIANS(${latitudeColumn})) + )) AS ${distanceColumn}`, [latitude, longitude, latitude])) + } } else if (op === 'like') { query = query.where(field, 'like', v) } else if (op === '|==' || op === '|===') { diff --git a/test/create_trx.spec.js b/test/create_trx.spec.js index 7808b79..957dedd 100644 --- a/test/create_trx.spec.js +++ b/test/create_trx.spec.js @@ -13,7 +13,7 @@ describe('DSSqlAdapter#create + transaction', function () { assert.isObject(findUser, 'user committed to database'); assert.equal(findUser.name, 'Jane'); assert.isDefined(findUser.id); - assert.equalObjects(findUser, {id: id, name: 'Jane', age: null, profileId: null}); + assert.equalObjects(findUser, {id: id, name: 'Jane', age: null, profileId: null, addressId: null}); }); it('rollback should not persist created user in a sql db', function* () { diff --git a/test/findAll.spec.js b/test/findAll.spec.js index 58882a1..7e68253 100644 --- a/test/findAll.spec.js +++ b/test/findAll.spec.js @@ -1,21 +1,181 @@ +'use strict'; + describe('DSSqlAdapter#findAll', function () { it('should not return relation columns on parent', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); + let profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); + let user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); - var users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'}); + let users = yield adapter.findAll(User, {'profile.email': 'foo@test.com'}); assert.equal(users.length, 1); assert.equal(users[0].profileId, profile1.id); assert.isUndefined(users[0].email); }); it('should filter when relations have same column if column is qualified', function* () { - var profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); - var user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); + let profile1 = yield adapter.create(Profile, { email: 'foo@test.com' }); + let user1 = yield adapter.create(User, {name: 'John', profileId: profile1.id}); // `id` column must be qualified with `user.` - var users = yield adapter.findAll(User, {'user.id': user1.id, 'profile.email': 'foo@test.com'}); + let users = yield adapter.findAll(User, {'user.id': user1.id, 'profile.email': 'foo@test.com'}); assert.equal(users.length, 1); assert.equal(users[0].profileId, profile1.id); }); -}); \ No newline at end of file + + describe('near', function () { + beforeEach(function * () { + this.googleAddress = yield adapter.create(Address, { name : 'Google', latitude: 37.4219999, longitude: -122.0862515 }); + this.appleAddress = yield adapter.create(Address, { name : 'Apple', latitude: 37.331852, longitude: -122.029599 }); + this.microsoftAddress = yield adapter.create(Address, { name : 'Microsoft', latitude: 47.639649, longitude: -122.128255 }); + this.amazonAddress = yield adapter.create(Address, { name : 'Amazon', latitude: 47.622915, longitude: -122.336384 }); + }) + + it('should filter using "near"', function* () { + let addresses = yield adapter.findAll(Address, { + where: { + 'latitude,longitude': { + 'near': { + center: [37.41, -122.06], + radius: 10 + } + } + } + }); + assert.equal(addresses.length, 2); + assert.equal(addresses[0].name, 'Google'); + assert.equal(addresses[1].name, 'Apple'); + }) + + it('should not contain distance column by default', function* () { + let addresses = yield adapter.findAll(Address, { + where: { + 'latitude,longitude': { + 'near': { + center: [37.41, -122.06], + radius: 5 + } + } + } + }); + assert.equal(addresses.length, 1); + assert.equal(addresses[0].name, 'Google'); + assert.equal(addresses[0].distance, undefined); + }) + + it('should contain distance column if "calculateDistance" is truthy', function* () { + let addresses = yield adapter.findAll(Address, { + where: { + 'latitude,longitude': { + 'near': { + center: [37.41, -122.06], + radius: 10, + calculateDistance: true + } + } + } + }); + assert.equal(addresses.length, 2); + + assert.equal(addresses[0].name, 'Google'); + assert.isNotNull(addresses[0].distance); + assert.equal(Math.round(addresses[0].distance), 2); + + assert.equal(addresses[1].name, 'Apple'); + assert.isNotNull(addresses[1].distance); + assert.equal(Math.round(addresses[1].distance), 6); + }) + + it('should contain custom distance column if "calculateDistance" is string', function* () { + let addresses = yield adapter.findAll(Address, { + where: { + 'latitude,longitude': { + 'near': { + center: [37.41, -122.06], + radius: 10, + calculateDistance: 'howfar' + } + } + } + }); + assert.equal(addresses.length, 2); + + assert.equal(addresses[0].name, 'Google'); + assert.equal(addresses[0].distance, undefined); + assert.isNotNull(addresses[0].howfar); + assert.equal(Math.round(addresses[0].howfar), 2); + + assert.equal(addresses[1].name, 'Apple'); + assert.equal(addresses[1].distance, undefined); + assert.isNotNull(addresses[1].howfar); + assert.equal(Math.round(addresses[1].howfar), 6); + }) + + it('should use kilometers instead of miles if radius ends with "k"', function* () { + let addresses = yield adapter.findAll(Address, { + where: { + 'latitude,longitude': { + 'near': { + center: [37.41, -122.06], + radius: '10k', + calculateDistance: true + } + } + } + }); + assert.equal(addresses.length, 2); + + assert.equal(addresses[0].name, 'Google'); + assert.isNotNull(addresses[0].distance); + assert.equal(Math.round(addresses[0].distance), 3); // in kilometers + + assert.equal(addresses[1].name, 'Apple'); + assert.isNotNull(addresses[1].distance); + assert.equal(Math.round(addresses[1].distance), 9); // in kilometers + }) + + it('should filter through relationships', function* () { + let user1 = yield adapter.create(User, { name : 'Larry Page', addressId: this.googleAddress.id }); + let user2 = yield adapter.create(User, { name : 'Tim Cook', addressId: this.appleAddress.id }); + + let users = yield adapter.findAll(User, { + where: { + 'address.latitude, address.longitude': { + 'near': { + center: [37.41, -122.06], + radius: 10, + calculateDistance: 'howfar' + } + } + } + }); + assert.equal(users.length, 2); + assert.equal(users[0].name, 'Larry Page'); + assert.equal(users[1].name, 'Tim Cook'); + }) + + it('should filter through multiple hasOne/belongsTo relations', function * () { + let user1 = yield adapter.create(User, { name : 'Larry Page', addressId: this.googleAddress.id }); + var post1 = yield adapter.create(Post, {content: 'foo', userId: user1.id}) + yield adapter.create(Comment, {content: 'test1', postId: post1.id, userId: post1.userId}) + + var user2 = yield adapter.create(User, {name: 'Tim Cook', addressId: this.appleAddress.id}) + var post2 = yield adapter.create(Post, {content: 'bar', userId: user2.id}) + yield adapter.create(Comment, {content: 'test2', postId: post2.id, userId: post2.userId}) + + let comments = yield adapter.findAll(Comment, { + where: { + 'user.address.latitude, user.address.longitude': { + 'near': { + center: [37.41, -122.06], + radius: 5 + } + } + } + }); + + assert.equal(comments.length, 1) + assert.equal(comments[0].userId, user1.id) + assert.equal(comments[0].content, 'test1') + }) + + }) +}); diff --git a/test/setup.sql b/test/setup.sql index cbf6ef3..51b6003 100644 --- a/test/setup.sql +++ b/test/setup.sql @@ -1,17 +1,34 @@ +DROP TABLE IF EXISTS `comment`; +DROP TABLE IF EXISTS `post`; +DROP TABLE IF EXISTS `user`; +DROP TABLE IF EXISTS `address`; +DROP TABLE IF EXISTS `profile`; + CREATE TABLE `profile` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `email` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1; +CREATE TABLE `address` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL DEFAULT '', + `latitude` Decimal(10,7) DEFAULT NULL, + `longitude` Decimal(10,7) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1; + CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '', `age` int(11) unsigned DEFAULT NULL, `profileId` int(11) unsigned DEFAULT NULL, + `addressId` int(11) unsigned DEFAULT NULL, PRIMARY KEY (`id`), KEY `fk-user-profile` (`profileId`), - CONSTRAINT `fk-user-profile` FOREIGN KEY (`profileId`) REFERENCES `profile` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION + KEY `fk-user-address` (`addressId`), + CONSTRAINT `fk-user-profile` FOREIGN KEY (`profileId`) REFERENCES `profile` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT `fk-user-address` FOREIGN KEY (`addressId`) REFERENCES `address` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=latin1; CREATE TABLE `post` ( diff --git a/test/update_trx.spec.js b/test/update_trx.spec.js index fe16b0c..db25567 100644 --- a/test/update_trx.spec.js +++ b/test/update_trx.spec.js @@ -9,13 +9,13 @@ describe('DSSqlAdapter#update + transaction', function () { var updatedUser = yield adapter.update(User, id, {name: 'Johnny'}, {transaction: trx}); assert.equal(updatedUser.name, 'Johnny'); assert.isDefined(updatedUser.id); - assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); + assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null, addressId: null}); })); var foundUser = yield adapter.find(User, id); assert.equal(foundUser.name, 'Johnny'); assert.isDefined(foundUser.id); - assert.equalObjects(foundUser, {id: id, name: 'Johnny', age: null, profileId: null}); + assert.equalObjects(foundUser, {id: id, name: 'Johnny', age: null, profileId: null, addressId: null}); }); it('rollback should not update a user in a Sql db', function* () { @@ -29,7 +29,7 @@ describe('DSSqlAdapter#update + transaction', function () { var updatedUser = yield adapter.update(User, id, {name: 'Johnny'}, {transaction: trx}); assert.equal(updatedUser.name, 'Johnny'); assert.isDefined(updatedUser.id); - assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null}); + assert.equalObjects(updatedUser, {id: id, name: 'Johnny', age: null, profileId: null, addressId: null}); throw new Error('rollback'); })); @@ -40,6 +40,6 @@ describe('DSSqlAdapter#update + transaction', function () { var foundUser = yield adapter.find(User, id); assert.equal(foundUser.name, 'John'); assert.isDefined(foundUser.id); - assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null}); + assert.equalObjects(foundUser, {id: id, name: 'John', age: null, profileId: null, addressId: null}); }); });