water-orm
Version:
A monolith version of Standalone waterline ORM
314 lines (229 loc) • 9.63 kB
JavaScript
/**
* Module dependencies
*/
var _ = require('lodash');
var SelectBuilder = require('./select');
var WhereBuilder = require('./where');
var utils = require('./lib/utils');
/**
* Sequel generator
*
* Given a Waterline Query Object build a SQL query.
*/
var Sequel = module.exports = function(schema, options) {
// Store the schema values for the database structure
this.schema = schema || {};
// To solve a query, multiple query strings may be needed.
this.queries = [];
// Flag whether queries should be built using parameterized queries or not.
// Default is true.
this.parameterized = options && utils.object.hasOwnProperty(options, 'parameterized') ? options.parameterized : true;
// Flag if things should be cast, useful for averages
this.cast = options && utils.object.hasOwnProperty(options, 'casting') ? options.casting : false;
// Flag whether the database is case-sensitive or not.
// Default is true.
// NOTE: This does not mean that your queries will be case sensitive. It just flags if the queries
// should use lower or regex logic for querying.
this.caseSensitive = options && utils.object.hasOwnProperty(options, 'caseSensitive') ? options.caseSensitive : true;
// Set the escape character, default is "
this.escapeCharacter = options && utils.object.hasOwnProperty(options, 'escapeCharacter') ? options.escapeCharacter : '"';
// Set if the database can return values from things such as an insert
this.canReturnValues = options && utils.object.hasOwnProperty(options, 'canReturnValues') ? options.canReturnValues : false;
// Determine if insert values should be escaped or not
this.escapeInserts = options && utils.object.hasOwnProperty(options, 'escapeInserts') ? options.escapeInserts : false;
// Determine if aliased tablenames in DELETE queries need to be referenced before the FROM, e.g.
// DELETE `tableName` FROM `tableName` as `otherTableName` WHERE `otherTableName`.`foo` = "bar"
// MySQL and Oracle require this, but it doesn't work in Postgresql.
this.declareDeleteAlias = options && utils.object.hasOwnProperty(options, 'declareDeleteAlias') ? options.declareDeleteAlias : true;
// Waterline NEXT
// These are flags that can be toggled today and expose future features. If any of the following are turned
// on the adapter tests will probably not pass. If you toggle these know what you are getting into.
var wlNext = options && utils.object.hasOwnProperty(options, 'wlNext') ? options.wlNext : {};
this.wlNext = {
// Case sensitive - false
// In the next version of WL queries will be case sensitive by default.
// Set this to true to experiement with that feature today.
caseSensitive: utils.object.hasOwnProperty(wlNext, 'caseSensitive') ? wlNext.caseSensitive : false
};
this.schemaName = options && utils.object.hasOwnProperty(options, 'schemaName') ? options.schemaName : false;
this.values = [];
return this;
};
/**
* Build a SQL Find Query using the defined schema.
*/
Sequel.prototype.find = function find(currentTable, queryObject) {
// Step 1:
// Build out the Select statements
var selectObject = this.select(currentTable, queryObject);
this.queries = selectObject.select;
var whereObject;
var childQueries;
var query;
var values;
/**
* Step 2 - Build out the parent query.
*/
whereObject = this.simpleWhere(currentTable, queryObject);
this.queries[0] += ' ' + whereObject.query;
this.values[0] = whereObject.values;
/**
* Step 3 - Build out the child query templates.
*/
childQueries = this.complexWhere(currentTable, queryObject);
this.queries = this.queries.concat(childQueries);
return {
query: this.queries,
values: this.values
};
};
/**
* Build a SQL Count Query using the defined schema.
*/
Sequel.prototype.count = function count(currentTable, queryObject) {
// Escape table name
var tableName = utils.escapeName(this.schema[currentTable].tableName, this.escapeCharacter, this.schemaName);
var alias = utils.escapeName(this.schema[currentTable].tableName, this.escapeCharacter);
// Step 1:
// Build out the Count statements
this.queries = ['SELECT COUNT(*) as count FROM '];
var subQuery = 'SELECT * FROM ' + tableName;
var whereObject;
var childQueries;
var query;
var values;
/**
* Step 2 - Build out the WHERE part of the query.
*/
whereObject = this.simpleWhere(currentTable, queryObject);
// Append the sub-query to the COUNT so you end up with something that looks like:
// SELECT count(*) as count FROM (SELECT * FROM table LIMIT 10 OFFSET 10) AS tableAlias;
this.queries[0] += '(SELECT * FROM ' + tableName + ' ' + whereObject.query + ') AS ' + alias;
this.values[0] = whereObject.values;
/**
* Step 3 - Build out the child query templates.
*/
childQueries = this.complexWhere(currentTable, queryObject);
this.queries = this.queries.concat(childQueries);
return {
query: this.queries,
values: this.values
};
};
/**
* Build a SQL Create Query.
*/
Sequel.prototype.create = function create(currentTable, data) {
var options = {
parameterized: this.parameterized,
escapeCharacter: this.escapeCharacter,
escapeInserts: this.escapeInserts,
schemaName: this.schemaName
};
// Transform the Data object into arrays used in a parameterized query
var attributes = utils.mapAttributes(data, options);
var columnNames = attributes.keys.join(', ');
var paramValues = attributes.params.join(', ');
// Build Query
var query = 'INSERT INTO ' + utils.escapeName(currentTable, this.escapeCharacter, this.schemaName) + ' (' + columnNames + ') values (' + paramValues + ')';
if(this.canReturnValues) {
query += ' RETURNING *';
}
return { query: query, values: attributes.values };
};
/**
* Build a SQL Update Query.
*/
Sequel.prototype.update = function update(currentTable, queryObject, data) {
var options = {
parameterized: this.parameterized,
escapeCharacter: this.escapeCharacter,
escapeInserts: this.escapeInserts,
schemaName: this.schemaName
};
// Get the attribute identity (as opposed to the table name)
var identity = currentTable;
// Create the query with the tablename aliased as the identity (in case they are different)
var query = 'UPDATE ' + utils.escapeName(currentTable, this.escapeCharacter, this.schemaName) + ' AS ' + utils.escapeName(identity, this.escapeCharacter) + ' ';
// Transform the Data object into arrays used in a parameterized query
var attributes = utils.mapAttributes(data, options);
// Update the paramCount
var paramCount = attributes.params.length + 1;
// Build SET string
var str = '';
for(var i=0; i < attributes.keys.length; i++) {
str += attributes.keys[i] + ' = ' + attributes.params[i] + ', ';
}
// Remove trailing comma
str = str.slice(0, -2);
query += 'SET ' + str + ' ';
// Add data values to this._values
var values = attributes.values;
// Build Criteria clause
var whereObject = this.simpleWhere(currentTable, queryObject, { paramCount: paramCount });
query += ' ' + whereObject.query;
values = values.concat(whereObject.values);
if(this.canReturnValues) {
query += ' RETURNING *';
}
return {
query: query,
values: values
};
};
/**
* Build Delete SQL query.
*/
Sequel.prototype.destroy = function destroy(currentTable, queryObject) {
// Get the attribute identity (as opposed to the table name)
var identity = currentTable;
var query = 'DELETE ' + (this.declareDeleteAlias ? utils.escapeName(identity, this.escapeCharacter) : '') + ' FROM ' + utils.escapeName(currentTable, this.escapeCharacter, this.schemaName) + ' AS ' + utils.escapeName(identity, this.escapeCharacter) + ' ';
// Build Criteria clause
var whereObject = this.simpleWhere(currentTable, queryObject);
query += ' ' + whereObject.query;
var values = whereObject.values;
if(this.canReturnValues) {
query += ' RETURNING *';
}
return {
query: query,
values: values
};
};
/**
* Build the select statements for a query.
*/
Sequel.prototype.select = function select(currentTable, queryObject) {
var options = {
escapeCharacter: this.escapeCharacter,
caseSensitive: this.caseSensitive,
cast: this.cast,
wlNext: this.wlNext,
schemaName: this.schemaName
};
return new SelectBuilder(this.schema, currentTable, queryObject, options);
};
/**
* Build the where statements for a query.
*/
Sequel.prototype.simpleWhere = function simpleWhere(currentTable, queryObject, options) {
var _options = {
parameterized: this.parameterized,
caseSensitive: this.caseSensitive,
escapeCharacter: this.escapeCharacter,
wlNext: this.wlNext,
schemaName: this.schemaName
};
var where = new WhereBuilder(this.schema, currentTable, _options);
return where.single(queryObject, options);
};
Sequel.prototype.complexWhere = function complexWhere(currentTable, queryObject, options) {
var _options = {
parameterized: this.parameterized,
caseSensitive: this.caseSensitive,
escapeCharacter: this.escapeCharacter,
schemaName: this.schemaName
};
var where = new WhereBuilder(this.schema, currentTable, _options);
return where.complex(queryObject, options);
};