From da1ec2ae5465555452a8f15a0d17e381db0fc270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mahieu?= Date: Sat, 27 Feb 2016 12:35:47 +0100 Subject: [PATCH 1/6] Refactor for simple inner join --- lib/sql.js | 38 +++++++++--------- lib/utils.js | 18 --------- test/sql.test.js | 101 ++++++++++++++++++++++++++++++----------------- 3 files changed, 82 insertions(+), 75 deletions(-) delete mode 100644 lib/utils.js diff --git a/lib/sql.js b/lib/sql.js index e7c1c063..693b34ed 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -5,7 +5,6 @@ var Connector = require('./connector'); var debug = require('debug')('loopback:connector:sql'); var ParameterizedSQL = require('./parameterized-sql'); var Transaction = require('./transaction'); -var assign = require('./utils').assign; module.exports = SQLConnector; @@ -1073,24 +1072,25 @@ SQLConnector.prototype.buildJoins = function(model, where) { var relations = modelDef.model.relations; var stmt = new ParameterizedSQL('', []); - var buildOneToMany = function buildOneToMany(modelFrom, keyFrom, modelTo, keyTo, filter) { + var buildOneToMany = function buildOneToMany(modelFrom, keyFrom, modelTo, keyTo, joinWhere) { var modelToEscaped = this.tableEscaped(modelTo); - var innerFilter = assign({}, filter); - var innerIdField = {}; - innerIdField[keyTo] = true; - innerFilter.fields = assign({}, innerFilter.fields, innerIdField); - var condition = this.columnEscaped(modelFrom, keyFrom) + '=' + - this.columnEscaped(modelTo, keyTo); + var condition = new ParameterizedSQL( + 'ON ' + + this.columnEscaped(modelFrom, keyFrom) + '=' + + this.columnEscaped(modelTo, keyTo) + ); - var innerSelect = this.buildSelect(modelTo, innerFilter, { - skipParameterize: true - }); + if (joinWhere) { + var innerWhere = this._buildWhere(modelTo, joinWhere); + if (innerWhere.sql.length) { + condition.merge(['AND', innerWhere]); + } + } - return new ParameterizedSQL('INNER JOIN (', []) - .merge(innerSelect) - .merge(') AS ' + modelToEscaped) - .merge('ON ' + condition); + return new ParameterizedSQL('INNER JOIN ' + modelToEscaped, []) + .merge(condition) + .merge(this.buildJoins(modelTo, joinWhere)); }.bind(this); for (var key in where) { @@ -1104,17 +1104,15 @@ SQLConnector.prototype.buildJoins = function(model, where) { var join; if (!rel.modelThrough) { // 1:n relation - join = buildOneToMany(model, keyFrom, modelTo, keyTo, where[key]); + join = buildOneToMany(model, keyFrom, modelTo, keyTo, where[key].where); } else { // n:m relation var modelThrough = rel.modelThrough.definition.name; var keyThrough = rel.keyThrough; var modelToKey = rel.modelTo.definition._ids[0].name; - var innerFilter = {fields: {}}; - innerFilter.fields[keyThrough] = true; - var joinInner = buildOneToMany(model, keyFrom, modelThrough, keyTo, innerFilter); - join = buildOneToMany(modelThrough, keyThrough, modelTo, modelToKey, where[key]); + var joinInner = buildOneToMany(model, keyFrom, modelThrough, keyTo); + join = buildOneToMany(modelThrough, keyThrough, modelTo, modelToKey, where[key].where); join = joinInner.merge(join); } stmt.merge(join); diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 37e0e577..00000000 --- a/lib/utils.js +++ /dev/null @@ -1,18 +0,0 @@ -var _hasOwnProp = Object.prototype.hasOwnProperty; - -/** - * Object.assign polyfill - */ -var assign = Object.assign || function(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (_hasOwnProp.call(source, key)) { - target[key] = source[key]; - } - } - } - return target; -}; - -exports.assign = assign; diff --git a/test/sql.test.js b/test/sql.test.js index a7268b29..645226c6 100644 --- a/test/sql.test.js +++ b/test/sql.test.js @@ -342,9 +342,9 @@ describe('sql connector', function() { it('builds INNER JOIN', function () { var sql = connector.buildJoins('customer', {orders: {where: {id: 10}}}); expect(sql.toJSON()).to.eql({ - sql: 'INNER JOIN ( SELECT `ORDER`.`CUSTOMER_NAME` FROM `ORDER` WHERE ' + - '`ORDER`.`ID`=? ORDER BY `ORDER`.`ID` ) AS `ORDER` ON ' + - '`CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME`', + sql: + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` AND `ORDER`.`ID`=? ', params: [10] }); }); @@ -361,11 +361,16 @@ describe('sql connector', function() { }); expect(sql.toJSON()).to.eql({ - sql: 'SELECT DISTINCT `CUSTOMER`.`NAME`,`CUSTOMER`.`VIP`,' + - '`CUSTOMER`.`ADDRESS`,`CUSTOMER`.`FAVORITE_STORE` FROM `CUSTOMER` ' + - 'INNER JOIN ( SELECT `ORDER`.`CUSTOMER_NAME` FROM `ORDER` WHERE ' + - '`ORDER`.`DATE` BETWEEN $1 AND $2 ORDER BY `ORDER`.`ID` ) AS `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ORDER BY `CUSTOMER`.`NAME`', + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'AND `ORDER`.`DATE` BETWEEN $1 AND $2 ' + + 'ORDER BY `CUSTOMER`.`NAME`', params: ['2015-01-01', '2015-01-31'] }); }); @@ -382,12 +387,16 @@ describe('sql connector', function() { }); expect(sql.toJSON()).to.eql({ - sql: 'SELECT DISTINCT `STORE`.`ID`,`STORE`.`STATE` FROM `STORE` INNER JOIN' + - ' ( SELECT `ORDER`.`CUSTOMER_NAME`,`ORDER`.`STORE_ID` FROM `ORDER` ' + - 'ORDER BY `ORDER`.`ID` ) AS `ORDER` ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + - 'INNER JOIN ( SELECT `CUSTOMER`.`NAME` FROM `CUSTOMER` WHERE ' + - '`CUSTOMER`.`VIP`=$1 ORDER BY `CUSTOMER`.`NAME` ) AS `CUSTOMER` ON ' + - '`ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ORDER BY `STORE`.`ID`', + sql: + 'SELECT DISTINCT `STORE`.`ID`,' + + '`STORE`.`STATE` ' + + 'FROM `STORE` ' + + 'INNER JOIN `ORDER` ' + + 'ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + + 'INNER JOIN `CUSTOMER` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + + 'AND `CUSTOMER`.`VIP`=$1 ' + + 'ORDER BY `STORE`.`ID`', params: [true] }); }); @@ -406,11 +415,16 @@ describe('sql connector', function() { }); expect(sql.toJSON()).to.eql({ - sql: 'SELECT DISTINCT `ORDER`.`ID`,`ORDER`.`DATE`,`ORDER`.`CUSTOMER_NAME`,' + - '`ORDER`.`STORE_ID` FROM `ORDER` INNER JOIN ( SELECT `CUSTOMER`.`NAME`,' + - '`CUSTOMER`.`VIP` FROM `CUSTOMER` ORDER BY `CUSTOMER`.`NAME` ) AS `CUSTOMER`' + - ' ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ORDER BY ' + - '`CUSTOMER`.`VIP` DESC,`CUSTOMER`.`NAME` ASC', + sql: + 'SELECT DISTINCT `ORDER`.`ID`,' + + '`ORDER`.`DATE`,' + + '`ORDER`.`CUSTOMER_NAME`,' + + '`ORDER`.`STORE_ID` ' + + 'FROM `ORDER` ' + + 'INNER JOIN `CUSTOMER` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + + 'ORDER BY `CUSTOMER`.`VIP` DESC,' + + '`CUSTOMER`.`NAME` ASC', params: [] }); }); @@ -433,13 +447,19 @@ describe('sql connector', function() { }); expect(sql.toJSON()).to.eql({ - sql: 'SELECT DISTINCT `CUSTOMER`.`NAME`,`CUSTOMER`.`VIP`,' + - '`CUSTOMER`.`ADDRESS`,`CUSTOMER`.`FAVORITE_STORE` FROM `CUSTOMER` ' + - 'INNER JOIN ( SELECT `ORDER`.`CUSTOMER_NAME` FROM `ORDER` WHERE ' + - '`ORDER`.`DATE` BETWEEN $1 AND $2 ORDER BY `ORDER`.`ID` ) AS `ORDER` ON ' + - '`CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` INNER JOIN ( SELECT `STORE`.`ID` ' + - 'FROM `STORE` WHERE `STORE`.`STATE`=$3 ORDER BY `STORE`.`ID` ) AS `STORE` ' + - 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ORDER BY `CUSTOMER`.`NAME`', + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'AND `ORDER`.`DATE` BETWEEN $1 AND $2 ' + + 'INNER JOIN `STORE` ' + + 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + + 'AND `STORE`.`STATE`=$3 ' + + 'ORDER BY `CUSTOMER`.`NAME`', params: ['2015-01-01', '2015-01-31', 'NY'] }); }); @@ -460,13 +480,17 @@ describe('sql connector', function() { }); expect(sql.toJSON()).to.eql({ - sql: 'SELECT DISTINCT `CUSTOMER`.`NAME`,`CUSTOMER`.`VIP`,' + - '`CUSTOMER`.`ADDRESS`,`CUSTOMER`.`FAVORITE_STORE` FROM `CUSTOMER` ' + - 'INNER JOIN ( SELECT DISTINCT `ORDER`.`CUSTOMER_NAME` FROM `ORDER` ' + - 'INNER JOIN ( SELECT `STORE`.`ID` FROM `STORE` WHERE `STORE`.`STATE`=$1 ' + - 'ORDER BY `STORE`.`ID` ) AS `STORE` ON `ORDER`.`STORE_ID`=`STORE`.`ID` ' + - 'ORDER BY `ORDER`.`ID` ) AS `ORDER` ON `CUSTOMER`.`NAME`=`ORDER`.' + - '`CUSTOMER_NAME` ORDER BY `CUSTOMER`.`NAME`', + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,'+ + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'INNER JOIN `STORE` ' + + 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` AND `STORE`.`STATE`=$1 ' + + 'ORDER BY `CUSTOMER`.`NAME`', params: ['NY'] }); }); @@ -497,10 +521,13 @@ describe('sql connector', function() { } }); expect(sql.toJSON()).to.eql({ - sql: 'SELECT count(DISTINCT `CUSTOMER`.`NAME`) as "cnt" FROM `CUSTOMER` ' + - 'INNER JOIN ( SELECT `ORDER`.`CUSTOMER_NAME` FROM `ORDER` WHERE ' + - '`ORDER`.`DATE` BETWEEN $1 AND $2 ORDER BY `ORDER`.`ID` ) AS `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` WHERE `CUSTOMER`.`NAME`=$3', + sql: + 'SELECT count(DISTINCT `CUSTOMER`.`NAME`) as \"cnt\" ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'AND `ORDER`.`DATE` BETWEEN $1 AND $2 ' + + 'WHERE `CUSTOMER`.`NAME`=$3', params: ['2015-01-01', '2015-01-31', 'John'] }); }); From bcfc014cd3c8f6d04a8bb836218442dc5b90c137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mahieu?= Date: Sat, 27 Feb 2016 13:17:04 +0100 Subject: [PATCH 2/6] Allow sorting on relation properties without where --- lib/sql.js | 41 ++++++++++++-- test/sql.test.js | 145 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 6 deletions(-) diff --git a/lib/sql.js b/lib/sql.js index 693b34ed..1caa5c98 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -868,12 +868,19 @@ SQLConnector.prototype.buildOrderBy = function(model, order) { } else { // Column name is in the format: relationName.columnName var colSplit = t[0].split('.'); - // Find the name of the relation's model ... - var modelDef = this.getModelDefinition(model); - var relation = modelDef.model.relations[colSplit[0]]; - var colModel = relation.modelTo.definition.name; - // ... and escape them - colName = self.columnEscaped(colModel, colSplit[1]); + var prevModel = model; + for (var y = 0; y < colSplit.length; y++) { + if (y >= colSplit.length - 1) { + break; + } + // Find the name of the relation's model ... + var modelDef = this.getModelDefinition(prevModel); + var relation = modelDef.model.relations[colSplit[y]]; + var colModel = relation.modelTo.definition.name; + prevModel = colModel; + // ... and escape them + colName = self.columnEscaped(colModel, colSplit[colSplit.length - 1]); + } } if (t.length === 1) { @@ -1010,6 +1017,28 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) { if (idNames && idNames.length) { filter.order = idNames; } + } else { + var order = filter.order; + if (!Array.isArray(order)) { + order = [ order ]; + } + + order.forEach(function (order) { + var nextWhere = filter.where = filter.where || {}; + var col = order.split(/[\s,]+/)[0].split('.'); + if (col.length <= 1) { + return; + } + + for (var i = 0; i < col.length; i++) { + if (i >= col.length -1) { + break; + } + var curWhere = nextWhere[col[i]] = nextWhere[col[i]] || {}; + curWhere.where = curWhere.where || {}; + nextWhere = curWhere.where; + } + }); } var haveRelationFilters = false; diff --git a/test/sql.test.js b/test/sql.test.js index 645226c6..f76a09cc 100644 --- a/test/sql.test.js +++ b/test/sql.test.js @@ -429,6 +429,151 @@ describe('sql connector', function() { }); }); + it('builds SELECT with INNER JOIN and order by relation columns with where but no conditions', function () { + var sql = connector.buildSelect('order', { + where: { + customer: {} + }, + order: ['customer.vip DESC', 'customer.name ASC'] + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `ORDER`.`ID`,' + + '`ORDER`.`DATE`,' + + '`ORDER`.`CUSTOMER_NAME`,' + + '`ORDER`.`STORE_ID` ' + + 'FROM `ORDER` ' + + 'INNER JOIN `CUSTOMER` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + + 'ORDER BY `CUSTOMER`.`VIP` DESC,' + + '`CUSTOMER`.`NAME` ASC', + params: [] + }); + }); + + it('builds SELECT with INNER JOIN and order by relation columns with any where', function () { + var sql = connector.buildSelect('order', { + order: ['customer.vip DESC', 'customer.name ASC'] + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `ORDER`.`ID`,' + + '`ORDER`.`DATE`,' + + '`ORDER`.`CUSTOMER_NAME`,' + + '`ORDER`.`STORE_ID` ' + + 'FROM `ORDER` ' + + 'INNER JOIN `CUSTOMER` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + + 'ORDER BY `CUSTOMER`.`VIP` DESC,' + + '`CUSTOMER`.`NAME` ASC', + params: [] + }); + }); + + it('builds SELECT with INNER JOIN and order by nested relation columns', function () { + var sql = connector.buildSelect('order', { + order: ['customer.favorite_store.state DESC', 'customer.name ASC'] + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `ORDER`.`ID`,' + + '`ORDER`.`DATE`,' + + '`ORDER`.`CUSTOMER_NAME`,' + + '`ORDER`.`STORE_ID` ' + + 'FROM `ORDER` ' + + 'INNER JOIN `CUSTOMER` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + + 'INNER JOIN `STORE` ' + + 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + + 'ORDER BY `STORE`.`STATE` DESC,' + + '`CUSTOMER`.`NAME` ASC', + params: [] + }); + }); + + it('builds SELECT with INNER JOIN and order by nested relation columns with where', function () { + var sql = connector.buildSelect('order', { + where: { + date: {between: ['2015-01-01', '2015-01-31']}, + customer: { + where: { + name: 'foo', + /*jshint camelcase:false */ + favorite_store: { + where: { + state: 'NY' + } + } + } + } + }, + order: ['customer.favorite_store.state DESC', 'customer.name ASC'] + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `ORDER`.`ID`,' + + '`ORDER`.`DATE`,' + + '`ORDER`.`CUSTOMER_NAME`,' + + '`ORDER`.`STORE_ID` ' + + 'FROM `ORDER` ' + + 'INNER JOIN `CUSTOMER` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + + 'AND `CUSTOMER`.`NAME`=$1 ' + + 'INNER JOIN `STORE` ' + + 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + + 'AND `STORE`.`STATE`=$2 ' + + 'WHERE `ORDER`.`DATE` BETWEEN $3 AND $4 ' + + 'ORDER BY `STORE`.`STATE` DESC,' + + '`CUSTOMER`.`NAME` ASC', + params: [ + 'foo', + 'NY', + '2015-01-01', + '2015-01-31' + ] + }); + }); + + it('builds SELECT with INNER JOIN and order by nested relation columns with partial where', function () { + var sql = connector.buildSelect('order', { + where: { + date: {between: ['2015-01-01', '2015-01-31']}, + customer: { + where: { + name: 'foo' + } + } + }, + order: ['customer.favorite_store.state DESC', 'customer.name ASC'] + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `ORDER`.`ID`,' + + '`ORDER`.`DATE`,' + + '`ORDER`.`CUSTOMER_NAME`,' + + '`ORDER`.`STORE_ID` ' + + 'FROM `ORDER` ' + + 'INNER JOIN `CUSTOMER` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + + 'AND `CUSTOMER`.`NAME`=$1 ' + + 'INNER JOIN `STORE` ' + + 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + + 'WHERE `ORDER`.`DATE` BETWEEN $2 AND $3 ' + + 'ORDER BY `STORE`.`STATE` DESC,' + + '`CUSTOMER`.`NAME` ASC', + params: [ + 'foo', + '2015-01-01', + '2015-01-31' + ] + }); + }); + it('builds SELECT with multiple INNER JOIN', function () { var sql = connector.buildSelect('customer', { where: { From ee1129bfc65b7eb36dac61edfcfaa20528535eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mahieu?= Date: Sat, 27 Feb 2016 17:00:28 +0100 Subject: [PATCH 3/6] Fix support of `and` and `or` operator in where clause with relations --- lib/sql.js | 103 +++++++++++++++++-- test/sql.test.js | 258 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 352 insertions(+), 9 deletions(-) diff --git a/lib/sql.js b/lib/sql.js index 1caa5c98..a00f4a5c 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -755,10 +755,56 @@ SQLConnector.prototype._buildWhere = function(model, where) { var clauses = where[key]; if (Array.isArray(clauses)) { for (var i = 0, n = clauses.length; i < n; i++) { + var relationClauses = []; + for (var ck in clauses[i]) { + if (ck in relations) { + // Build a where clause with relation model + var relClauses = []; + var relModel = relations[ck].modelTo; + var relWhere = clauses[i][ck]; + var lastRelWhere = relWhere; + + do { + lastRelWhere = relWhere; + var loopRelModel = relModel; + + for (var wk in lastRelWhere) { + if (wk in relModel.relations) { + relModel = relModel.relations[wk].modelTo; + relWhere = lastRelWhere[wk]; + } else { + var w = {}; + w[wk] = lastRelWhere[wk]; + relClauses.push(self._buildWhere(loopRelModel.definition.name, w)); + } + } + } while(lastRelWhere !== relWhere); + + if (relClauses.length) { + relationClauses = relationClauses.concat(relClauses); + } + } + } + var stmtForClause = self._buildWhere(model, clauses[i]); - stmtForClause.sql = '(' + stmtForClause.sql + ')'; branchParams = branchParams.concat(stmtForClause.params); - branches.push(stmtForClause.sql); + + if (relationClauses.length) { + var relationSql = []; + + for (var y = 0; y < relationClauses.length; y++) { + branchParams = branchParams.concat(relationClauses[y].params); + relationSql.push(relationClauses[y].sql); + } + + if (stmtForClause.sql.length) { + stmtForClause.sql = stmtForClause.sql + ' AND ' + relationSql.join(' AND '); + } else { + stmtForClause.sql = relationSql.join(' AND '); + } + } + + branches.push('(' + stmtForClause.sql + ')'); } stmt.merge({ sql: branches.join(' ' + key.toUpperCase() + ' '), @@ -1003,14 +1049,14 @@ SQLConnector.prototype.buildColumnNames = function(model, filter) { }; /** - * Build a SQL SELECT statement + * Prepare filter data form buildSelect and buildCount * @param {String} model Model name * @param {Object} filter Filter object * @param {Object} options Options object - * @returns {ParameterizedSQL} Statement object {sql: ..., params: [...]} + * @returns {Object} filter Filter object */ -SQLConnector.prototype.buildSelect = function(model, filter, options) { - options = options || {}; +SQLConnector.prototype._prepareFilter = function (model, filter) { + var relations = this.getModelDefinition(model).model.relations; if (!filter.order) { var idNames = this.idNames(model); @@ -1034,13 +1080,50 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) { if (i >= col.length -1) { break; } - var curWhere = nextWhere[col[i]] = nextWhere[col[i]] || {}; - curWhere.where = curWhere.where || {}; - nextWhere = curWhere.where; + nextWhere = nextWhere[col[i]] = nextWhere[col[i]] || {}; } }); } + if (filter.where) { + fixWhere(filter.where, relations, filter.where); + } + + return filter; + + // Fix and/or relations + // Make sure if a relation is targetted in a `and`/`or` + // we create a empty where clause that will produce a join later + function fixWhere (where, relations, dest) { + if (Array.isArray(where)) { + where.forEach(function (where) { + fixWhere(where, relations, dest); + }); + } else { + for (var key in where) { + if (key === 'and' || key === 'or') { + fixWhere(where[key], relations, dest); + } else if (key in relations) { + dest[key] = dest[key] || {}; + dest[key].where = dest[key].where || {}; + fixWhere(where[key], relations[key].modelTo.relations, dest[key].where); + } + } + } + } +}; + +/** + * Build a SQL SELECT statement + * @param {String} model Model name + * @param {Object} filter Filter object + * @param {Object} options Options object + * @returns {ParameterizedSQL} Statement object {sql: ..., params: [...]} + */ +SQLConnector.prototype.buildSelect = function(model, filter, options) { + options = options || {}; + filter = this._prepareFilter(model, filter); + var haveRelationFilters = false; if (filter.where) { var relations = this.getModelDefinition(model).model.relations; @@ -1259,6 +1342,8 @@ Connector.defineAliases(SQLConnector.prototype, 'find', ['findById']); * @returns {ParameterizedSQL} Statement object {sql: ..., params: [...]} */ SQLConnector.prototype.buildCount = function(model, where, options) { + where = this._prepareFilter(model, { where: where }).where; + var haveRelationFilters = false; if (where) { var relations = this.getModelDefinition(model).model.relations; diff --git a/test/sql.test.js b/test/sql.test.js index f76a09cc..e9123f84 100644 --- a/test/sql.test.js +++ b/test/sql.test.js @@ -401,6 +401,232 @@ describe('sql connector', function() { }); }); + it('builds SELECT with INNER JOIN with or', function () { + var sql = connector.buildSelect('customer', { + where: { + or: [{ + orders: { + date: {between: ['2015-01-01', '2015-01-31']} + } + }, { + orders: { + date: {between: ['2015-02-01', '2015-02-31']} + } + }] + } + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2) ' + + 'OR (`ORDER`.`DATE` BETWEEN $3 AND $4) ' + + 'ORDER BY `CUSTOMER`.`NAME`', + params: [ + '2015-01-01', + '2015-01-31', + '2015-02-01', + '2015-02-31' + ] + }); + }); + + it('builds SELECT with INNER JOIN with and', function () { + var sql = connector.buildSelect('customer', { + where: { + and: [{ + orders: { + date: {between: ['2015-01-01', '2015-01-31']} + } + }, { + orders: { + date: {between: ['2015-02-01', '2015-02-31']} + } + }] + } + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2) ' + + 'AND (`ORDER`.`DATE` BETWEEN $3 AND $4) ' + + 'ORDER BY `CUSTOMER`.`NAME`', + params: [ + '2015-01-01', + '2015-01-31', + '2015-02-01', + '2015-02-31' + ] + }); + }); + + it('builds SELECT with INNER JOIN with or and nested relation', function () { + var sql = connector.buildSelect('customer', { + where: { + or: [{ + orders: { + store: { + state: 'NY' + } + } + }, { + orders: { + date: {between: ['2015-02-01', '2015-02-31']} + } + }] + } + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'INNER JOIN `STORE` ' + + 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` ' + + 'WHERE (`STORE`.`STATE`=$1) ' + + 'OR (`ORDER`.`DATE` BETWEEN $2 AND $3) ' + + 'ORDER BY `CUSTOMER`.`NAME`', + params: [ + 'NY', + '2015-02-01', + '2015-02-31' + ] + }); + }); + + it('builds SELECT with INNER JOIN with or and nested relation with non relation search', function () { + var sql = connector.buildSelect('customer', { + where: { + or: [{ + orders: { + date: {between: ['2015-01-01', '2015-01-31']}, + store: { + state: 'NY' + } + } + }, { + orders: { + store: { + state: 'NY' + }, + date: {between: ['2015-02-01', '2015-02-31']} + } + }] + } + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'INNER JOIN `STORE` ' + + 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` ' + + 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2 AND `STORE`.`STATE`=$3) ' + + 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5 AND `STORE`.`STATE`=$6) ' + + 'ORDER BY `CUSTOMER`.`NAME`', + params: [ + '2015-01-01', + '2015-01-31', + 'NY', + '2015-02-01', + '2015-02-31', + 'NY' + ] + }); + }); + + it('builds SELECT with INNER JOIN with nested or', function () { + var sql = connector.buildSelect('customer', { + where: { + or: [{ + /*jshint camelcase:false */ + favorite_store: { + state: 'NY', + or: [{ + orders: { + date: {between: ['2015-01-01', '2015-01-31']} + } + }, { + orders: { + date: {between: ['2015-02-01', '2015-02-31']} + } + }] + } + }, { + /*jshint camelcase:false */ + favorite_store: { + state: 'baz', + and: [{ + orders: { + date: {between: ['2015-01-01', '2015-01-31']} + } + }, { + orders: { + date: {between: ['2015-02-01', '2015-02-31']} + } + }] + } + }] + } + }); + + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `STORE` ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + + 'INNER JOIN `ORDER` ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + + 'WHERE (' + + '`STORE`.`STATE`=$1 AND (' + + '`ORDER`.`DATE` BETWEEN $2 AND $3' + + ') ' + + 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5)' + + ') OR (' + + '`STORE`.`STATE`=$6 ' + + 'AND (`ORDER`.`DATE` BETWEEN $7 AND $8) ' + + 'AND (`ORDER`.`DATE` BETWEEN $9 AND $10)' + + ') ' + + 'ORDER BY `CUSTOMER`.`NAME`', + params: [ + 'NY', + '2015-01-01', + '2015-01-31', + '2015-02-01', + '2015-02-31', + 'baz', + '2015-01-01', + '2015-01-31', + '2015-02-01', + '2015-02-31' + ] + }); + }); + it('builds SELECT with INNER JOIN and order by relation columns', function () { var sql = connector.buildSelect('order', { where: { @@ -677,6 +903,38 @@ describe('sql connector', function() { }); }); + it('builds count with WHERE and JOIN with or', function() { + var sql = connector.buildCount('customer', { + name: 'John', + or: [{ + orders: { + date: {between: ['2015-01-01', '2015-01-31']} + } + }, { + orders: { + date: {between: ['2015-02-01', '2015-02-31']} + } + }] + }); + expect(sql.toJSON()).to.eql({ + sql: + 'SELECT count(DISTINCT `CUSTOMER`.`NAME`) as \"cnt\" ' + + 'FROM `CUSTOMER` ' + + 'INNER JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'WHERE `CUSTOMER`.`NAME`=$1 ' + + 'AND (`ORDER`.`DATE` BETWEEN $2 AND $3) ' + + 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5)', + params: [ + 'John', + '2015-01-01', + '2015-01-31', + '2015-02-01', + '2015-02-31' + ] + }); + }); + it('normalizes a SQL statement from string', function() { var sql = 'SELECT * FROM `CUSTOMER`'; var stmt = new ParameterizedSQL(sql); From 8beaa32631a37c8b9fd28fba7b6191009eed2504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mahieu?= Date: Mon, 29 Feb 2016 11:55:40 +0100 Subject: [PATCH 4/6] Only use INNER JOIN when there is a condition --- lib/sql.js | 12 +++++++- test/sql.test.js | 76 ++++++++++++++++++++++++++++-------------------- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/lib/sql.js b/lib/sql.js index a00f4a5c..31a809e4 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -1186,6 +1186,9 @@ SQLConnector.prototype.buildJoins = function(model, where) { var buildOneToMany = function buildOneToMany(modelFrom, keyFrom, modelTo, keyTo, joinWhere) { var modelToEscaped = this.tableEscaped(modelTo); + var relModel = this.getModelDefinition(modelTo); + var relRelations = relModel.model.relations; + var joinType = 'LEFT'; var condition = new ParameterizedSQL( 'ON ' + @@ -1194,13 +1197,20 @@ SQLConnector.prototype.buildJoins = function(model, where) { ); if (joinWhere) { + for (var key in joinWhere) { + if (key in relRelations) continue; + + joinType = 'INNER'; + break; + } + var innerWhere = this._buildWhere(modelTo, joinWhere); if (innerWhere.sql.length) { condition.merge(['AND', innerWhere]); } } - return new ParameterizedSQL('INNER JOIN ' + modelToEscaped, []) + return new ParameterizedSQL(joinType + ' JOIN ' + modelToEscaped, []) .merge(condition) .merge(this.buildJoins(modelTo, joinWhere)); }.bind(this); diff --git a/test/sql.test.js b/test/sql.test.js index e9123f84..7458520f 100644 --- a/test/sql.test.js +++ b/test/sql.test.js @@ -349,6 +349,16 @@ describe('sql connector', function() { }); }); + it('builds LEFT JOIN', function () { + var sql = connector.buildJoins('customer', {orders: {}}); + expect(sql.toJSON()).to.eql({ + sql: + 'LEFT JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ', + params: [] + }); + }); + it('builds SELECT with INNER JOIN (1:n relation)', function () { var sql = connector.buildSelect('customer', { where: { @@ -391,7 +401,7 @@ describe('sql connector', function() { 'SELECT DISTINCT `STORE`.`ID`,' + '`STORE`.`STATE` ' + 'FROM `STORE` ' + - 'INNER JOIN `ORDER` ' + + 'LEFT JOIN `ORDER` ' + 'ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + 'INNER JOIN `CUSTOMER` ' + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + @@ -401,7 +411,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN with or', function () { + it('builds SELECT with JOIN with or', function () { var sql = connector.buildSelect('customer', { where: { or: [{ @@ -423,9 +433,13 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2) ' + - 'OR (`ORDER`.`DATE` BETWEEN $3 AND $4) ' + + 'LEFT JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'WHERE (' + + '`ORDER`.`DATE` BETWEEN $1 AND $2' + + ') OR (' + + '`ORDER`.`DATE` BETWEEN $3 AND $4' + + ') ' + 'ORDER BY `CUSTOMER`.`NAME`', params: [ '2015-01-01', @@ -436,7 +450,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN with and', function () { + it('builds SELECT with JOIN with and', function () { var sql = connector.buildSelect('customer', { where: { and: [{ @@ -458,7 +472,7 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + + 'LEFT JOIN `ORDER` ' + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2) ' + 'AND (`ORDER`.`DATE` BETWEEN $3 AND $4) ' + @@ -472,7 +486,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN with or and nested relation', function () { + it('builds SELECT with JOIN with or and nested relation', function () { var sql = connector.buildSelect('customer', { where: { or: [{ @@ -496,9 +510,9 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + + 'LEFT JOIN `ORDER` ' + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'INNER JOIN `STORE` ' + + 'LEFT JOIN `STORE` ' + 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` ' + 'WHERE (`STORE`.`STATE`=$1) ' + 'OR (`ORDER`.`DATE` BETWEEN $2 AND $3) ' + @@ -511,7 +525,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN with or and nested relation with non relation search', function () { + it('builds SELECT with JOIN with or and nested relation with non relation search', function () { var sql = connector.buildSelect('customer', { where: { or: [{ @@ -539,9 +553,9 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + + 'LEFT JOIN `ORDER` ' + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'INNER JOIN `STORE` ' + + 'LEFT JOIN `STORE` ' + 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` ' + 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2 AND `STORE`.`STATE`=$3) ' + 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5 AND `STORE`.`STATE`=$6) ' + @@ -557,7 +571,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN with nested or', function () { + it('builds SELECT with JOIN with nested or', function () { var sql = connector.buildSelect('customer', { where: { or: [{ @@ -599,8 +613,8 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `STORE` ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + - 'INNER JOIN `ORDER` ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + + 'LEFT JOIN `STORE` ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + + 'LEFT JOIN `ORDER` ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + 'WHERE (' + '`STORE`.`STATE`=$1 AND (' + '`ORDER`.`DATE` BETWEEN $2 AND $3' + @@ -627,7 +641,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN and order by relation columns', function () { + it('builds SELECT with JOIN and order by relation columns', function () { var sql = connector.buildSelect('order', { where: { customer: { @@ -647,7 +661,7 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'INNER JOIN `CUSTOMER` ' + + 'LEFT JOIN `CUSTOMER` ' + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + 'ORDER BY `CUSTOMER`.`VIP` DESC,' + '`CUSTOMER`.`NAME` ASC', @@ -655,7 +669,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN and order by relation columns with where but no conditions', function () { + it('builds SELECT with JOIN and order by relation columns with where but no conditions', function () { var sql = connector.buildSelect('order', { where: { customer: {} @@ -670,7 +684,7 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'INNER JOIN `CUSTOMER` ' + + 'LEFT JOIN `CUSTOMER` ' + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + 'ORDER BY `CUSTOMER`.`VIP` DESC,' + '`CUSTOMER`.`NAME` ASC', @@ -678,7 +692,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN and order by relation columns with any where', function () { + it('builds SELECT with JOIN and order by relation columns with any where', function () { var sql = connector.buildSelect('order', { order: ['customer.vip DESC', 'customer.name ASC'] }); @@ -690,7 +704,7 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'INNER JOIN `CUSTOMER` ' + + 'LEFT JOIN `CUSTOMER` ' + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + 'ORDER BY `CUSTOMER`.`VIP` DESC,' + '`CUSTOMER`.`NAME` ASC', @@ -698,7 +712,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN and order by nested relation columns', function () { + it('builds SELECT with JOIN and order by nested relation columns', function () { var sql = connector.buildSelect('order', { order: ['customer.favorite_store.state DESC', 'customer.name ASC'] }); @@ -710,9 +724,9 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'INNER JOIN `CUSTOMER` ' + + 'LEFT JOIN `CUSTOMER` ' + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'INNER JOIN `STORE` ' + + 'LEFT JOIN `STORE` ' + 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + 'ORDER BY `STORE`.`STATE` DESC,' + '`CUSTOMER`.`NAME` ASC', @@ -787,7 +801,7 @@ describe('sql connector', function() { 'INNER JOIN `CUSTOMER` ' + 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + 'AND `CUSTOMER`.`NAME`=$1 ' + - 'INNER JOIN `STORE` ' + + 'LEFT JOIN `STORE` ' + 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + 'WHERE `ORDER`.`DATE` BETWEEN $2 AND $3 ' + 'ORDER BY `STORE`.`STATE` DESC,' + @@ -857,7 +871,7 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,'+ '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + + 'LEFT JOIN `ORDER` ' + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + 'INNER JOIN `STORE` ' + 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` AND `STORE`.`STATE`=$1 ' + @@ -920,11 +934,11 @@ describe('sql connector', function() { sql: 'SELECT count(DISTINCT `CUSTOMER`.`NAME`) as \"cnt\" ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'LEFT JOIN `ORDER` ' + + 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + 'WHERE `CUSTOMER`.`NAME`=$1 ' + - 'AND (`ORDER`.`DATE` BETWEEN $2 AND $3) ' + - 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5)', + 'AND (`ORDER`.`DATE` BETWEEN $2 AND $3) ' + + 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5)', params: [ 'John', '2015-01-01', From 5f303ac809a0ae9a6f0045f5c4082cc94817580c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mahieu?= Date: Mon, 29 Feb 2016 12:21:07 +0100 Subject: [PATCH 5/6] Use scoped joins for allowing join on model with the same name --- lib/sql.js | 66 ++++++++++++----- test/sql.test.js | 185 ++++++++++++++++++++++++----------------------- 2 files changed, 143 insertions(+), 108 deletions(-) diff --git a/lib/sql.js b/lib/sql.js index 31a809e4..1a8be3db 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -228,8 +228,15 @@ SQLConnector.prototype.tableEscaped = function(model) { * @param {String} property The property name * @returns {String} The escaped column name */ -SQLConnector.prototype.columnEscaped = function(model, property) { - return this.tableEscaped(model) + '.' + this.escapeName(this.column(model, property)); +SQLConnector.prototype.columnEscaped = function(model, property, relName) { + var prefix = ''; + if (!relName) { + prefix = this.tableEscaped(model); + } else { + prefix = this.escapeName(relName); + } + + return prefix + '.' + this.escapeName(this.column(model, property)); }; /*! @@ -728,7 +735,7 @@ SQLConnector.prototype.buildExpression = function(columnName, operator, columnVa * @returns {ParameterizedSQL} * @private */ -SQLConnector.prototype._buildWhere = function(model, where) { +SQLConnector.prototype._buildWhere = function(model, where, asName) { if (!where) { return new ParameterizedSQL(''); } @@ -760,22 +767,30 @@ SQLConnector.prototype._buildWhere = function(model, where) { if (ck in relations) { // Build a where clause with relation model var relClauses = []; - var relModel = relations[ck].modelTo; + var rel = relations[ck]; + var relModel = rel.modelTo; var relWhere = clauses[i][ck]; var lastRelWhere = relWhere; + var loopRelName = ''; do { lastRelWhere = relWhere; + if (loopRelName) { + loopRelName = loopRelName + '_' + rel.name; + } else { + loopRelName = rel.name; + } var loopRelModel = relModel; for (var wk in lastRelWhere) { if (wk in relModel.relations) { - relModel = relModel.relations[wk].modelTo; + rel = relModel.relations[wk]; + relModel = rel.modelTo; relWhere = lastRelWhere[wk]; } else { var w = {}; w[wk] = lastRelWhere[wk]; - relClauses.push(self._buildWhere(loopRelModel.definition.name, w)); + relClauses.push(self._buildWhere(loopRelModel.definition.name, w, loopRelName)); } } } while(lastRelWhere !== relWhere); @@ -815,7 +830,7 @@ SQLConnector.prototype._buildWhere = function(model, where) { } // The value is not an array, fall back to regular fields } - var columnName = self.columnEscaped(model, key); + var columnName = self.columnEscaped(model, key, asName); var expression = where[key]; var columnValue; var sqlExp; @@ -915,6 +930,7 @@ SQLConnector.prototype.buildOrderBy = function(model, order) { // Column name is in the format: relationName.columnName var colSplit = t[0].split('.'); var prevModel = model; + var relName = ''; for (var y = 0; y < colSplit.length; y++) { if (y >= colSplit.length - 1) { break; @@ -924,8 +940,13 @@ SQLConnector.prototype.buildOrderBy = function(model, order) { var relation = modelDef.model.relations[colSplit[y]]; var colModel = relation.modelTo.definition.name; prevModel = colModel; + if (relName) { + relName = relName + '_' + relation.name; + } else { + relName = relation.name; + } // ... and escape them - colName = self.columnEscaped(colModel, colSplit[colSplit.length - 1]); + colName = self.columnEscaped(colModel, colSplit[colSplit.length - 1], relName); } } @@ -1179,12 +1200,13 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) { * @param {object} where An object for the where conditions * @returns {ParameterizedSQL} The SQL INNER JOIN clauses */ -SQLConnector.prototype.buildJoins = function(model, where) { +SQLConnector.prototype.buildJoins = function(model, where, prevRelName) { var modelDef = this.getModelDefinition(model); var relations = modelDef.model.relations; var stmt = new ParameterizedSQL('', []); - var buildOneToMany = function buildOneToMany(modelFrom, keyFrom, modelTo, keyTo, joinWhere) { + var buildOneToMany = function buildOneToMany(relName, modelFrom, keyFrom, modelTo, keyTo, joinWhere, + forcePrevRelName) { var modelToEscaped = this.tableEscaped(modelTo); var relModel = this.getModelDefinition(modelTo); var relRelations = relModel.model.relations; @@ -1192,8 +1214,8 @@ SQLConnector.prototype.buildJoins = function(model, where) { var condition = new ParameterizedSQL( 'ON ' + - this.columnEscaped(modelFrom, keyFrom) + '=' + - this.columnEscaped(modelTo, keyTo) + this.columnEscaped(modelFrom, keyFrom, forcePrevRelName || prevRelName) + '=' + + this.columnEscaped(modelTo, keyTo, relName) ); if (joinWhere) { @@ -1204,15 +1226,18 @@ SQLConnector.prototype.buildJoins = function(model, where) { break; } - var innerWhere = this._buildWhere(modelTo, joinWhere); + var innerWhere = this._buildWhere(modelTo, joinWhere, relName); if (innerWhere.sql.length) { condition.merge(['AND', innerWhere]); } } - return new ParameterizedSQL(joinType + ' JOIN ' + modelToEscaped, []) + var join = joinType + ' JOIN ' + modelToEscaped; + join += ' AS ' + this.escapeName(relName); + + return new ParameterizedSQL(join, []) .merge(condition) - .merge(this.buildJoins(modelTo, joinWhere)); + .merge(this.buildJoins(modelTo, joinWhere, relName)); }.bind(this); for (var key in where) { @@ -1222,19 +1247,24 @@ SQLConnector.prototype.buildJoins = function(model, where) { var keyFrom = rel.keyFrom; var modelTo = rel.modelTo.definition.name; var keyTo = rel.keyTo; + var relName = rel.name; + if (prevRelName) { + relName = prevRelName + '_' + relName; + } var join; if (!rel.modelThrough) { // 1:n relation - join = buildOneToMany(model, keyFrom, modelTo, keyTo, where[key].where); + join = buildOneToMany(relName, model, keyFrom, modelTo, keyTo, where[key].where); } else { // n:m relation var modelThrough = rel.modelThrough.definition.name; var keyThrough = rel.keyThrough; var modelToKey = rel.modelTo.definition._ids[0].name; - var joinInner = buildOneToMany(model, keyFrom, modelThrough, keyTo); - join = buildOneToMany(modelThrough, keyThrough, modelTo, modelToKey, where[key].where); + var joinInner = buildOneToMany(relName + '_though', model, keyFrom, modelThrough, keyTo); + join = buildOneToMany(relName, modelThrough, keyThrough, modelTo, modelToKey, where[key].where, + relName + '_though'); join = joinInner.merge(join); } stmt.merge(join); diff --git a/test/sql.test.js b/test/sql.test.js index 7458520f..e75ee5a6 100644 --- a/test/sql.test.js +++ b/test/sql.test.js @@ -343,8 +343,8 @@ describe('sql connector', function() { var sql = connector.buildJoins('customer', {orders: {where: {id: 10}}}); expect(sql.toJSON()).to.eql({ sql: - 'INNER JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` AND `ORDER`.`ID`=? ', + 'INNER JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` AND `orders`.`ID`=? ', params: [10] }); }); @@ -353,8 +353,8 @@ describe('sql connector', function() { var sql = connector.buildJoins('customer', {orders: {}}); expect(sql.toJSON()).to.eql({ sql: - 'LEFT JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ', + 'LEFT JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ', params: [] }); }); @@ -377,9 +377,9 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'AND `ORDER`.`DATE` BETWEEN $1 AND $2 ' + + 'INNER JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + + 'AND `orders`.`DATE` BETWEEN $1 AND $2 ' + 'ORDER BY `CUSTOMER`.`NAME`', params: ['2015-01-01', '2015-01-31'] }); @@ -401,11 +401,11 @@ describe('sql connector', function() { 'SELECT DISTINCT `STORE`.`ID`,' + '`STORE`.`STATE` ' + 'FROM `STORE` ' + - 'LEFT JOIN `ORDER` ' + - 'ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + - 'INNER JOIN `CUSTOMER` ' + - 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'AND `CUSTOMER`.`VIP`=$1 ' + + 'LEFT JOIN `ORDER` AS `customers_though` ' + + 'ON `STORE`.`ID`=`customers_though`.`STORE_ID` ' + + 'INNER JOIN `CUSTOMER` AS `customers` ' + + 'ON `customers_though`.`CUSTOMER_NAME`=`customers`.`NAME` ' + + 'AND `customers`.`VIP`=$1 ' + 'ORDER BY `STORE`.`ID`', params: [true] }); @@ -433,12 +433,12 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'LEFT JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'LEFT JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + 'WHERE (' + - '`ORDER`.`DATE` BETWEEN $1 AND $2' + + '`orders`.`DATE` BETWEEN $1 AND $2' + ') OR (' + - '`ORDER`.`DATE` BETWEEN $3 AND $4' + + '`orders`.`DATE` BETWEEN $3 AND $4' + ') ' + 'ORDER BY `CUSTOMER`.`NAME`', params: [ @@ -472,10 +472,13 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'LEFT JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2) ' + - 'AND (`ORDER`.`DATE` BETWEEN $3 AND $4) ' + + 'LEFT JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + + 'WHERE (' + + '`orders`.`DATE` BETWEEN $1 AND $2' + + ') AND (' + + '`orders`.`DATE` BETWEEN $3 AND $4' + + ') ' + 'ORDER BY `CUSTOMER`.`NAME`', params: [ '2015-01-01', @@ -510,12 +513,12 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'LEFT JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'LEFT JOIN `STORE` ' + - 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` ' + - 'WHERE (`STORE`.`STATE`=$1) ' + - 'OR (`ORDER`.`DATE` BETWEEN $2 AND $3) ' + + 'LEFT JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + + 'LEFT JOIN `STORE` AS `orders_store` ' + + 'ON `orders`.`STORE_ID`=`orders_store`.`ID` ' + + 'WHERE (`orders_store`.`STATE`=$1) ' + + 'OR (`orders`.`DATE` BETWEEN $2 AND $3) ' + 'ORDER BY `CUSTOMER`.`NAME`', params: [ 'NY', @@ -553,12 +556,12 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'LEFT JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'LEFT JOIN `STORE` ' + - 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` ' + - 'WHERE (`ORDER`.`DATE` BETWEEN $1 AND $2 AND `STORE`.`STATE`=$3) ' + - 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5 AND `STORE`.`STATE`=$6) ' + + 'LEFT JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + + 'LEFT JOIN `STORE` AS `orders_store` ' + + 'ON `orders`.`STORE_ID`=`orders_store`.`ID` ' + + 'WHERE (`orders`.`DATE` BETWEEN $1 AND $2 AND `orders_store`.`STATE`=$3) ' + + 'OR (`orders`.`DATE` BETWEEN $4 AND $5 AND `orders_store`.`STATE`=$6) ' + 'ORDER BY `CUSTOMER`.`NAME`', params: [ '2015-01-01', @@ -613,17 +616,19 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'LEFT JOIN `STORE` ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + - 'LEFT JOIN `ORDER` ON `STORE`.`ID`=`ORDER`.`STORE_ID` ' + + 'LEFT JOIN `STORE` AS `favorite_store` ' + + 'ON `CUSTOMER`.`FAVORITE_STORE`=`favorite_store`.`ID` ' + + 'LEFT JOIN `ORDER` AS `favorite_store_orders` ' + + 'ON `favorite_store`.`ID`=`favorite_store_orders`.`STORE_ID` ' + 'WHERE (' + - '`STORE`.`STATE`=$1 AND (' + - '`ORDER`.`DATE` BETWEEN $2 AND $3' + + '`favorite_store`.`STATE`=$1 AND (' + + '`orders`.`DATE` BETWEEN $2 AND $3' + ') ' + - 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5)' + + 'OR (`orders`.`DATE` BETWEEN $4 AND $5)' + ') OR (' + - '`STORE`.`STATE`=$6 ' + - 'AND (`ORDER`.`DATE` BETWEEN $7 AND $8) ' + - 'AND (`ORDER`.`DATE` BETWEEN $9 AND $10)' + + '`favorite_store`.`STATE`=$6 ' + + 'AND (`orders`.`DATE` BETWEEN $7 AND $8) ' + + 'AND (`orders`.`DATE` BETWEEN $9 AND $10)' + ') ' + 'ORDER BY `CUSTOMER`.`NAME`', params: [ @@ -661,10 +666,10 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'LEFT JOIN `CUSTOMER` ' + - 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'ORDER BY `CUSTOMER`.`VIP` DESC,' + - '`CUSTOMER`.`NAME` ASC', + 'LEFT JOIN `CUSTOMER` AS `customer` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`customer`.`NAME` ' + + 'ORDER BY `customer`.`VIP` DESC,' + + '`customer`.`NAME` ASC', params: [] }); }); @@ -684,10 +689,10 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'LEFT JOIN `CUSTOMER` ' + - 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'ORDER BY `CUSTOMER`.`VIP` DESC,' + - '`CUSTOMER`.`NAME` ASC', + 'LEFT JOIN `CUSTOMER` AS `customer` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`customer`.`NAME` ' + + 'ORDER BY `customer`.`VIP` DESC,' + + '`customer`.`NAME` ASC', params: [] }); }); @@ -704,10 +709,10 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'LEFT JOIN `CUSTOMER` ' + - 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'ORDER BY `CUSTOMER`.`VIP` DESC,' + - '`CUSTOMER`.`NAME` ASC', + 'LEFT JOIN `CUSTOMER` AS `customer` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`customer`.`NAME` ' + + 'ORDER BY `customer`.`VIP` DESC,' + + '`customer`.`NAME` ASC', params: [] }); }); @@ -724,12 +729,12 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'LEFT JOIN `CUSTOMER` ' + - 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'LEFT JOIN `STORE` ' + - 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + - 'ORDER BY `STORE`.`STATE` DESC,' + - '`CUSTOMER`.`NAME` ASC', + 'LEFT JOIN `CUSTOMER` AS `customer` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`customer`.`NAME` ' + + 'LEFT JOIN `STORE` AS `customer_favorite_store` ' + + 'ON `customer`.`FAVORITE_STORE`=`customer_favorite_store`.`ID` ' + + 'ORDER BY `customer_favorite_store`.`STATE` DESC,' + + '`customer`.`NAME` ASC', params: [] }); }); @@ -760,15 +765,15 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'INNER JOIN `CUSTOMER` ' + - 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'AND `CUSTOMER`.`NAME`=$1 ' + - 'INNER JOIN `STORE` ' + - 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + - 'AND `STORE`.`STATE`=$2 ' + + 'INNER JOIN `CUSTOMER` AS `customer` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`customer`.`NAME` ' + + 'AND `customer`.`NAME`=$1 ' + + 'INNER JOIN `STORE` AS `customer_favorite_store` ' + + 'ON `customer`.`FAVORITE_STORE`=`customer_favorite_store`.`ID` ' + + 'AND `customer_favorite_store`.`STATE`=$2 ' + 'WHERE `ORDER`.`DATE` BETWEEN $3 AND $4 ' + - 'ORDER BY `STORE`.`STATE` DESC,' + - '`CUSTOMER`.`NAME` ASC', + 'ORDER BY `customer_favorite_store`.`STATE` DESC,' + + '`customer`.`NAME` ASC', params: [ 'foo', 'NY', @@ -798,14 +803,14 @@ describe('sql connector', function() { '`ORDER`.`CUSTOMER_NAME`,' + '`ORDER`.`STORE_ID` ' + 'FROM `ORDER` ' + - 'INNER JOIN `CUSTOMER` ' + - 'ON `ORDER`.`CUSTOMER_NAME`=`CUSTOMER`.`NAME` ' + - 'AND `CUSTOMER`.`NAME`=$1 ' + - 'LEFT JOIN `STORE` ' + - 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + + 'INNER JOIN `CUSTOMER` AS `customer` ' + + 'ON `ORDER`.`CUSTOMER_NAME`=`customer`.`NAME` ' + + 'AND `customer`.`NAME`=$1 ' + + 'LEFT JOIN `STORE` AS `customer_favorite_store` ' + + 'ON `customer`.`FAVORITE_STORE`=`customer_favorite_store`.`ID` ' + 'WHERE `ORDER`.`DATE` BETWEEN $2 AND $3 ' + - 'ORDER BY `STORE`.`STATE` DESC,' + - '`CUSTOMER`.`NAME` ASC', + 'ORDER BY `customer_favorite_store`.`STATE` DESC,' + + '`customer`.`NAME` ASC', params: [ 'foo', '2015-01-01', @@ -838,12 +843,12 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,' + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'AND `ORDER`.`DATE` BETWEEN $1 AND $2 ' + - 'INNER JOIN `STORE` ' + - 'ON `CUSTOMER`.`FAVORITE_STORE`=`STORE`.`ID` ' + - 'AND `STORE`.`STATE`=$3 ' + + 'INNER JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + + 'AND `orders`.`DATE` BETWEEN $1 AND $2 ' + + 'INNER JOIN `STORE` AS `favorite_store` ' + + 'ON `CUSTOMER`.`FAVORITE_STORE`=`favorite_store`.`ID` ' + + 'AND `favorite_store`.`STATE`=$3 ' + 'ORDER BY `CUSTOMER`.`NAME`', params: ['2015-01-01', '2015-01-31', 'NY'] }); @@ -871,10 +876,10 @@ describe('sql connector', function() { '`CUSTOMER`.`ADDRESS`,'+ '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + - 'LEFT JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'INNER JOIN `STORE` ' + - 'ON `ORDER`.`STORE_ID`=`STORE`.`ID` AND `STORE`.`STATE`=$1 ' + + 'LEFT JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + + 'INNER JOIN `STORE` AS `orders_store` ' + + 'ON `orders`.`STORE_ID`=`orders_store`.`ID` AND `orders_store`.`STATE`=$1 ' + 'ORDER BY `CUSTOMER`.`NAME`', params: ['NY'] }); @@ -909,9 +914,9 @@ describe('sql connector', function() { sql: 'SELECT count(DISTINCT `CUSTOMER`.`NAME`) as \"cnt\" ' + 'FROM `CUSTOMER` ' + - 'INNER JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + - 'AND `ORDER`.`DATE` BETWEEN $1 AND $2 ' + + 'INNER JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + + 'AND `orders`.`DATE` BETWEEN $1 AND $2 ' + 'WHERE `CUSTOMER`.`NAME`=$3', params: ['2015-01-01', '2015-01-31', 'John'] }); @@ -934,11 +939,11 @@ describe('sql connector', function() { sql: 'SELECT count(DISTINCT `CUSTOMER`.`NAME`) as \"cnt\" ' + 'FROM `CUSTOMER` ' + - 'LEFT JOIN `ORDER` ' + - 'ON `CUSTOMER`.`NAME`=`ORDER`.`CUSTOMER_NAME` ' + + 'LEFT JOIN `ORDER` AS `orders` ' + + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + 'WHERE `CUSTOMER`.`NAME`=$1 ' + - 'AND (`ORDER`.`DATE` BETWEEN $2 AND $3) ' + - 'OR (`ORDER`.`DATE` BETWEEN $4 AND $5)', + 'AND (`orders`.`DATE` BETWEEN $2 AND $3) ' + + 'OR (`orders`.`DATE` BETWEEN $4 AND $5)', params: [ 'John', '2015-01-01', From 24687b0d86fc3e4c2440f0ab3fd20fbb0004e456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mahieu?= Date: Sat, 27 Feb 2016 13:28:05 +0100 Subject: [PATCH 6/6] Refactor query-relations for avoid nested where --- lib/sql.js | 7 +++-- test/sql.test.js | 66 ++++++++++++++---------------------------------- 2 files changed, 22 insertions(+), 51 deletions(-) diff --git a/lib/sql.js b/lib/sql.js index 1a8be3db..2eb32599 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -1126,8 +1126,7 @@ SQLConnector.prototype._prepareFilter = function (model, filter) { fixWhere(where[key], relations, dest); } else if (key in relations) { dest[key] = dest[key] || {}; - dest[key].where = dest[key].where || {}; - fixWhere(where[key], relations[key].modelTo.relations, dest[key].where); + fixWhere(where[key], relations[key].modelTo.relations, dest[key]); } } } @@ -1255,7 +1254,7 @@ SQLConnector.prototype.buildJoins = function(model, where, prevRelName) { var join; if (!rel.modelThrough) { // 1:n relation - join = buildOneToMany(relName, model, keyFrom, modelTo, keyTo, where[key].where); + join = buildOneToMany(relName, model, keyFrom, modelTo, keyTo, where[key]); } else { // n:m relation var modelThrough = rel.modelThrough.definition.name; @@ -1263,7 +1262,7 @@ SQLConnector.prototype.buildJoins = function(model, where, prevRelName) { var modelToKey = rel.modelTo.definition._ids[0].name; var joinInner = buildOneToMany(relName + '_though', model, keyFrom, modelThrough, keyTo); - join = buildOneToMany(relName, modelThrough, keyThrough, modelTo, modelToKey, where[key].where, + join = buildOneToMany(relName, modelThrough, keyThrough, modelTo, modelToKey, where[key], relName + '_though'); join = joinInner.merge(join); } diff --git a/test/sql.test.js b/test/sql.test.js index e75ee5a6..076ddb79 100644 --- a/test/sql.test.js +++ b/test/sql.test.js @@ -340,7 +340,7 @@ describe('sql connector', function() { }); it('builds INNER JOIN', function () { - var sql = connector.buildJoins('customer', {orders: {where: {id: 10}}}); + var sql = connector.buildJoins('customer', {orders: {id: 10}}); expect(sql.toJSON()).to.eql({ sql: 'INNER JOIN `ORDER` AS `orders` ' + @@ -363,9 +363,7 @@ describe('sql connector', function() { var sql = connector.buildSelect('customer', { where: { orders: { - where: { - date: {between: ['2015-01-01', '2015-01-31']} - } + date: {between: ['2015-01-01', '2015-01-31']} } } }); @@ -389,9 +387,7 @@ describe('sql connector', function() { var sql = connector.buildSelect('store', { where: { customers: { - where: { - vip: true - } + vip: true } } }); @@ -429,9 +425,9 @@ describe('sql connector', function() { expect(sql.toJSON()).to.eql({ sql: 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + - '`CUSTOMER`.`VIP`,' + - '`CUSTOMER`.`ADDRESS`,' + - '`CUSTOMER`.`FAVORITE_STORE` ' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + 'LEFT JOIN `ORDER` AS `orders` ' + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + @@ -468,9 +464,9 @@ describe('sql connector', function() { expect(sql.toJSON()).to.eql({ sql: 'SELECT DISTINCT `CUSTOMER`.`NAME`,' + - '`CUSTOMER`.`VIP`,' + - '`CUSTOMER`.`ADDRESS`,' + - '`CUSTOMER`.`FAVORITE_STORE` ' + + '`CUSTOMER`.`VIP`,' + + '`CUSTOMER`.`ADDRESS`,' + + '`CUSTOMER`.`FAVORITE_STORE` ' + 'FROM `CUSTOMER` ' + 'LEFT JOIN `ORDER` AS `orders` ' + 'ON `CUSTOMER`.`NAME`=`orders`.`CUSTOMER_NAME` ' + @@ -648,14 +644,6 @@ describe('sql connector', function() { it('builds SELECT with JOIN and order by relation columns', function () { var sql = connector.buildSelect('order', { - where: { - customer: { - fields: { - 'name': true, - 'vip': true - } - } - }, order: ['customer.vip DESC', 'customer.name ASC'] }); @@ -744,14 +732,10 @@ describe('sql connector', function() { where: { date: {between: ['2015-01-01', '2015-01-31']}, customer: { - where: { - name: 'foo', - /*jshint camelcase:false */ - favorite_store: { - where: { - state: 'NY' - } - } + name: 'foo', + /*jshint camelcase:false */ + favorite_store: { + state: 'NY' } } }, @@ -788,9 +772,7 @@ describe('sql connector', function() { where: { date: {between: ['2015-01-01', '2015-01-31']}, customer: { - where: { - name: 'foo' - } + name: 'foo' } }, order: ['customer.favorite_store.state DESC', 'customer.name ASC'] @@ -823,15 +805,11 @@ describe('sql connector', function() { var sql = connector.buildSelect('customer', { where: { orders: { - where: { - date: {between: ['2015-01-01', '2015-01-31']} - } + date: {between: ['2015-01-01', '2015-01-31']} }, /*jshint camelcase:false */ favorite_store: { - where: { - state: 'NY' - } + state: 'NY' } } }); @@ -858,12 +836,8 @@ describe('sql connector', function() { var sql = connector.buildSelect('customer', { where: { orders: { - where: { - store: { - where: { - state: 'NY' - } - } + store: { + state: 'NY' } } } @@ -905,9 +879,7 @@ describe('sql connector', function() { var sql = connector.buildCount('customer', { name: 'John', orders: { - where: { - date: {between: ['2015-01-01', '2015-01-31']} - } + date: {between: ['2015-01-01', '2015-01-31']} } }); expect(sql.toJSON()).to.eql({