waterline-postgresql
Version:
PostgreSQL Adapter for Sails and Waterline
224 lines (180 loc) • 7.05 kB
JavaScript
/**
* Module dependencies
*/
var _ = require('lodash');
var utils = require('./lib/utils');
var hop = utils.object.hasOwnProperty;
/**
* Build Select statements.
*
* Given a Waterline Query Object determine which select statements will be needed.
*/
var SelectBuilder = module.exports = function(schema, currentTable, queryObject, options) {
this.schema = schema;
this.currentSchema = schema[currentTable].attributes;
this.currentTable = currentTable;
this.escapeCharacter = '"';
this.cast = false;
this.wlNext = {};
if(options && hop(options, 'escapeCharacter')) {
this.escapeCharacter = options.escapeCharacter;
}
if(options && hop(options, 'cast')) {
this.cast = options.cast;
}
// Add support for WLNext features
if(options && hop(options, 'wlNext')) {
this.wlNext = options.wlNext;
}
if(options && hop(options, 'schemaName')) {
this.schemaName = options.schemaName;
}
var queries = [];
queries.push(this.buildSimpleSelect(queryObject));
return {
select: queries
};
};
/**
* Build a simple Select statement.
*/
SelectBuilder.prototype.buildSimpleSelect = function buildSimpleSelect(queryObject) {
var self = this;
// Check for aggregations
var aggregations = this.processAggregates(queryObject);
if(aggregations) {
return aggregations;
}
// Escape table name
var tableName = utils.escapeName(self.schema[self.currentTable].tableName, self.escapeCharacter, self.schemaName);
var selectKeys = [];
var query = 'SELECT ';
var attributes = queryObject.select || Object.keys(this.schema[this.currentTable].attributes);
delete queryObject.select;
attributes.forEach(function(key) {
// Default schema to {} in case a raw DB column name is sent. This shouldn't happen
// after https://github.com/balderdashy/waterline/commit/687c869ad54f499018ab0b038d3de4435c96d1dd
// but leaving here as a failsafe.
var schema = self.schema[self.currentTable].attributes[key] || {};
if(hop(schema, 'collection')) return;
selectKeys.push({ table: self.currentTable, key: schema.columnName || key });
});
// Add any hasFK strategy joins to the main query
_.keys(queryObject.instructions).forEach(function(attr) {
var strategy = queryObject.instructions[attr].strategy.strategy;
if(strategy !== 1) return;
var population = queryObject.instructions[attr].instructions[0];
// Handle hasFK
var childAlias = _.find(_.values(self.schema), {tableName: population.child}).tableName;
_.keys(self.schema[childAlias].attributes).forEach(function(key) {
var schema = self.schema[childAlias].attributes[key];
if(hop(schema, 'collection')) return;
selectKeys.push({ table: population.alias ? "__"+population.alias : population.child, key: schema.columnName || key, alias: population.parentKey });
});
});
// Add all the columns to be selected
selectKeys.forEach(function(select) {
// If there is an alias, set it in the select (used for hasFK associations)
if(select.alias) {
query += utils.escapeName(select.table, self.escapeCharacter) + '.' + utils.escapeName(select.key, self.escapeCharacter) + ' AS ' + self.escapeCharacter + select.alias + '___' + select.key + self.escapeCharacter + ', ';
}
else {
query += utils.escapeName(select.table, self.escapeCharacter) + '.' + utils.escapeName(select.key, self.escapeCharacter) + ', ';
}
});
// Remove the last comma
query = query.slice(0, -2) + ' FROM ' + tableName + ' AS ' + utils.escapeName(self.currentTable, self.escapeCharacter) + ' ';
return query;
};
/**
* Aggregates
*
*/
SelectBuilder.prototype.processAggregates = function processAggregates(criteria) {
var self = this;
if(!criteria.groupBy && !criteria.sum && !criteria.average && !criteria.min && !criteria.max) {
return false;
}
// Error if groupBy is used and no calculations are given
if(!criteria.sum && !criteria.average && !criteria.min && !criteria.max) {
throw new Error('An aggregation was used but no calculations were given');
}
var query = 'SELECT ';
var tableName = utils.escapeName(this.currentTable, this.escapeCharacter);
// Append groupBy columns to select statement
if(criteria.groupBy) {
if(!Array.isArray(criteria.groupBy)) criteria.groupBy = [criteria.groupBy];
criteria.groupBy.forEach(function(key, index) {
// Check whether we are grouping by a column or an expression.
if (_.includes(_.keys(self.currentSchema), key)) {
query += tableName + '.' + utils.escapeName(key, self.escapeCharacter) + ', ';
} else {
query += key + ' as group' + index + ', ';
}
});
}
// Handle SUM
if (criteria.sum) {
var sum = '';
if(Array.isArray(criteria.sum)) {
criteria.sum.forEach(function(opt) {
sum = 'SUM(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ')';
if(self.cast) {
sum = 'CAST(' + sum + ' AS float)';
}
query += sum + ' AS ' + opt + ', ';
});
} else {
sum = 'SUM(' + tableName + '.' + utils.escapeName(criteria.sum, self.escapeCharacter) + ')';
if(self.cast) {
sum = 'CAST(' + sum + ' AS float)';
}
query += sum + ' AS ' + criteria.sum + ', ';
}
}
// Handle AVG (casting to float to fix percision with trailing zeros)
if (criteria.average) {
var avg = '';
if(Array.isArray(criteria.average)) {
criteria.average.forEach(function(opt){
avg = 'AVG(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ')';
if(self.cast) {
avg = 'CAST( ' + avg + ' AS float)';
}
query += avg + ' AS ' + opt + ', ';
});
} else {
avg = 'AVG(' + tableName + '.' + utils.escapeName(criteria.average, self.escapeCharacter) + ')';
if(self.cast) {
avg = 'CAST( ' + avg + ' AS float)';
}
query += avg + ' AS ' + criteria.average + ', ';
}
}
// Handle MAX
if (criteria.max) {
var max = '';
if(Array.isArray(criteria.max)) {
criteria.max.forEach(function(opt){
query += 'MAX(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ') AS ' + opt + ', ';
});
} else {
query += 'MAX(' + tableName + '.' + utils.escapeName(criteria.max, self.escapeCharacter) + ') AS ' + criteria.max + ', ';
}
}
// Handle MIN
if (criteria.min) {
if(Array.isArray(criteria.min)) {
criteria.min.forEach(function(opt){
query += 'MIN(' + tableName + '.' + utils.escapeName(opt, self.escapeCharacter) + ') AS ' + opt + ', ';
});
} else {
query += 'MIN(' + tableName + '.' + utils.escapeName(criteria.min, self.escapeCharacter) + ') AS ' + criteria.min + ', ';
}
}
// trim trailing comma
query = query.slice(0, -2) + ' ';
// Add FROM clause
query += 'FROM ' + utils.escapeName(self.schema[self.currentTable].tableName, self.escapeCharacter, self.schemaName) + ' AS ' + tableName + ' ';
return query;
};