From 7b305b4920c5f988b8a33518521d0cbe1f826650 Mon Sep 17 00:00:00 2001 From: Diogo Doreto Date: Sun, 22 Nov 2015 21:44:14 -0200 Subject: [PATCH] Add support to n:m relations --- lib/sql.js | 54 ++++++++++++++++++++++++++++++++---------------- test/sql.test.js | 30 ++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/lib/sql.js b/lib/sql.js index d3492f9..e7c1c06 100644 --- a/lib/sql.js +++ b/lib/sql.js @@ -1073,33 +1073,51 @@ 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 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 innerSelect = this.buildSelect(modelTo, innerFilter, { + skipParameterize: true + }); + + return new ParameterizedSQL('INNER JOIN (', []) + .merge(innerSelect) + .merge(') AS ' + modelToEscaped) + .merge('ON ' + condition); + }.bind(this); + for (var key in where) { if (!(key in relations)) continue; var rel = relations[key]; var keyFrom = rel.keyFrom; var modelTo = rel.modelTo.definition.name; - var modelToEscaped = this.tableEscaped(modelTo); var keyTo = rel.keyTo; - var innerWhere = assign({}, where[key]); - var innerIdField = {}; - innerIdField[keyTo] = true; - innerWhere.fields = assign({}, innerWhere.fields, innerIdField); - - var condition = this.columnEscaped(model, keyFrom) + '=' + - this.columnEscaped(modelTo, keyTo); - - var innerSelect = this.buildSelect(modelTo, innerWhere, { - skipParameterize: true - }); - - stmt - .merge('INNER JOIN (') - .merge(innerSelect) - .merge(') AS ' + modelToEscaped) - .merge('ON ' + condition); + var join; + if (!rel.modelThrough) { + // 1:n relation + join = buildOneToMany(model, keyFrom, modelTo, keyTo, where[key]); + } 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]); + join = joinInner.merge(join); + } + stmt.merge(join); } return stmt; diff --git a/test/sql.test.js b/test/sql.test.js index e6859d4..a7268b2 100644 --- a/test/sql.test.js +++ b/test/sql.test.js @@ -57,6 +57,12 @@ describe('sql connector', function() { Order.belongsTo(Customer, {as: 'customer', foreignKey: 'customer_name'}); Order.belongsTo(Store, {as: 'store', foreignKey: 'store_id'}); Store.hasMany(Order, {as: 'orders', foreignKey: 'store_id'}); + Store.hasMany(Customer, { + as: 'customers', + through: Order, + foreignKey: 'store_id', + keyThrough: 'customer_name' + }); Customer.belongsTo(Store, {as: 'favorite_store', foreignKey: 'favorite_store'}); Store.hasMany(Customer, {as: 'customers_fav', foreignKey: 'favorite_store'}); }); @@ -343,7 +349,7 @@ describe('sql connector', function() { }); }); - it('builds SELECT with INNER JOIN', function () { + it('builds SELECT with INNER JOIN (1:n relation)', function () { var sql = connector.buildSelect('customer', { where: { orders: { @@ -364,6 +370,28 @@ describe('sql connector', function() { }); }); + it('builds SELECT with INNER JOIN (n:n relation)', function () { + var sql = connector.buildSelect('store', { + where: { + customers: { + where: { + vip: true + } + } + } + }); + + 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`', + params: [true] + }); + }); + it('builds SELECT with INNER JOIN and order by relation columns', function () { var sql = connector.buildSelect('order', { where: {