Merge branch 'query-relations' of https://github.com/DiogoDoreto/loopback-connector into DiogoDoreto-query-relations

This commit is contained in:
Raymond Feng 2016-03-22 13:31:51 -07:00
commit f9cdbafcdc
3 changed files with 400 additions and 38 deletions

View File

@ -5,6 +5,7 @@ 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;
@ -229,7 +230,7 @@ SQLConnector.prototype.tableEscaped = function(model) {
* @returns {String} The escaped column name
*/
SQLConnector.prototype.columnEscaped = function(model, property) {
return this.escapeName(this.column(model, property));
return this.tableEscaped(model) + '.' + this.escapeName(this.column(model, property));
};
/*!
@ -741,11 +742,17 @@ SQLConnector.prototype._buildWhere = function(model, where) {
return new ParameterizedSQL('');
}
var self = this;
var props = self.getModelDefinition(model).properties;
var modelDef = self.getModelDefinition(model);
var props = modelDef.properties;
var relations = modelDef.model.relations;
var whereStmts = [];
for (var key in where) {
var stmt = new ParameterizedSQL('', []);
if (relations && key in relations) {
// relationships are handled on joins
continue;
}
// Handle and/or operators
if (key === 'and' || key === 'or') {
var branches = [];
@ -860,10 +867,24 @@ SQLConnector.prototype.buildOrderBy = function(model, order) {
var clauses = [];
for (var i = 0, n = order.length; i < n; i++) {
var t = order[i].split(/[\s,]+/);
if (t.length === 1) {
clauses.push(self.columnEscaped(model, order[i]));
var colName;
if (t[0].indexOf('.') < 0) {
colName = self.columnEscaped(model, t[0]);
} else {
clauses.push(self.columnEscaped(model, t[0]) + ' ' + t[1]);
// 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]);
}
if (t.length === 1) {
clauses.push(colName);
} else {
clauses.push(colName + ' ' + t[1]);
}
}
return 'ORDER BY ' + clauses.join(',');
@ -987,6 +1008,8 @@ SQLConnector.prototype.buildColumnNames = function(model, filter) {
* @returns {ParameterizedSQL} Statement object {sql: ..., params: [...]}
*/
SQLConnector.prototype.buildSelect = function(model, filter, options) {
options = options || {};
if (!filter.order) {
var idNames = this.idNames(model);
if (idNames && idNames.length) {
@ -994,11 +1017,30 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) {
}
}
var selectStmt = new ParameterizedSQL('SELECT ' +
var haveRelationFilters = false;
if (filter.where) {
var relations = this.getModelDefinition(model).model.relations;
if (relations) {
for (var key in filter.where) {
if (key in relations) {
haveRelationFilters = true;
break;
}
}
}
}
var distinct = haveRelationFilters ? 'DISTINCT ' : '';
var selectStmt = new ParameterizedSQL('SELECT ' + distinct +
this.buildColumnNames(model, filter) +
' FROM ' + this.tableEscaped(model)
);
if (haveRelationFilters) {
var joinsStmts = this.buildJoins(model, filter.where);
selectStmt.merge(joinsStmts);
}
if (filter) {
if (filter.where) {
@ -1016,9 +1058,75 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) {
}
}
if (options.skipParameterize === true) {
return selectStmt;
}
return this.parameterize(selectStmt);
};
/**
* Build the SQL INNER JOIN clauses
* @param {string} model Model name
* @param {object} where An object for the where conditions
* @returns {ParameterizedSQL} The SQL INNER JOIN clauses
*/
SQLConnector.prototype.buildJoins = function(model, where) {
var modelDef = this.getModelDefinition(model);
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 keyTo = rel.keyTo;
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;
};
/**
* Transform the row data into a model data object
* @param {string} model Model name
@ -1120,6 +1228,45 @@ SQLConnector.prototype.find = function(model, id, options, cb) {
// Alias to `find`. Juggler checks `findById` only.
Connector.defineAliases(SQLConnector.prototype, 'find', ['findById']);
/**
* Build a SQL SELECT statement to count rows
* @param {String} model Model name
* @param {Object} where Where object
* @param {Object} options Options object
* @returns {ParameterizedSQL} Statement object {sql: ..., params: [...]}
*/
SQLConnector.prototype.buildCount = function(model, where, options) {
var haveRelationFilters = false;
if (where) {
var relations = this.getModelDefinition(model).model.relations;
if (relations) {
for (var key in where) {
if (key in relations) {
haveRelationFilters = true;
break;
}
}
}
}
var count = 'count(*)';
if (haveRelationFilters) {
var idColumn = this.columnEscaped(model, this.idColumn(model));
count = 'count(DISTINCT ' + idColumn + ')';
}
var stmt = new ParameterizedSQL('SELECT ' + count +
' as "cnt" FROM ' + this.tableEscaped(model));
if (haveRelationFilters) {
var joinsStmts = this.buildJoins(model, where);
stmt = stmt.merge(joinsStmts);
}
stmt = stmt.merge(this.buildWhere(model, where));
return this.parameterize(stmt);
};
/**
* Count all model instances by the where filter
*
@ -1137,10 +1284,8 @@ SQLConnector.prototype.count = function(model, where, options, cb) {
where = tmp;
}
var stmt = new ParameterizedSQL('SELECT count(*) as "cnt" FROM ' +
this.tableEscaped(model));
stmt = stmt.merge(this.buildWhere(model, where));
stmt = this.parameterize(stmt);
var stmt = this.buildCount(model, where, options);
this.execute(stmt.sql, stmt.params,
function(err, res) {
if (err) {

18
lib/utils.js Normal file
View File

@ -0,0 +1,18 @@
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;

View File

@ -10,6 +10,8 @@ var ds = new juggler.DataSource({
});
var connector;
var Customer;
var Order;
var Store;
describe('sql connector', function() {
before(function() {
@ -35,6 +37,34 @@ describe('sql connector', function() {
address: String
},
{testdb: {table: 'CUSTOMER'}});
Order = ds.createModel('order',
{
id: {
id: true
},
date: Date
});
Store = ds.createModel('store',
{
id: {
id: true,
type: String
},
state: String
});
// Relations
Customer.hasMany(Order, {as: 'orders', foreignKey: 'customer_name'});
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'});
});
it('should map table name', function() {
@ -78,7 +108,7 @@ describe('sql connector', function() {
it('should find escaped column name', function() {
var column = connector.columnEscaped('customer', 'vip');
expect(column).to.eql('`VIP`');
expect(column).to.eql('`CUSTOMER`.`VIP`');
});
it('should convert to escaped id column value', function() {
@ -89,7 +119,7 @@ describe('sql connector', function() {
it('builds where', function() {
var where = connector.buildWhere('customer', {name: 'John'});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME`=?',
sql: 'WHERE `CUSTOMER`.`NAME`=?',
params: ['John']
});
});
@ -97,7 +127,7 @@ describe('sql connector', function() {
it('builds where with null', function() {
var where = connector.buildWhere('customer', {name: null});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` IS NULL',
sql: 'WHERE `CUSTOMER`.`NAME` IS NULL',
params: []
});
});
@ -105,7 +135,7 @@ describe('sql connector', function() {
it('builds where with inq', function() {
var where = connector.buildWhere('customer', {name: {inq: ['John', 'Mary']}});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` IN (?,?)',
sql: 'WHERE `CUSTOMER`.`NAME` IN (?,?)',
params: ['John', 'Mary']
});
});
@ -114,7 +144,7 @@ describe('sql connector', function() {
var where = connector.buildWhere('customer',
{or: [{name: 'John'}, {name: 'Mary'}]});
expect(where.toJSON()).to.eql({
sql: 'WHERE (`NAME`=?) OR (`NAME`=?)',
sql: 'WHERE (`CUSTOMER`.`NAME`=?) OR (`CUSTOMER`.`NAME`=?)',
params: ['John', 'Mary']
});
});
@ -123,7 +153,7 @@ describe('sql connector', function() {
var where = connector.buildWhere('customer',
{and: [{name: 'John'}, {vip: true}]});
expect(where.toJSON()).to.eql({
sql: 'WHERE (`NAME`=?) AND (`VIP`=?)',
sql: 'WHERE (`CUSTOMER`.`NAME`=?) AND (`CUSTOMER`.`VIP`=?)',
params: ['John', true]
});
});
@ -135,7 +165,7 @@ describe('sql connector', function() {
}
});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` REGEXP ?',
sql: 'WHERE `CUSTOMER`.`NAME` REGEXP ?',
params: ['^J']
});
});
@ -147,7 +177,7 @@ describe('sql connector', function() {
}
});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` REGEXP ?',
sql: 'WHERE `CUSTOMER`.`NAME` REGEXP ?',
params: ['^J/i']
});
});
@ -159,7 +189,7 @@ describe('sql connector', function() {
}
});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` REGEXP ?',
sql: 'WHERE `CUSTOMER`.`NAME` REGEXP ?',
params: [/^J/]
});
});
@ -171,7 +201,7 @@ describe('sql connector', function() {
}
});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` REGEXP ?',
sql: 'WHERE `CUSTOMER`.`NAME` REGEXP ?',
params: [/^J/i]
});
});
@ -183,7 +213,7 @@ describe('sql connector', function() {
}
});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` REGEXP ?',
sql: 'WHERE `CUSTOMER`.`NAME` REGEXP ?',
params: [/^J/]
});
});
@ -195,7 +225,7 @@ describe('sql connector', function() {
}
});
expect(where.toJSON()).to.eql({
sql: 'WHERE `NAME` REGEXP ?',
sql: 'WHERE `CUSTOMER`.`NAME` REGEXP ?',
params: [new RegExp(/^J/i)]
});
});
@ -204,30 +234,31 @@ describe('sql connector', function() {
var where = connector.buildWhere('customer',
{and: [{name: 'John'}, {or: [{vip: true}, {address: null}]}]});
expect(where.toJSON()).to.eql({
sql: 'WHERE (`NAME`=?) AND ((`VIP`=?) OR (`ADDRESS` IS NULL))',
sql: 'WHERE (`CUSTOMER`.`NAME`=?) AND ((`CUSTOMER`.`VIP`=?) OR ' +
'(`CUSTOMER`.`ADDRESS` IS NULL))',
params: ['John', true]
});
});
it('builds order by with one field', function() {
var orderBy = connector.buildOrderBy('customer', 'name');
expect(orderBy).to.eql('ORDER BY `NAME`');
expect(orderBy).to.eql('ORDER BY `CUSTOMER`.`NAME`');
});
it('builds order by with two fields', function() {
var orderBy = connector.buildOrderBy('customer', ['name', 'vip']);
expect(orderBy).to.eql('ORDER BY `NAME`,`VIP`');
expect(orderBy).to.eql('ORDER BY `CUSTOMER`.`NAME`,`CUSTOMER`.`VIP`');
});
it('builds order by with two fields and dirs', function() {
var orderBy = connector.buildOrderBy('customer', ['name ASC', 'vip DESC']);
expect(orderBy).to.eql('ORDER BY `NAME` ASC,`VIP` DESC');
expect(orderBy).to.eql('ORDER BY `CUSTOMER`.`NAME` ASC,`CUSTOMER`.`VIP` DESC');
});
it('builds fields for columns', function() {
var fields = connector.buildFields('customer',
{name: 'John', vip: true, unknown: 'Random'});
expect(fields.names).to.eql(['`NAME`', '`VIP`']);
expect(fields.names).to.eql(['`CUSTOMER`.`NAME`', '`CUSTOMER`.`VIP`']);
expect(fields.columnValues[0].toJSON()).to.eql(
{sql: '?', params: ['John']});
expect(fields.columnValues[1].toJSON()).to.eql(
@ -238,7 +269,7 @@ describe('sql connector', function() {
var fields = connector.buildFieldsForUpdate('customer',
{name: 'John', vip: true});
expect(fields.toJSON()).to.eql({
sql: 'SET `VIP`=?',
sql: 'SET `CUSTOMER`.`VIP`=?',
params: [true]
});
});
@ -247,35 +278,36 @@ describe('sql connector', function() {
var fields = connector.buildFieldsForUpdate('customer',
{name: 'John', vip: true}, false);
expect(fields.toJSON()).to.eql({
sql: 'SET `NAME`=?,`VIP`=?',
sql: 'SET `CUSTOMER`.`NAME`=?,`CUSTOMER`.`VIP`=?',
params: ['John', true]
});
});
it('builds column names for SELECT', function() {
var cols = connector.buildColumnNames('customer');
expect(cols).to.eql('`NAME`,`VIP`,`ADDRESS`');
expect(cols).to.eql('`CUSTOMER`.`NAME`,`CUSTOMER`.`VIP`,' +
'`CUSTOMER`.`ADDRESS`,`CUSTOMER`.`FAVORITE_STORE`');
});
it('builds column names with true fields filter for SELECT', function() {
var cols = connector.buildColumnNames('customer', {fields: {name: true}});
expect(cols).to.eql('`NAME`');
expect(cols).to.eql('`CUSTOMER`.`NAME`');
});
it('builds column names with false fields filter for SELECT', function() {
var cols = connector.buildColumnNames('customer', {fields: {name: false}});
expect(cols).to.eql('`VIP`,`ADDRESS`');
expect(cols).to.eql('`CUSTOMER`.`VIP`,`CUSTOMER`.`ADDRESS`,`CUSTOMER`.`FAVORITE_STORE`');
});
it('builds column names with array fields filter for SELECT', function() {
var cols = connector.buildColumnNames('customer', {fields: ['name']});
expect(cols).to.eql('`NAME`');
expect(cols).to.eql('`CUSTOMER`.`NAME`');
});
it('builds DELETE', function() {
var sql = connector.buildDelete('customer', {name: 'John'});
expect(sql.toJSON()).to.eql({
sql: 'DELETE FROM `CUSTOMER` WHERE `NAME`=$1',
sql: 'DELETE FROM `CUSTOMER` WHERE `CUSTOMER`.`NAME`=$1',
params: ['John']
});
});
@ -283,7 +315,7 @@ describe('sql connector', function() {
it('builds UPDATE', function() {
var sql = connector.buildUpdate('customer', {name: 'John'}, {vip: false});
expect(sql.toJSON()).to.eql({
sql: 'UPDATE `CUSTOMER` SET `VIP`=$1 WHERE `NAME`=$2',
sql: 'UPDATE `CUSTOMER` SET `CUSTOMER`.`VIP`=$1 WHERE `CUSTOMER`.`NAME`=$2',
params: [false, 'John']
});
});
@ -292,8 +324,9 @@ describe('sql connector', function() {
var sql = connector.buildSelect('customer',
{order: 'name', limit: 5, where: {name: 'John'}});
expect(sql.toJSON()).to.eql({
sql: 'SELECT `NAME`,`VIP`,`ADDRESS` FROM `CUSTOMER`' +
' WHERE `NAME`=$1 ORDER BY `NAME` LIMIT 5',
sql: 'SELECT `CUSTOMER`.`NAME`,`CUSTOMER`.`VIP`,`CUSTOMER`.`ADDRESS`,' +
'`CUSTOMER`.`FAVORITE_STORE` FROM `CUSTOMER` WHERE `CUSTOMER`.`NAME`=$1 ' +
'ORDER BY `CUSTOMER`.`NAME` LIMIT 5',
params: ['John']
});
});
@ -301,11 +334,177 @@ describe('sql connector', function() {
it('builds INSERT', function() {
var sql = connector.buildInsert('customer', {name: 'John', vip: true});
expect(sql.toJSON()).to.eql({
sql: 'INSERT INTO `CUSTOMER`(`NAME`,`VIP`) VALUES($1,$2)',
sql: 'INSERT INTO `CUSTOMER`(`CUSTOMER`.`NAME`,`CUSTOMER`.`VIP`) VALUES($1,$2)',
params: ['John', true]
});
});
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`',
params: [10]
});
});
it('builds SELECT with INNER JOIN (1:n relation)', function () {
var sql = connector.buildSelect('customer', {
where: {
orders: {
where: {
date: {between: ['2015-01-01', '2015-01-31']}
}
}
}
});
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`',
params: ['2015-01-01', '2015-01-31']
});
});
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: {
customer: {
fields: {
'name': true,
'vip': true
}
}
},
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 ( 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',
params: []
});
});
it('builds SELECT with multiple INNER JOIN', function () {
var sql = connector.buildSelect('customer', {
where: {
orders: {
where: {
date: {between: ['2015-01-01', '2015-01-31']}
}
},
/*jshint camelcase:false */
favorite_store: {
where: {
state: 'NY'
}
}
}
});
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`',
params: ['2015-01-01', '2015-01-31', 'NY']
});
});
it('builds nested SELECTs', function () {
var sql = connector.buildSelect('customer', {
where: {
orders: {
where: {
store: {
where: {
state: 'NY'
}
}
}
}
}
});
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`',
params: ['NY']
});
});
it('builds count', function() {
var sql = connector.buildCount('customer');
expect(sql.toJSON()).to.eql({
sql: 'SELECT count(*) as "cnt" FROM `CUSTOMER` ',
params: []
});
});
it('builds count with WHERE', function() {
var sql = connector.buildCount('customer', {name: 'John'});
expect(sql.toJSON()).to.eql({
sql: 'SELECT count(*) as "cnt" FROM `CUSTOMER` WHERE `CUSTOMER`.`NAME`=$1',
params: ['John']
});
});
it('builds count with WHERE and JOIN', function() {
var sql = connector.buildCount('customer', {
name: 'John',
orders: {
where: {
date: {between: ['2015-01-01', '2015-01-31']}
}
}
});
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',
params: ['2015-01-01', '2015-01-31', 'John']
});
});
it('normalizes a SQL statement from string', function() {
var sql = 'SELECT * FROM `CUSTOMER`';
var stmt = new ParameterizedSQL(sql);