2150 lines
73 KiB
JavaScript
2150 lines
73 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
var Utils = require('../../utils')
|
||
|
|
, SqlString = require('../../sql-string')
|
||
|
|
, Model = require('../../model')
|
||
|
|
, DataTypes = require('../../data-types')
|
||
|
|
, _ = require('lodash')
|
||
|
|
, util = require('util')
|
||
|
|
, Dottie = require('dottie')
|
||
|
|
, uuid = require('node-uuid');
|
||
|
|
|
||
|
|
/* istanbul ignore next */
|
||
|
|
var throwMethodUndefined = function(methodName) {
|
||
|
|
throw new Error('The method "' + methodName + '" is not defined! Please add it to your sql dialect.');
|
||
|
|
};
|
||
|
|
|
||
|
|
var QueryGenerator = {
|
||
|
|
options: {},
|
||
|
|
|
||
|
|
addSchema: function(param) {
|
||
|
|
var self = this
|
||
|
|
, schema = (!param.options && param.schema) || (param.options && param.options.schema ? param.options.schema : undefined)
|
||
|
|
, schemaDelimiter = (param.options && param.options.schemaDelimiter ? param.options.schemaDelimiter : undefined);
|
||
|
|
|
||
|
|
if (!schema) return param.tableName || param;
|
||
|
|
|
||
|
|
return {
|
||
|
|
tableName: param.tableName || param,
|
||
|
|
table: param.tableName || param,
|
||
|
|
name: param.name || param,
|
||
|
|
schema: schema,
|
||
|
|
delimiter: schemaDelimiter || '.',
|
||
|
|
toString: function() {
|
||
|
|
return self.quoteTable(this);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query for dropping a schema
|
||
|
|
*/
|
||
|
|
dropSchema: function(tableName, options) {
|
||
|
|
return this.dropTableQuery(tableName, options);
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query for creating a table.
|
||
|
|
Parameters:
|
||
|
|
- tableName: Name of the new table.
|
||
|
|
- attributes: An object with containing attribute-attributeType-pairs.
|
||
|
|
Attributes should have the format:
|
||
|
|
{attributeName: type, attr2: type2}
|
||
|
|
--> e.g. {title: 'VARCHAR(255)'}
|
||
|
|
- options: An object with options.
|
||
|
|
Defaults: { engine: 'InnoDB', charset: null }
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
createTableQuery: function(tableName, attributes, options) {
|
||
|
|
throwMethodUndefined('createTableQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
versionQuery: function(tableName, attributes, options) {
|
||
|
|
throwMethodUndefined('versionQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
describeTableQuery: function(tableName, schema, schemaDelimiter) {
|
||
|
|
var table = this.quoteTable(
|
||
|
|
this.addSchema({
|
||
|
|
tableName: tableName,
|
||
|
|
options: {
|
||
|
|
schema: schema,
|
||
|
|
schemaDelimiter: schemaDelimiter
|
||
|
|
}
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
return 'DESCRIBE ' + table + ';';
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query for dropping a table.
|
||
|
|
*/
|
||
|
|
dropTableQuery: function(tableName, options) {
|
||
|
|
options = options || {};
|
||
|
|
|
||
|
|
var query = 'DROP TABLE IF EXISTS <%= table %>;';
|
||
|
|
|
||
|
|
return Utils._.template(query)({
|
||
|
|
table: this.quoteTable(tableName)
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a rename table query.
|
||
|
|
Parameters:
|
||
|
|
- originalTableName: Name of the table before execution.
|
||
|
|
- futureTableName: Name of the table after execution.
|
||
|
|
*/
|
||
|
|
renameTableQuery: function(before, after) {
|
||
|
|
var query = 'ALTER TABLE <%= before %> RENAME TO <%= after %>;';
|
||
|
|
return Utils._.template(query)({
|
||
|
|
before: this.quoteTable(before),
|
||
|
|
after: this.quoteTable(after)
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query, which gets all available table names in the database.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
showTablesQuery: function() {
|
||
|
|
throwMethodUndefined('showTablesQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query, which adds an attribute to an existing table.
|
||
|
|
Parameters:
|
||
|
|
- tableName: Name of the existing table.
|
||
|
|
- attributes: A hash with attribute-attributeOptions-pairs.
|
||
|
|
- key: attributeName
|
||
|
|
- value: A hash with attribute specific options:
|
||
|
|
- type: DataType
|
||
|
|
- defaultValue: A String with the default value
|
||
|
|
- allowNull: Boolean
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
addColumnQuery: function(tableName, attributes) {
|
||
|
|
throwMethodUndefined('addColumnQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query, which removes an attribute from an existing table.
|
||
|
|
Parameters:
|
||
|
|
- tableName: Name of the existing table
|
||
|
|
- attributeName: Name of the obsolete attribute.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
removeColumnQuery: function(tableName, attributeName) {
|
||
|
|
throwMethodUndefined('removeColumnQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query, which modifies an existing attribute from a table.
|
||
|
|
Parameters:
|
||
|
|
- tableName: Name of the existing table.
|
||
|
|
- attributes: A hash with attribute-attributeOptions-pairs.
|
||
|
|
- key: attributeName
|
||
|
|
- value: A hash with attribute specific options:
|
||
|
|
- type: DataType
|
||
|
|
- defaultValue: A String with the default value
|
||
|
|
- allowNull: Boolean
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
changeColumnQuery: function(tableName, attributes) {
|
||
|
|
throwMethodUndefined('changeColumnQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query, which renames an existing attribute.
|
||
|
|
Parameters:
|
||
|
|
- tableName: Name of an existing table.
|
||
|
|
- attrNameBefore: The name of the attribute, which shall be renamed.
|
||
|
|
- attrNameAfter: The name of the attribute, after renaming.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
renameColumnQuery: function(tableName, attrNameBefore, attrNameAfter) {
|
||
|
|
throwMethodUndefined('renameColumnQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns an insert into command. Parameters: table name + hash of attribute-value-pairs.
|
||
|
|
*/
|
||
|
|
insertQuery: function(table, valueHash, modelAttributes, options) {
|
||
|
|
options = options || {};
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
var query
|
||
|
|
, valueQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %> (<%= attributes %>)<%= output %> VALUES (<%= values %>)'
|
||
|
|
, emptyQuery = '<%= tmpTable %>INSERT<%= ignore %> INTO <%= table %><%= output %>'
|
||
|
|
, outputFragment
|
||
|
|
, fields = []
|
||
|
|
, values = []
|
||
|
|
, key
|
||
|
|
, value
|
||
|
|
, identityWrapperRequired = false
|
||
|
|
, modelAttributeMap = {}
|
||
|
|
, tmpTable = '' //tmpTable declaration for trigger
|
||
|
|
, selectFromTmp = '' //Select statement for trigger
|
||
|
|
, tmpColumns = '' //Columns for temp table for trigger
|
||
|
|
, outputColumns = '' //Columns to capture into temp table for trigger
|
||
|
|
, attribute //Model attribute holder
|
||
|
|
, modelKey; //key for model
|
||
|
|
|
||
|
|
if (modelAttributes) {
|
||
|
|
Utils._.each(modelAttributes, function(attribute, key) {
|
||
|
|
modelAttributeMap[key] = attribute;
|
||
|
|
if (attribute.field) {
|
||
|
|
modelAttributeMap[attribute.field] = attribute;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._dialect.supports['DEFAULT VALUES']) {
|
||
|
|
emptyQuery += ' DEFAULT VALUES';
|
||
|
|
} else if (this._dialect.supports['VALUES ()']) {
|
||
|
|
emptyQuery += ' VALUES ()';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._dialect.supports.returnValues && options.returning) {
|
||
|
|
if (!!this._dialect.supports.returnValues.returning) {
|
||
|
|
valueQuery += ' RETURNING *';
|
||
|
|
emptyQuery += ' RETURNING *';
|
||
|
|
} else if (!!this._dialect.supports.returnValues.output) {
|
||
|
|
outputFragment = ' OUTPUT INSERTED.*';
|
||
|
|
|
||
|
|
//To capture output rows when there is a trigger on MSSQL DB
|
||
|
|
if (modelAttributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {
|
||
|
|
tmpTable = 'declare @tmp table (<%= columns %>); ';
|
||
|
|
|
||
|
|
for (modelKey in modelAttributes){
|
||
|
|
attribute = modelAttributes[modelKey];
|
||
|
|
if(!(attribute.type instanceof DataTypes.VIRTUAL)){
|
||
|
|
if (tmpColumns.length > 0){
|
||
|
|
tmpColumns += ',';
|
||
|
|
outputColumns += ',';
|
||
|
|
}
|
||
|
|
|
||
|
|
tmpColumns += this.quoteIdentifier(attribute.field) + ' ' + attribute.type.toSql();
|
||
|
|
outputColumns += 'INSERTED.' + this.quoteIdentifier(attribute.field);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var replacement ={
|
||
|
|
columns : tmpColumns
|
||
|
|
};
|
||
|
|
|
||
|
|
tmpTable = Utils._.template(tmpTable)(replacement).trim();
|
||
|
|
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
|
||
|
|
selectFromTmp = ';select * from @tmp';
|
||
|
|
|
||
|
|
valueQuery += selectFromTmp;
|
||
|
|
emptyQuery += selectFromTmp;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._dialect.supports.EXCEPTION && options.exception) {
|
||
|
|
// Mostly for internal use, so we expect the user to know what he's doing!
|
||
|
|
// pg_temp functions are private per connection, so we never risk this function interfering with another one.
|
||
|
|
|
||
|
|
// <= 9.1
|
||
|
|
//options.exception = 'WHEN unique_violation THEN NULL;';
|
||
|
|
//valueQuery = 'CREATE OR REPLACE FUNCTION pg_temp.testfunc() RETURNS SETOF <%= table %> AS $body$ BEGIN RETURN QUERY ' + valueQuery + '; EXCEPTION ' + options.exception + ' END; $body$ LANGUAGE plpgsql; SELECT * FROM pg_temp.testfunc(); DROP FUNCTION IF EXISTS pg_temp.testfunc();';
|
||
|
|
|
||
|
|
// >= 9.2 - Use a UUID but prefix with 'func_' (numbers first not allowed)
|
||
|
|
var delimiter = '$func_' + uuid.v4().replace(/-/g, '') + '$';
|
||
|
|
|
||
|
|
options.exception = 'WHEN unique_violation THEN GET STACKED DIAGNOSTICS sequelize_caught_exception = PG_EXCEPTION_DETAIL;';
|
||
|
|
valueQuery = 'CREATE OR REPLACE FUNCTION pg_temp.testfunc(OUT response <%= table %>, OUT sequelize_caught_exception text) RETURNS RECORD AS ' + delimiter +
|
||
|
|
' BEGIN ' + valueQuery + ' INTO response; EXCEPTION ' + options.exception + ' END ' + delimiter +
|
||
|
|
' LANGUAGE plpgsql; SELECT (testfunc.response).*, testfunc.sequelize_caught_exception FROM pg_temp.testfunc(); DROP FUNCTION IF EXISTS pg_temp.testfunc()';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._dialect.supports['ON DUPLICATE KEY'] && options.onDuplicate) {
|
||
|
|
valueQuery += ' ON DUPLICATE KEY ' + options.onDuplicate;
|
||
|
|
emptyQuery += ' ON DUPLICATE KEY ' + options.onDuplicate;
|
||
|
|
}
|
||
|
|
|
||
|
|
valueHash = Utils.removeNullValuesFromHash(valueHash, this.options.omitNull);
|
||
|
|
|
||
|
|
for (key in valueHash) {
|
||
|
|
if (valueHash.hasOwnProperty(key)) {
|
||
|
|
value = valueHash[key];
|
||
|
|
fields.push(this.quoteIdentifier(key));
|
||
|
|
|
||
|
|
// SERIALS' can't be NULL in postgresql, use DEFAULT where supported
|
||
|
|
if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true && !value) {
|
||
|
|
if (!this._dialect.supports.autoIncrement.defaultValue) {
|
||
|
|
fields.splice(-1,1);
|
||
|
|
} else if (this._dialect.supports.DEFAULT) {
|
||
|
|
values.push('DEFAULT');
|
||
|
|
} else {
|
||
|
|
values.push(this.escape(null));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (modelAttributeMap && modelAttributeMap[key] && modelAttributeMap[key].autoIncrement === true) {
|
||
|
|
identityWrapperRequired = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
values.push(this.escape(value, (modelAttributeMap && modelAttributeMap[key]) || undefined));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var replacements = {
|
||
|
|
ignore: options.ignore ? this._dialect.supports.IGNORE : '',
|
||
|
|
table: this.quoteTable(table),
|
||
|
|
attributes: fields.join(','),
|
||
|
|
output: outputFragment,
|
||
|
|
values: values.join(','),
|
||
|
|
tmpTable: tmpTable
|
||
|
|
};
|
||
|
|
|
||
|
|
query = (replacements.attributes.length ? valueQuery : emptyQuery) + ';';
|
||
|
|
if (identityWrapperRequired && this._dialect.supports.autoIncrement.identityInsert) {
|
||
|
|
query = [
|
||
|
|
'SET IDENTITY_INSERT', this.quoteTable(table), 'ON;',
|
||
|
|
query,
|
||
|
|
'SET IDENTITY_INSERT', this.quoteTable(table), 'OFF;',
|
||
|
|
].join(' ');
|
||
|
|
}
|
||
|
|
|
||
|
|
return Utils._.template(query)(replacements);
|
||
|
|
},
|
||
|
|
/*
|
||
|
|
Returns an insert into command for multiple values.
|
||
|
|
Parameters: table name + list of hashes of attribute-value-pairs.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
bulkInsertQuery: function(tableName, attrValueHashes, options, modelAttributes) {
|
||
|
|
throwMethodUndefined('bulkInsertQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns an update query.
|
||
|
|
Parameters:
|
||
|
|
- tableName -> Name of the table
|
||
|
|
- values -> A hash with attribute-value-pairs
|
||
|
|
- where -> A hash with conditions (e.g. {name: 'foo'})
|
||
|
|
OR an ID as integer
|
||
|
|
OR a string with conditions (e.g. 'name="foo"').
|
||
|
|
If you use a string, you have to escape it on your own.
|
||
|
|
*/
|
||
|
|
updateQuery: function(tableName, attrValueHash, where, options, attributes) {
|
||
|
|
options = options || {};
|
||
|
|
_.defaults(options, this.options);
|
||
|
|
|
||
|
|
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, options.omitNull, options);
|
||
|
|
|
||
|
|
var query
|
||
|
|
, values = []
|
||
|
|
, outputFragment
|
||
|
|
, modelAttributeMap = {}
|
||
|
|
, tmpTable = '' //tmpTable declaration for trigger
|
||
|
|
, selectFromTmp = '' //Select statement for trigger
|
||
|
|
, tmpColumns = '' //Columns for temp table for trigger
|
||
|
|
, outputColumns = '' //Columns to capture into temp table for trigger
|
||
|
|
, attribute //Model attribute holder
|
||
|
|
, modelKey; //key for model
|
||
|
|
|
||
|
|
query = '<%= tmpTable %>UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
|
||
|
|
|
||
|
|
if (this._dialect.supports['LIMIT ON UPDATE'] && options.limit) {
|
||
|
|
query += ' LIMIT ' + this.escape(options.limit) + ' ';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._dialect.supports.returnValues) {
|
||
|
|
if (!!this._dialect.supports.returnValues.output) {
|
||
|
|
// we always need this for mssql
|
||
|
|
outputFragment = ' OUTPUT INSERTED.*';
|
||
|
|
|
||
|
|
//To capture output rows when there is a trigger on MSSQL DB
|
||
|
|
if (attributes && options.hasTrigger && this._dialect.supports.tmpTableTrigger) {
|
||
|
|
tmpTable = 'declare @tmp table (<%= columns %>); ';
|
||
|
|
|
||
|
|
for (modelKey in attributes){
|
||
|
|
attribute = attributes[modelKey];
|
||
|
|
if(!(attribute.type instanceof DataTypes.VIRTUAL)){
|
||
|
|
if (tmpColumns.length > 0){
|
||
|
|
tmpColumns += ',';
|
||
|
|
outputColumns += ',';
|
||
|
|
}
|
||
|
|
|
||
|
|
tmpColumns += this.quoteIdentifier(attribute.field) + ' ' + attribute.type.toSql();
|
||
|
|
outputColumns += 'INSERTED.' + this.quoteIdentifier(attribute.field);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var replacement ={
|
||
|
|
columns : tmpColumns
|
||
|
|
};
|
||
|
|
|
||
|
|
tmpTable = Utils._.template(tmpTable)(replacement).trim();
|
||
|
|
outputFragment = ' OUTPUT ' + outputColumns + ' into @tmp';
|
||
|
|
selectFromTmp = ';select * from @tmp';
|
||
|
|
|
||
|
|
query += selectFromTmp;
|
||
|
|
}
|
||
|
|
} else if (this._dialect.supports.returnValues && options.returning) {
|
||
|
|
query += ' RETURNING *';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attributes) {
|
||
|
|
Utils._.each(attributes, function(attribute, key) {
|
||
|
|
modelAttributeMap[key] = attribute;
|
||
|
|
if (attribute.field) {
|
||
|
|
modelAttributeMap[attribute.field] = attribute;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
for (var key in attrValueHash) {
|
||
|
|
if (modelAttributeMap && modelAttributeMap[key] &&
|
||
|
|
modelAttributeMap[key].autoIncrement === true &&
|
||
|
|
!this._dialect.supports.autoIncrement.update) {
|
||
|
|
// not allowed to update identity column
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
var value = attrValueHash[key];
|
||
|
|
values.push(this.quoteIdentifier(key) + '=' + this.escape(value, (modelAttributeMap && modelAttributeMap[key] || undefined)));
|
||
|
|
}
|
||
|
|
|
||
|
|
var replacements = {
|
||
|
|
table: this.quoteTable(tableName),
|
||
|
|
values: values.join(','),
|
||
|
|
output: outputFragment,
|
||
|
|
where: this.whereQuery(where),
|
||
|
|
tmpTable: tmpTable
|
||
|
|
};
|
||
|
|
|
||
|
|
if (values.length === 0) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
return Utils._.template(query)(replacements).trim();
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns an upsert query.
|
||
|
|
*/
|
||
|
|
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
|
||
|
|
throwMethodUndefined('upsertQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a deletion query.
|
||
|
|
Parameters:
|
||
|
|
- tableName -> Name of the table
|
||
|
|
- where -> A hash with conditions (e.g. {name: 'foo'})
|
||
|
|
OR an ID as integer
|
||
|
|
OR a string with conditions (e.g. 'name="foo"').
|
||
|
|
If you use a string, you have to escape it on your own.
|
||
|
|
Options:
|
||
|
|
- limit -> Maximaum count of lines to delete
|
||
|
|
- truncate -> boolean - whether to use an 'optimized' mechanism (i.e. TRUNCATE) if available,
|
||
|
|
note that this should not be the default behaviour because TRUNCATE does not
|
||
|
|
always play nicely (e.g. InnoDB tables with FK constraints)
|
||
|
|
(@see http://dev.mysql.com/doc/refman/5.6/en/truncate-table.html).
|
||
|
|
Note that truncate must ignore limit and where
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
deleteQuery: function(tableName, where, options) {
|
||
|
|
throwMethodUndefined('deleteQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns an update query.
|
||
|
|
Parameters:
|
||
|
|
- tableName -> Name of the table
|
||
|
|
- values -> A hash with attribute-value-pairs
|
||
|
|
- where -> A hash with conditions (e.g. {name: 'foo'})
|
||
|
|
OR an ID as integer
|
||
|
|
OR a string with conditions (e.g. 'name="foo"').
|
||
|
|
If you use a string, you have to escape it on your own.
|
||
|
|
*/
|
||
|
|
incrementQuery: function(tableName, attrValueHash, where, options) {
|
||
|
|
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull);
|
||
|
|
|
||
|
|
var query
|
||
|
|
, key
|
||
|
|
, value
|
||
|
|
, values = []
|
||
|
|
, outputFragment;
|
||
|
|
|
||
|
|
query = 'UPDATE <%= table %> SET <%= values %><%= output %> <%= where %>';
|
||
|
|
if (this._dialect.supports.returnValues) {
|
||
|
|
if (!!this._dialect.supports.returnValues.returning) {
|
||
|
|
query += ' RETURNING *';
|
||
|
|
} else if (!!this._dialect.supports.returnValues.output) {
|
||
|
|
outputFragment = ' OUTPUT INSERTED.*';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (key in attrValueHash) {
|
||
|
|
value = attrValueHash[key];
|
||
|
|
values.push(this.quoteIdentifier(key) + '=' + this.quoteIdentifier(key) + ' + ' + this.escape(value));
|
||
|
|
}
|
||
|
|
|
||
|
|
options = options || {};
|
||
|
|
for (key in options) {
|
||
|
|
value = options[key];
|
||
|
|
values.push(this.quoteIdentifier(key) + '=' + this.escape(value));
|
||
|
|
}
|
||
|
|
|
||
|
|
var replacements = {
|
||
|
|
table: this.quoteTable(tableName),
|
||
|
|
values: values.join(','),
|
||
|
|
output: outputFragment,
|
||
|
|
where: this.whereQuery(where)
|
||
|
|
};
|
||
|
|
|
||
|
|
return Utils._.template(query)(replacements);
|
||
|
|
},
|
||
|
|
|
||
|
|
nameIndexes: function (indexes, rawTablename) {
|
||
|
|
return Utils._.map(indexes, function (index) {
|
||
|
|
if (!index.hasOwnProperty('name')) {
|
||
|
|
var onlyAttributeNames = index.fields.map(function(field) {
|
||
|
|
return (typeof field === 'string') ? field : (field.name || field.attribute);
|
||
|
|
}.bind(this));
|
||
|
|
|
||
|
|
index.name = Utils.inflection.underscore(rawTablename + '_' + onlyAttributeNames.join('_'));
|
||
|
|
}
|
||
|
|
|
||
|
|
return index;
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns an add index query.
|
||
|
|
Parameters:
|
||
|
|
- tableName -> Name of an existing table, possibly with schema.
|
||
|
|
- options:
|
||
|
|
- type: UNIQUE|FULLTEXT|SPATIAL
|
||
|
|
- name: The name of the index. Default is <table>_<attr1>_<attr2>
|
||
|
|
- fields: An array of attributes as string or as hash.
|
||
|
|
If the attribute is a hash, it must have the following content:
|
||
|
|
- name: The name of the attribute/column
|
||
|
|
- length: An integer. Optional
|
||
|
|
- order: 'ASC' or 'DESC'. Optional
|
||
|
|
- parser
|
||
|
|
- rawTablename, the name of the table, without schema. Used to create the name of the index
|
||
|
|
*/
|
||
|
|
addIndexQuery: function(tableName, attributes, options, rawTablename) {
|
||
|
|
var fieldsSql;
|
||
|
|
options = options || {};
|
||
|
|
|
||
|
|
if (!Array.isArray(attributes)) {
|
||
|
|
options = attributes;
|
||
|
|
attributes = undefined;
|
||
|
|
} else {
|
||
|
|
options.fields = attributes;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Backwards compatability
|
||
|
|
if (options.indexName) {
|
||
|
|
options.name = options.indexName;
|
||
|
|
}
|
||
|
|
if (options.indicesType) {
|
||
|
|
options.type = options.indicesType;
|
||
|
|
}
|
||
|
|
if (options.indexType || options.method) {
|
||
|
|
options.using = options.indexType || options.method;
|
||
|
|
}
|
||
|
|
|
||
|
|
options.prefix = options.prefix || rawTablename || tableName;
|
||
|
|
if (options.prefix && _.isString(options.prefix)) {
|
||
|
|
options.prefix = options.prefix.replace(/\./g, '_');
|
||
|
|
options.prefix = options.prefix.replace(/(\"|\')/g, '');
|
||
|
|
}
|
||
|
|
|
||
|
|
fieldsSql = options.fields.map(function(field) {
|
||
|
|
if (typeof field === 'string') {
|
||
|
|
return this.quoteIdentifier(field);
|
||
|
|
} else if (field._isSequelizeMethod) {
|
||
|
|
return this.handleSequelizeMethod(field);
|
||
|
|
} else {
|
||
|
|
var result = '';
|
||
|
|
|
||
|
|
if (field.attribute) {
|
||
|
|
field.name = field.attribute;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!field.name) {
|
||
|
|
throw new Error('The following index field has no name: ' + util.inspect(field));
|
||
|
|
}
|
||
|
|
|
||
|
|
result += this.quoteIdentifier(field.name);
|
||
|
|
|
||
|
|
if (this._dialect.supports.index.collate && field.collate) {
|
||
|
|
result += ' COLLATE ' + this.quoteIdentifier(field.collate);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this._dialect.supports.index.length && field.length) {
|
||
|
|
result += '(' + field.length + ')';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (field.order) {
|
||
|
|
result += ' ' + field.order;
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
}.bind(this));
|
||
|
|
|
||
|
|
if (!options.name) {
|
||
|
|
// Mostly for cases where addIndex is called directly by the user without an options object (for example in migrations)
|
||
|
|
// All calls that go through sequelize should already have a name
|
||
|
|
options = this.nameIndexes([options], options.prefix)[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
options = Utils._.defaults(options, {
|
||
|
|
type: '',
|
||
|
|
parser: null
|
||
|
|
});
|
||
|
|
|
||
|
|
if (options.type.toLowerCase() === 'unique') {
|
||
|
|
options.unique = true;
|
||
|
|
delete options.type;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!this._dialect.supports.index.type) {
|
||
|
|
delete options.type;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.where) {
|
||
|
|
options.where = this.whereQuery(options.where);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_.isString(tableName)) {
|
||
|
|
tableName = this.quoteIdentifiers(tableName);
|
||
|
|
} else {
|
||
|
|
tableName = this.quoteTable(tableName);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
var concurrently = this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined
|
||
|
|
, ind;
|
||
|
|
if (this._dialect.supports.indexViaAlter) {
|
||
|
|
ind = [
|
||
|
|
'ALTER TABLE',
|
||
|
|
tableName,
|
||
|
|
concurrently,
|
||
|
|
'ADD'
|
||
|
|
];
|
||
|
|
} else {
|
||
|
|
ind = ['CREATE'];
|
||
|
|
}
|
||
|
|
|
||
|
|
ind = ind.concat(
|
||
|
|
options.unique ? 'UNIQUE' : '',
|
||
|
|
options.type, 'INDEX',
|
||
|
|
!this._dialect.supports.indexViaAlter ? concurrently : undefined,
|
||
|
|
this.quoteIdentifiers(options.name),
|
||
|
|
this._dialect.supports.index.using === 1 && options.using ? 'USING ' + options.using : '',
|
||
|
|
!this._dialect.supports.indexViaAlter ? 'ON ' + tableName : undefined,
|
||
|
|
this._dialect.supports.index.using === 2 && options.using ? 'USING ' + options.using : '',
|
||
|
|
'(' + fieldsSql.join(', ') + (options.operator ? ' '+options.operator : '') + ')',
|
||
|
|
(this._dialect.supports.index.parser && options.parser ? 'WITH PARSER ' + options.parser : undefined),
|
||
|
|
(this._dialect.supports.index.where && options.where ? options.where : undefined)
|
||
|
|
);
|
||
|
|
|
||
|
|
return Utils._.compact(ind).join(' ');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query listing indexes for a given table.
|
||
|
|
Parameters:
|
||
|
|
- tableName: Name of an existing table.
|
||
|
|
- options:
|
||
|
|
- database: Name of the database.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
showIndexesQuery: function(tableName, options) {
|
||
|
|
throwMethodUndefined('showIndexesQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a remove index query.
|
||
|
|
Parameters:
|
||
|
|
- tableName: Name of an existing table.
|
||
|
|
- indexNameOrAttributes: The name of the index as string or an array of attribute names.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
removeIndexQuery: function(tableName, indexNameOrAttributes) {
|
||
|
|
throwMethodUndefined('removeIndexQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
This method transforms an array of attribute hashes into equivalent
|
||
|
|
sql attribute definition.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
attributesToSQL: function(attributes) {
|
||
|
|
throwMethodUndefined('attributesToSQL');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns all auto increment fields of a factory.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
findAutoIncrementField: function(factory) {
|
||
|
|
throwMethodUndefined('findAutoIncrementField');
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
quoteTable: function(param, as) {
|
||
|
|
var table = '';
|
||
|
|
|
||
|
|
if (as === true) {
|
||
|
|
as = param.as || param.name || param;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_.isObject(param)) {
|
||
|
|
if (this._dialect.supports.schemas) {
|
||
|
|
if (param.schema) {
|
||
|
|
table += this.quoteIdentifier(param.schema) + '.';
|
||
|
|
}
|
||
|
|
|
||
|
|
table += this.quoteIdentifier(param.tableName);
|
||
|
|
} else {
|
||
|
|
if (param.schema) {
|
||
|
|
table += param.schema + (param.delimiter || '.');
|
||
|
|
}
|
||
|
|
|
||
|
|
table += param.tableName;
|
||
|
|
table = this.quoteIdentifier(table);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
} else {
|
||
|
|
table = this.quoteIdentifier(param);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (as) {
|
||
|
|
table += ' AS ' + this.quoteIdentifier(as);
|
||
|
|
}
|
||
|
|
return table;
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Quote an object based on its type. This is a more general version of quoteIdentifiers
|
||
|
|
Strings: should proxy to quoteIdentifiers
|
||
|
|
Arrays:
|
||
|
|
* Expects array in the form: [<model> (optional), <model> (optional),... String, String (optional)]
|
||
|
|
Each <model> can be a model or an object {model: Model, as: String}, matching include
|
||
|
|
* Zero or more models can be included in the array and are used to trace a path through the tree of
|
||
|
|
included nested associations. This produces the correct table name for the ORDER BY/GROUP BY SQL
|
||
|
|
and quotes it.
|
||
|
|
* If a single string is appended to end of array, it is quoted.
|
||
|
|
If two strings appended, the 1st string is quoted, the 2nd string unquoted.
|
||
|
|
Objects:
|
||
|
|
* If raw is set, that value should be returned verbatim, without quoting
|
||
|
|
* If fn is set, the string should start with the value of fn, starting paren, followed by
|
||
|
|
the values of cols (which is assumed to be an array), quoted and joined with ', ',
|
||
|
|
unless they are themselves objects
|
||
|
|
* If direction is set, should be prepended
|
||
|
|
|
||
|
|
Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could
|
||
|
|
potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
|
||
|
|
*/
|
||
|
|
quote: function(obj, parent, force) {
|
||
|
|
if (Utils._.isString(obj)) {
|
||
|
|
return this.quoteIdentifiers(obj, force);
|
||
|
|
} else if (Array.isArray(obj)) {
|
||
|
|
// loop through array, adding table names of models to quoted
|
||
|
|
// (checking associations to see if names should be singularised or not)
|
||
|
|
var tableNames = []
|
||
|
|
, parentAssociation
|
||
|
|
, len = obj.length
|
||
|
|
, item
|
||
|
|
, model
|
||
|
|
, as
|
||
|
|
, association;
|
||
|
|
for (var i = 0; i < len - 1; i++) {
|
||
|
|
item = obj[i];
|
||
|
|
if (item._modelAttribute || Utils._.isString(item) || item._isSequelizeMethod || 'raw' in item) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (item instanceof Model) {
|
||
|
|
model = item;
|
||
|
|
as = undefined;
|
||
|
|
} else {
|
||
|
|
model = item.model;
|
||
|
|
as = item.as;
|
||
|
|
}
|
||
|
|
|
||
|
|
// check if model provided is through table
|
||
|
|
if (!as && parentAssociation && parentAssociation.through && parentAssociation.through.model === model) {
|
||
|
|
association = {as: model.name};
|
||
|
|
} else {
|
||
|
|
// find applicable association for linking parent to this model
|
||
|
|
association = parent.getAssociation(model, as);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (association) {
|
||
|
|
tableNames[i] = association.as;
|
||
|
|
parent = model;
|
||
|
|
parentAssociation = association;
|
||
|
|
} else {
|
||
|
|
tableNames[i] = model.tableName;
|
||
|
|
throw new Error('\'' + tableNames.join('.') + '\' in order / group clause is not valid association');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// add 1st string as quoted, 2nd as unquoted raw
|
||
|
|
var sql = (i > 0 ? this.quoteIdentifier(tableNames.join('.')) + '.' : (Utils._.isString(obj[0]) ? this.quoteIdentifier(parent.name) + '.' : '')) + this.quote(obj[i], parent, force);
|
||
|
|
if (i < len - 1) {
|
||
|
|
if (obj[i + 1]._isSequelizeMethod) {
|
||
|
|
sql += this.handleSequelizeMethod(obj[i + 1]);
|
||
|
|
} else {
|
||
|
|
sql += ' ' + obj[i + 1];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return sql;
|
||
|
|
} else if (obj._modelAttribute) {
|
||
|
|
return this.quoteTable(obj.Model.name) + '.' + obj.fieldName;
|
||
|
|
} else if (obj._isSequelizeMethod) {
|
||
|
|
return this.handleSequelizeMethod(obj);
|
||
|
|
} else if (Utils._.isObject(obj) && 'raw' in obj) {
|
||
|
|
return obj.raw;
|
||
|
|
} else {
|
||
|
|
throw new Error('Unknown structure passed to order / group: ' + JSON.stringify(obj));
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Create a trigger
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
createTrigger: function(tableName, triggerName, timingType, fireOnArray, functionName, functionParams, optionsArray) {
|
||
|
|
throwMethodUndefined('createTrigger');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Drop a trigger
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
dropTrigger: function(tableName, triggerName) {
|
||
|
|
throwMethodUndefined('dropTrigger');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Rename a trigger
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
renameTrigger: function(tableName, oldTriggerName, newTriggerName) {
|
||
|
|
throwMethodUndefined('renameTrigger');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Create a function
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
createFunction: function(functionName, params, returnType, language, body, options) {
|
||
|
|
throwMethodUndefined('createFunction');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Drop a function
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
dropFunction: function(functionName, params) {
|
||
|
|
throwMethodUndefined('dropFunction');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Rename a function
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
renameFunction: function(oldFunctionName, params, newFunctionName) {
|
||
|
|
throwMethodUndefined('renameFunction');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Escape an identifier (e.g. a table or attribute name)
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
quoteIdentifier: function(identifier, force) {
|
||
|
|
throwMethodUndefined('quoteIdentifier');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Split an identifier into .-separated tokens and quote each part
|
||
|
|
*/
|
||
|
|
quoteIdentifiers: function(identifiers, force) {
|
||
|
|
if (identifiers.indexOf('.') !== -1) {
|
||
|
|
identifiers = identifiers.split('.');
|
||
|
|
return this.quoteIdentifier(identifiers.slice(0, identifiers.length - 1).join('.')) + '.' + this.quoteIdentifier(identifiers[identifiers.length - 1]);
|
||
|
|
} else {
|
||
|
|
return this.quoteIdentifier(identifiers);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Escape a value (e.g. a string, number or date)
|
||
|
|
*/
|
||
|
|
escape: function(value, field) {
|
||
|
|
if (value && value._isSequelizeMethod) {
|
||
|
|
return this.handleSequelizeMethod(value);
|
||
|
|
} else {
|
||
|
|
return SqlString.escape(value, false, this.options.timezone, this.dialect, field);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generates an SQL query that returns all foreign keys of a table.
|
||
|
|
*
|
||
|
|
* @param {String} tableName The name of the table.
|
||
|
|
* @param {String} schemaName The name of the schema.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
getForeignKeysQuery: function(tableName, schemaName) {
|
||
|
|
throwMethodUndefined('getForeignKeysQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generates an SQL query that removes a foreign key from a table.
|
||
|
|
*
|
||
|
|
* @param {String} tableName The name of the table.
|
||
|
|
* @param {String} foreignKey The name of the foreign key constraint.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
/* istanbul ignore next */
|
||
|
|
dropForeignKeyQuery: function(tableName, foreignKey) {
|
||
|
|
throwMethodUndefined('dropForeignKeyQuery');
|
||
|
|
},
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Returns a query for selecting elements in the table <tableName>.
|
||
|
|
Options:
|
||
|
|
- attributes -> An array of attributes (e.g. ['name', 'birthday']). Default: *
|
||
|
|
- where -> A hash with conditions (e.g. {name: 'foo'})
|
||
|
|
OR an ID as integer
|
||
|
|
OR a string with conditions (e.g. 'name="foo"').
|
||
|
|
If you use a string, you have to escape it on your own.
|
||
|
|
- order -> e.g. 'id DESC'
|
||
|
|
- group
|
||
|
|
- limit -> The maximum count you want to get.
|
||
|
|
- offset -> An offset value to start from. Only useable with limit!
|
||
|
|
*/
|
||
|
|
|
||
|
|
selectQuery: function(tableName, options, model) {
|
||
|
|
// Enter and change at your own peril -- Mick Hansen
|
||
|
|
|
||
|
|
options = options || {};
|
||
|
|
|
||
|
|
var table = null
|
||
|
|
, self = this
|
||
|
|
, query
|
||
|
|
, limit = options.limit
|
||
|
|
, mainModel = model
|
||
|
|
, mainQueryItems = []
|
||
|
|
, mainAttributes = options.attributes && options.attributes.slice(0)
|
||
|
|
, mainJoinQueries = []
|
||
|
|
// We'll use a subquery if we have a hasMany association and a limit
|
||
|
|
, subQuery = options.subQuery === undefined ?
|
||
|
|
limit && options.hasMultiAssociation :
|
||
|
|
options.subQuery
|
||
|
|
, subQueryItems = []
|
||
|
|
, subQueryAttributes = null
|
||
|
|
, subJoinQueries = []
|
||
|
|
, mainTableAs = null;
|
||
|
|
|
||
|
|
if (options.tableAs) {
|
||
|
|
mainTableAs = this.quoteTable(options.tableAs);
|
||
|
|
} else if (!Array.isArray(tableName) && model) {
|
||
|
|
options.tableAs = mainTableAs = this.quoteTable(model.name);
|
||
|
|
}
|
||
|
|
|
||
|
|
options.table = table = !Array.isArray(tableName) ? this.quoteTable(tableName) : tableName.map(function(t) {
|
||
|
|
if (Array.isArray(t)) {
|
||
|
|
return this.quoteTable(t[0], t[1]);
|
||
|
|
}
|
||
|
|
return this.quoteTable(t, true);
|
||
|
|
}.bind(this)).join(', ');
|
||
|
|
|
||
|
|
if (subQuery && mainAttributes) {
|
||
|
|
model.primaryKeyAttributes.forEach(function(keyAtt) {
|
||
|
|
// Check if mainAttributes contain the primary key of the model either as a field or an aliased field
|
||
|
|
if (!_.find(mainAttributes, function (attr) {
|
||
|
|
return keyAtt === attr || keyAtt === attr[0] || keyAtt === attr[1];
|
||
|
|
})) {
|
||
|
|
mainAttributes.push(model.rawAttributes[keyAtt].field ? [keyAtt, model.rawAttributes[keyAtt].field] : keyAtt);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Escape attributes
|
||
|
|
mainAttributes = mainAttributes && mainAttributes.map(function(attr) {
|
||
|
|
var addTable = true;
|
||
|
|
|
||
|
|
if (attr._isSequelizeMethod) {
|
||
|
|
return self.handleSequelizeMethod(attr);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Array.isArray(attr) && attr.length === 2) {
|
||
|
|
if (attr[0]._isSequelizeMethod) {
|
||
|
|
attr[0] = self.handleSequelizeMethod(attr[0]);
|
||
|
|
addTable = false;
|
||
|
|
} else {
|
||
|
|
if (attr[0].indexOf('(') === -1 && attr[0].indexOf(')') === -1) {
|
||
|
|
attr[0] = self.quoteIdentifier(attr[0]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
attr = [attr[0], self.quoteIdentifier(attr[1])].join(' AS ');
|
||
|
|
} else {
|
||
|
|
attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? self.quoteIdentifiers(attr) : attr;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.include && attr.indexOf('.') === -1 && addTable) {
|
||
|
|
attr = mainTableAs + '.' + attr;
|
||
|
|
}
|
||
|
|
return attr;
|
||
|
|
});
|
||
|
|
|
||
|
|
// If no attributes specified, use *
|
||
|
|
mainAttributes = mainAttributes || (options.include ? [mainTableAs + '.*'] : ['*']);
|
||
|
|
|
||
|
|
// If subquery, we ad the mainAttributes to the subQuery and set the mainAttributes to select * from subquery
|
||
|
|
if (subQuery) {
|
||
|
|
// We need primary keys
|
||
|
|
subQueryAttributes = mainAttributes;
|
||
|
|
mainAttributes = [mainTableAs + '.*'];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.include) {
|
||
|
|
var generateJoinQueries = function(include, parentTable) {
|
||
|
|
var table = include.model.getTableName()
|
||
|
|
, as = include.as
|
||
|
|
, joinQueryItem = ''
|
||
|
|
, joinQueries = {
|
||
|
|
mainQuery: [],
|
||
|
|
subQuery: []
|
||
|
|
}
|
||
|
|
, attributes
|
||
|
|
, association = include.association
|
||
|
|
, through = include.through
|
||
|
|
, joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN '
|
||
|
|
, whereOptions = Utils._.clone(options)
|
||
|
|
, targetWhere;
|
||
|
|
|
||
|
|
whereOptions.keysEscaped = true;
|
||
|
|
|
||
|
|
if (tableName !== parentTable && mainTableAs !== parentTable) {
|
||
|
|
as = parentTable + '.' + include.as;
|
||
|
|
}
|
||
|
|
|
||
|
|
// includeIgnoreAttributes is used by aggregate functions
|
||
|
|
if (options.includeIgnoreAttributes !== false) {
|
||
|
|
|
||
|
|
attributes = include.attributes.map(function(attr) {
|
||
|
|
var attrAs = attr,
|
||
|
|
verbatim = false;
|
||
|
|
|
||
|
|
if (Array.isArray(attr) && attr.length === 2) {
|
||
|
|
if (attr[0]._isSequelizeMethod) {
|
||
|
|
if (attr[0] instanceof Utils.literal ||
|
||
|
|
attr[0] instanceof Utils.cast ||
|
||
|
|
attr[0] instanceof Utils.fn
|
||
|
|
) {
|
||
|
|
verbatim = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
attr = attr.map(function($attr) {
|
||
|
|
return $attr._isSequelizeMethod ? self.handleSequelizeMethod($attr) : $attr;
|
||
|
|
});
|
||
|
|
|
||
|
|
attrAs = attr[1];
|
||
|
|
attr = attr[0];
|
||
|
|
} else if (attr instanceof Utils.literal) {
|
||
|
|
return attr.val; // We trust the user to rename the field correctly
|
||
|
|
} else if (attr instanceof Utils.cast ||
|
||
|
|
attr instanceof Utils.fn
|
||
|
|
) {
|
||
|
|
throw new Error(
|
||
|
|
'Tried to select attributes using Sequelize.cast or Sequelize.fn without specifying an alias for the result, during eager loading. ' +
|
||
|
|
'This means the attribute will not be added to the returned instance'
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
var prefix;
|
||
|
|
if (verbatim === true) {
|
||
|
|
prefix = attr;
|
||
|
|
} else {
|
||
|
|
prefix = self.quoteIdentifier(as) + '.' + self.quoteIdentifier(attr);
|
||
|
|
}
|
||
|
|
return prefix + ' AS ' + self.quoteIdentifier(as + '.' + attrAs, true);
|
||
|
|
});
|
||
|
|
if (include.subQuery && subQuery) {
|
||
|
|
subQueryAttributes = subQueryAttributes.concat(attributes);
|
||
|
|
} else {
|
||
|
|
mainAttributes = mainAttributes.concat(attributes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (through) {
|
||
|
|
var throughTable = through.model.getTableName()
|
||
|
|
, throughAs = as + '.' + through.as
|
||
|
|
, throughAttributes = through.attributes.map(function(attr) {
|
||
|
|
return self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(Array.isArray(attr) ? attr[0] : attr) +
|
||
|
|
' AS ' +
|
||
|
|
self.quoteIdentifier(throughAs + '.' + (Array.isArray(attr) ? attr[1] : attr));
|
||
|
|
})
|
||
|
|
, primaryKeysSource = association.source.primaryKeyAttributes
|
||
|
|
, tableSource = parentTable
|
||
|
|
, identSource = association.identifierField
|
||
|
|
, attrSource = primaryKeysSource[0]
|
||
|
|
, primaryKeysTarget = association.target.primaryKeyAttributes
|
||
|
|
, tableTarget = as
|
||
|
|
, identTarget = association.foreignIdentifierField
|
||
|
|
, attrTarget = association.target.rawAttributes[primaryKeysTarget[0]].field || primaryKeysTarget[0]
|
||
|
|
|
||
|
|
, sourceJoinOn
|
||
|
|
, targetJoinOn
|
||
|
|
|
||
|
|
, throughWhere;
|
||
|
|
|
||
|
|
if (options.includeIgnoreAttributes !== false) {
|
||
|
|
// Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery)
|
||
|
|
mainAttributes = mainAttributes.concat(throughAttributes);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Figure out if we need to use field or attribute
|
||
|
|
if (!subQuery) {
|
||
|
|
attrSource = association.source.rawAttributes[primaryKeysSource[0]].field;
|
||
|
|
}
|
||
|
|
if (subQuery && !include.subQuery && !include.parent.subQuery && include.parent.model !== mainModel) {
|
||
|
|
attrSource = association.source.rawAttributes[primaryKeysSource[0]].field;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Filter statement for left side of through
|
||
|
|
// Used by both join and subquery where
|
||
|
|
|
||
|
|
// If parent include was in a subquery need to join on the aliased attribute
|
||
|
|
if (subQuery && !include.subQuery && include.parent.subQuery) {
|
||
|
|
sourceJoinOn = self.quoteIdentifier(tableSource + '.' + attrSource) + ' = ';
|
||
|
|
} else {
|
||
|
|
sourceJoinOn = self.quoteTable(tableSource) + '.' + self.quoteIdentifier(attrSource) + ' = ';
|
||
|
|
}
|
||
|
|
sourceJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identSource);
|
||
|
|
|
||
|
|
// Filter statement for right side of through
|
||
|
|
// Used by both join and subquery where
|
||
|
|
targetJoinOn = self.quoteIdentifier(tableTarget) + '.' + self.quoteIdentifier(attrTarget) + ' = ';
|
||
|
|
targetJoinOn += self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identTarget);
|
||
|
|
|
||
|
|
if (include.through.where) {
|
||
|
|
throughWhere = self.getWhereConditions(include.through.where, self.sequelize.literal(self.quoteIdentifier(throughAs)), include.through.model);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (self._dialect.supports.joinTableDependent) {
|
||
|
|
// Generate a wrapped join so that the through table join can be dependent on the target join
|
||
|
|
joinQueryItem += joinType + '(';
|
||
|
|
joinQueryItem += self.quoteTable(throughTable, throughAs);
|
||
|
|
joinQueryItem += ' INNER JOIN ' + self.quoteTable(table, as) + ' ON ';
|
||
|
|
joinQueryItem += targetJoinOn;
|
||
|
|
|
||
|
|
if (throughWhere) {
|
||
|
|
joinQueryItem += ' AND ' + throughWhere;
|
||
|
|
}
|
||
|
|
|
||
|
|
joinQueryItem += ') ON '+sourceJoinOn;
|
||
|
|
} else {
|
||
|
|
// Generate join SQL for left side of through
|
||
|
|
joinQueryItem += joinType + self.quoteTable(throughTable, throughAs) + ' ON ';
|
||
|
|
joinQueryItem += sourceJoinOn;
|
||
|
|
|
||
|
|
// Generate join SQL for right side of through
|
||
|
|
joinQueryItem += joinType + self.quoteTable(table, as) + ' ON ';
|
||
|
|
joinQueryItem += targetJoinOn;
|
||
|
|
|
||
|
|
if (throughWhere) {
|
||
|
|
joinQueryItem += ' AND ' + throughWhere;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
if (include.where || include.through.where) {
|
||
|
|
if (include.where) {
|
||
|
|
targetWhere = self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.model, whereOptions);
|
||
|
|
if (targetWhere) {
|
||
|
|
joinQueryItem += ' AND ' + targetWhere;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (subQuery && include.required) {
|
||
|
|
if (!options.where) options.where = {};
|
||
|
|
(function (include) {
|
||
|
|
// Closure to use sane local variables
|
||
|
|
|
||
|
|
var parent = include
|
||
|
|
, child = include
|
||
|
|
, nestedIncludes = []
|
||
|
|
, topParent
|
||
|
|
, topInclude
|
||
|
|
, $query;
|
||
|
|
|
||
|
|
while (parent = parent.parent) {
|
||
|
|
nestedIncludes = [_.extend({}, child, {include: nestedIncludes})];
|
||
|
|
child = parent;
|
||
|
|
}
|
||
|
|
|
||
|
|
topInclude = nestedIncludes[0];
|
||
|
|
topParent = topInclude.parent;
|
||
|
|
|
||
|
|
if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
|
||
|
|
$query = self.selectQuery(topInclude.through.model.getTableName(), {
|
||
|
|
attributes: [topInclude.through.model.primaryKeyAttributes[0]],
|
||
|
|
include: [{
|
||
|
|
model: topInclude.model,
|
||
|
|
as: topInclude.model.name,
|
||
|
|
attributes: [],
|
||
|
|
association: {
|
||
|
|
associationType: 'BelongsTo',
|
||
|
|
isSingleAssociation: true,
|
||
|
|
source: topInclude.association.target,
|
||
|
|
target: topInclude.association.source,
|
||
|
|
identifier: topInclude.association.foreignIdentifier,
|
||
|
|
identifierField: topInclude.association.foreignIdentifierField
|
||
|
|
},
|
||
|
|
required: true,
|
||
|
|
include: topInclude.include,
|
||
|
|
_pseudo: true
|
||
|
|
}],
|
||
|
|
where: self.sequelize.and(
|
||
|
|
self.sequelize.asIs([
|
||
|
|
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyAttributes[0]),
|
||
|
|
self.quoteIdentifier(topInclude.through.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField)
|
||
|
|
].join(' = ')),
|
||
|
|
topInclude.through.where
|
||
|
|
),
|
||
|
|
limit: 1,
|
||
|
|
includeIgnoreAttributes: false
|
||
|
|
}, topInclude.through.model);
|
||
|
|
} else {
|
||
|
|
$query = self.selectQuery(topInclude.model.tableName, {
|
||
|
|
attributes: [topInclude.model.primaryKeyAttributes[0]],
|
||
|
|
include: topInclude.include,
|
||
|
|
where: {
|
||
|
|
$join: self.sequelize.asIs([
|
||
|
|
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyAttributes[0]),
|
||
|
|
self.quoteIdentifier(topInclude.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField)
|
||
|
|
].join(' = '))
|
||
|
|
},
|
||
|
|
limit: 1,
|
||
|
|
includeIgnoreAttributes: false
|
||
|
|
}, topInclude.model);
|
||
|
|
}
|
||
|
|
|
||
|
|
options.where['__' + throughAs] = self.sequelize.asIs([
|
||
|
|
'(',
|
||
|
|
$query.replace(/\;$/, ''),
|
||
|
|
')',
|
||
|
|
'IS NOT NULL'
|
||
|
|
].join(' '));
|
||
|
|
})(include);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
var left = association.source
|
||
|
|
, right = association.target
|
||
|
|
, primaryKeysLeft = left.primaryKeyAttributes
|
||
|
|
, primaryKeysRight = right.primaryKeyAttributes
|
||
|
|
, tableLeft = parentTable
|
||
|
|
, attrLeft = association.associationType === 'BelongsTo' ?
|
||
|
|
association.identifierField || association.identifier :
|
||
|
|
primaryKeysLeft[0]
|
||
|
|
|
||
|
|
, tableRight = as
|
||
|
|
, attrRight = association.associationType !== 'BelongsTo' ?
|
||
|
|
association.identifierField || association.identifier :
|
||
|
|
right.rawAttributes[association.targetIdentifier || primaryKeysRight[0]].field
|
||
|
|
, joinOn
|
||
|
|
, subQueryJoinOn;
|
||
|
|
|
||
|
|
// Filter statement
|
||
|
|
// Used by both join and where
|
||
|
|
if (subQuery && !include.subQuery && include.parent.subQuery && (include.hasParentRequired || include.hasParentWhere || include.parent.hasIncludeRequired || include.parent.hasIncludeWhere)) {
|
||
|
|
joinOn = self.quoteIdentifier(tableLeft + '.' + attrLeft);
|
||
|
|
} else {
|
||
|
|
if (association.associationType !== 'BelongsTo') {
|
||
|
|
// Alias the left attribute if the left attribute is not from a subqueried main table
|
||
|
|
// When doing a query like SELECT aliasedKey FROM (SELECT primaryKey FROM primaryTable) only aliasedKey is available to the join, this is not the case when doing a regular select where you can't used the aliased attribute
|
||
|
|
if (!subQuery || (subQuery && include.parent.model !== mainModel)) {
|
||
|
|
if (left.rawAttributes[attrLeft].field) {
|
||
|
|
attrLeft = left.rawAttributes[attrLeft].field;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
joinOn = self.quoteTable(tableLeft) + '.' + self.quoteIdentifier(attrLeft);
|
||
|
|
}
|
||
|
|
subQueryJoinOn = self.quoteTable(tableLeft) + '.' + self.quoteIdentifier(attrLeft);
|
||
|
|
|
||
|
|
joinOn += ' = ' + self.quoteTable(tableRight) + '.' + self.quoteIdentifier(attrRight);
|
||
|
|
subQueryJoinOn += ' = ' + self.quoteTable(tableRight) + '.' + self.quoteIdentifier(attrRight);
|
||
|
|
|
||
|
|
if (include.where) {
|
||
|
|
targetWhere = self.getWhereConditions(include.where, self.sequelize.literal(self.quoteIdentifier(as)), include.model, whereOptions);
|
||
|
|
if (targetWhere) {
|
||
|
|
joinOn += ' AND ' + targetWhere;
|
||
|
|
subQueryJoinOn += ' AND ' + targetWhere;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If its a multi association and the main query is a subquery (because of limit) we need to filter based on this association in a subquery
|
||
|
|
if (subQuery && association.isMultiAssociation && include.required) {
|
||
|
|
if (!options.where) options.where = {};
|
||
|
|
// Creating the as-is where for the subQuery, checks that the required association exists
|
||
|
|
var $query = self.selectQuery(include.model.getTableName(), {
|
||
|
|
tableAs: as,
|
||
|
|
attributes: [attrRight],
|
||
|
|
where: self.sequelize.asIs(subQueryJoinOn ? [subQueryJoinOn] : [joinOn]),
|
||
|
|
limit: 1
|
||
|
|
}, include.model);
|
||
|
|
|
||
|
|
var subQueryWhere = self.sequelize.asIs([
|
||
|
|
'(',
|
||
|
|
$query.replace(/\;$/, ''),
|
||
|
|
')',
|
||
|
|
'IS NOT NULL'
|
||
|
|
].join(' '));
|
||
|
|
|
||
|
|
if (options.where instanceof Utils.and) {
|
||
|
|
options.where.args.push(subQueryWhere);
|
||
|
|
} else if (Utils._.isPlainObject(options.where)) {
|
||
|
|
options.where['__' + as] = subQueryWhere;
|
||
|
|
} else {
|
||
|
|
options.where = { $and: [options.where, subQueryWhere] };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Generate join SQL
|
||
|
|
joinQueryItem += joinType + self.quoteTable(table, as) + ' ON ' + joinOn;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (include.subQuery && subQuery) {
|
||
|
|
joinQueries.subQuery.push(joinQueryItem);
|
||
|
|
} else {
|
||
|
|
joinQueries.mainQuery.push(joinQueryItem);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (include.include) {
|
||
|
|
include.include.forEach(function(childInclude) {
|
||
|
|
if (childInclude._pseudo) return;
|
||
|
|
var childJoinQueries = generateJoinQueries(childInclude, as);
|
||
|
|
|
||
|
|
if (childInclude.subQuery && subQuery) {
|
||
|
|
joinQueries.subQuery = joinQueries.subQuery.concat(childJoinQueries.subQuery);
|
||
|
|
}
|
||
|
|
if (childJoinQueries.mainQuery) {
|
||
|
|
joinQueries.mainQuery = joinQueries.mainQuery.concat(childJoinQueries.mainQuery);
|
||
|
|
}
|
||
|
|
|
||
|
|
}.bind(this));
|
||
|
|
}
|
||
|
|
|
||
|
|
return joinQueries;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Loop through includes and generate subqueries
|
||
|
|
options.include.forEach(function(include) {
|
||
|
|
var joinQueries = generateJoinQueries(include, options.tableAs);
|
||
|
|
|
||
|
|
subJoinQueries = subJoinQueries.concat(joinQueries.subQuery);
|
||
|
|
mainJoinQueries = mainJoinQueries.concat(joinQueries.mainQuery);
|
||
|
|
|
||
|
|
}.bind(this));
|
||
|
|
}
|
||
|
|
|
||
|
|
// If using subQuery select defined subQuery attributes and join subJoinQueries
|
||
|
|
if (subQuery) {
|
||
|
|
subQueryItems.push('SELECT ' + subQueryAttributes.join(', ') + ' FROM ' + options.table);
|
||
|
|
if (mainTableAs) {
|
||
|
|
subQueryItems.push(' AS ' + mainTableAs);
|
||
|
|
}
|
||
|
|
subQueryItems.push(subJoinQueries.join(''));
|
||
|
|
|
||
|
|
// Else do it the reguar way
|
||
|
|
} else {
|
||
|
|
mainQueryItems.push('SELECT ' + mainAttributes.join(', ') + ' FROM ' + options.table);
|
||
|
|
if (mainTableAs) {
|
||
|
|
mainQueryItems.push(' AS ' + mainTableAs);
|
||
|
|
}
|
||
|
|
mainQueryItems.push(mainJoinQueries.join(''));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add WHERE to sub or main query
|
||
|
|
if (options.hasOwnProperty('where')) {
|
||
|
|
options.where = this.getWhereConditions(options.where, mainTableAs || tableName, model, options);
|
||
|
|
if (options.where) {
|
||
|
|
if (subQuery) {
|
||
|
|
subQueryItems.push(' WHERE ' + options.where);
|
||
|
|
} else {
|
||
|
|
mainQueryItems.push(' WHERE ' + options.where);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add GROUP BY to sub or main query
|
||
|
|
if (options.group) {
|
||
|
|
options.group = Array.isArray(options.group) ? options.group.map(function(t) { return this.quote(t, model); }.bind(this)).join(', ') : options.group;
|
||
|
|
if (subQuery) {
|
||
|
|
subQueryItems.push(' GROUP BY ' + options.group);
|
||
|
|
} else {
|
||
|
|
mainQueryItems.push(' GROUP BY ' + options.group);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add HAVING to sub or main query
|
||
|
|
if (options.hasOwnProperty('having')) {
|
||
|
|
options.having = this.getWhereConditions(options.having, tableName, model, options, false);
|
||
|
|
if (subQuery) {
|
||
|
|
subQueryItems.push(' HAVING ' + options.having);
|
||
|
|
} else {
|
||
|
|
mainQueryItems.push(' HAVING ' + options.having);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Add ORDER to sub or main query
|
||
|
|
if (options.order) {
|
||
|
|
var mainQueryOrder = [];
|
||
|
|
var subQueryOrder = [];
|
||
|
|
|
||
|
|
var validateOrder = function(order) {
|
||
|
|
if (order instanceof Utils.literal) return;
|
||
|
|
|
||
|
|
if (!_.contains([
|
||
|
|
'ASC',
|
||
|
|
'DESC',
|
||
|
|
'ASC NULLS LAST',
|
||
|
|
'DESC NULLS LAST',
|
||
|
|
'ASC NULLS FIRST',
|
||
|
|
'DESC NULLS FIRST',
|
||
|
|
'NULLS FIRST',
|
||
|
|
'NULLS LAST'
|
||
|
|
], order.toUpperCase())) {
|
||
|
|
throw new Error(util.format('Order must be \'ASC\' or \'DESC\', \'%s\' given', order));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (Array.isArray(options.order)) {
|
||
|
|
options.order.forEach(function(t) {
|
||
|
|
if (Array.isArray(t) && _.size(t) > 1) {
|
||
|
|
if (t[0] instanceof Model || t[0].model instanceof Model) {
|
||
|
|
if (typeof t[t.length - 2] === 'string') {
|
||
|
|
validateOrder(_.last(t));
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
validateOrder(_.last(t));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (subQuery && (Array.isArray(t) && !(t[0] instanceof Model) && !(t[0].model instanceof Model))) {
|
||
|
|
subQueryOrder.push(this.quote(t, model));
|
||
|
|
}
|
||
|
|
|
||
|
|
mainQueryOrder.push(this.quote(t, model));
|
||
|
|
}.bind(this));
|
||
|
|
} else {
|
||
|
|
mainQueryOrder.push(this.quote(typeof options.order === 'string' ? new Utils.literal(options.order) : options.order, model));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mainQueryOrder.length) {
|
||
|
|
mainQueryItems.push(' ORDER BY ' + mainQueryOrder.join(', '));
|
||
|
|
}
|
||
|
|
if (subQueryOrder.length) {
|
||
|
|
subQueryItems.push(' ORDER BY ' + subQueryOrder.join(', '));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add LIMIT, OFFSET to sub or main query
|
||
|
|
var limitOrder = this.addLimitAndOffset(options, model);
|
||
|
|
if (limitOrder) {
|
||
|
|
if (subQuery) {
|
||
|
|
subQueryItems.push(limitOrder);
|
||
|
|
} else {
|
||
|
|
mainQueryItems.push(limitOrder);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If using subQuery, select attributes from wrapped subQuery and join out join tables
|
||
|
|
if (subQuery) {
|
||
|
|
query = 'SELECT ' + mainAttributes.join(', ') + ' FROM (';
|
||
|
|
query += subQueryItems.join('');
|
||
|
|
query += ') AS ' + options.tableAs;
|
||
|
|
query += mainJoinQueries.join('');
|
||
|
|
query += mainQueryItems.join('');
|
||
|
|
} else {
|
||
|
|
query = mainQueryItems.join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.lock && this._dialect.supports.lock) {
|
||
|
|
var lock = options.lock;
|
||
|
|
if (typeof options.lock === 'object') {
|
||
|
|
lock = options.lock.level;
|
||
|
|
}
|
||
|
|
if (this._dialect.supports.lockKey && (lock === 'KEY SHARE' || lock === 'NO KEY UPDATE')) {
|
||
|
|
query += ' FOR ' + lock;
|
||
|
|
} else if (lock === 'SHARE') {
|
||
|
|
query += ' ' + this._dialect.supports.forShare;
|
||
|
|
} else {
|
||
|
|
query += ' FOR UPDATE';
|
||
|
|
}
|
||
|
|
if (this._dialect.supports.lockOf && options.lock.of instanceof Model) {
|
||
|
|
query += ' OF ' + this.quoteTable(options.lock.of.name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
query += ';';
|
||
|
|
|
||
|
|
return query;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a query that starts a transaction.
|
||
|
|
*
|
||
|
|
* @param {Boolean} value A boolean that states whether autocommit shall be done or not.
|
||
|
|
* @param {Object} options An object with options.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
setAutocommitQuery: function(value, options) {
|
||
|
|
if (options.parent) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'SET autocommit = ' + (!!value ? 1 : 0) + ';';
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a query that sets the transaction isolation level.
|
||
|
|
*
|
||
|
|
* @param {String} value The isolation level.
|
||
|
|
* @param {Object} options An object with options.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
setIsolationLevelQuery: function(value, options) {
|
||
|
|
if (options.parent) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'SET SESSION TRANSACTION ISOLATION LEVEL ' + value + ';';
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a query that starts a transaction.
|
||
|
|
*
|
||
|
|
* @param {Transaction} transaction
|
||
|
|
* @param {Object} options An object with options.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
startTransactionQuery: function(transaction, options) {
|
||
|
|
if (options.parent) {
|
||
|
|
return 'SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'START TRANSACTION;';
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a query that defers the constraints. Only works for postgres.
|
||
|
|
*
|
||
|
|
* @param {Transaction} transaction
|
||
|
|
* @param {Object} options An object with options.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
deferConstraintsQuery: function () {},
|
||
|
|
|
||
|
|
setConstraintQuery: function () {},
|
||
|
|
setDeferredQuery: function () {},
|
||
|
|
setImmediateQuery: function () {},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a query that commits a transaction.
|
||
|
|
*
|
||
|
|
* @param {Object} options An object with options.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
commitTransactionQuery: function(options) {
|
||
|
|
if (options.parent) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'COMMIT;';
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a query that rollbacks a transaction.
|
||
|
|
*
|
||
|
|
* @param {Transaction} transaction
|
||
|
|
* @param {Object} options An object with options.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
rollbackTransactionQuery: function(transaction, options) {
|
||
|
|
if (options.parent) {
|
||
|
|
return 'ROLLBACK TO SAVEPOINT ' + this.quoteIdentifier(transaction.name) + ';';
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'ROLLBACK;';
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns an SQL fragment for adding result constraints
|
||
|
|
*
|
||
|
|
* @param {Object} options An object with selectQuery options.
|
||
|
|
* @param {Object} options The model passed to the selectQuery.
|
||
|
|
* @return {String} The generated sql query.
|
||
|
|
*/
|
||
|
|
addLimitAndOffset: function(options, model) {
|
||
|
|
var fragment = '';
|
||
|
|
if (options.offset && !options.limit) {
|
||
|
|
fragment += ' LIMIT ' + options.offset + ', ' + 18440000000000000000;
|
||
|
|
} else if (options.limit) {
|
||
|
|
if (options.offset) {
|
||
|
|
fragment += ' LIMIT ' + options.offset + ', ' + options.limit;
|
||
|
|
} else {
|
||
|
|
fragment += ' LIMIT ' + options.limit;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return fragment;
|
||
|
|
},
|
||
|
|
|
||
|
|
handleSequelizeMethod: function (smth, tableName, factory, options, prepend) {
|
||
|
|
var self = this
|
||
|
|
, result;
|
||
|
|
|
||
|
|
if ((smth instanceof Utils.and) || (smth instanceof Utils.or)) {
|
||
|
|
var connector = (smth instanceof Utils.and) ? ' AND ' : ' OR ';
|
||
|
|
|
||
|
|
result = smth.args.filter(function(arg) {
|
||
|
|
return arg !== undefined;
|
||
|
|
}).map(function(arg) {
|
||
|
|
return self.getWhereConditions(arg, tableName, factory, options, prepend);
|
||
|
|
}).join(connector);
|
||
|
|
|
||
|
|
result = result.length && '(' + result + ')' || undefined;
|
||
|
|
} else if (smth instanceof Utils.where) {
|
||
|
|
var value = smth.logic
|
||
|
|
, key;
|
||
|
|
|
||
|
|
if (smth.attribute._isSequelizeMethod) {
|
||
|
|
key = this.getWhereConditions(smth.attribute, tableName, factory, options, prepend);
|
||
|
|
} else {
|
||
|
|
key = this.quoteTable(smth.attribute.Model.name) + '.' + this.quoteIdentifier(smth.attribute.field || smth.attribute.fieldName);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (value._isSequelizeMethod) {
|
||
|
|
value = this.getWhereConditions(value, tableName, factory, options, prepend);
|
||
|
|
|
||
|
|
result = (value === 'NULL') ? key + ' IS NULL' : [key, value].join(smth.comparator);
|
||
|
|
} else if (_.isPlainObject(value)) {
|
||
|
|
result = this.whereItemQuery(smth.attribute, value, {
|
||
|
|
model: factory
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
if (typeof value === 'boolean') {
|
||
|
|
value = this.booleanValue(value);
|
||
|
|
} else {
|
||
|
|
value = this.escape(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
result = (value === 'NULL') ? key + ' IS NULL' : [key, value].join(' ' + smth.comparator + ' ');
|
||
|
|
}
|
||
|
|
} else if (smth instanceof Utils.literal) {
|
||
|
|
result = smth.val;
|
||
|
|
} else if (smth instanceof Utils.cast) {
|
||
|
|
if (smth.val._isSequelizeMethod) {
|
||
|
|
result = this.handleSequelizeMethod(smth.val, tableName, factory, options, prepend);
|
||
|
|
} else {
|
||
|
|
result = this.escape(smth.val);
|
||
|
|
}
|
||
|
|
|
||
|
|
result = 'CAST(' + result + ' AS ' + smth.type.toUpperCase() + ')';
|
||
|
|
} else if (smth instanceof Utils.fn) {
|
||
|
|
result = smth.fn + '(' + smth.args.map(function(arg) {
|
||
|
|
if (arg._isSequelizeMethod) {
|
||
|
|
return self.handleSequelizeMethod(arg, tableName, factory, options, prepend);
|
||
|
|
} else {
|
||
|
|
return self.escape(arg);
|
||
|
|
}
|
||
|
|
}).join(', ') + ')';
|
||
|
|
} else if (smth instanceof Utils.col) {
|
||
|
|
if (Array.isArray(smth.col)) {
|
||
|
|
if (!factory) {
|
||
|
|
throw new Error('Cannot call Sequelize.col() with array outside of order / group clause');
|
||
|
|
}
|
||
|
|
} else if (smth.col.indexOf('*') === 0) {
|
||
|
|
return '*';
|
||
|
|
}
|
||
|
|
return this.quote(smth.col, factory);
|
||
|
|
} else {
|
||
|
|
result = smth.toString(this, factory);
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
},
|
||
|
|
|
||
|
|
whereQuery: function(where, options) {
|
||
|
|
var query = this.whereItemsQuery(where, options);
|
||
|
|
if (query && query.length) {
|
||
|
|
return 'WHERE '+query;
|
||
|
|
}
|
||
|
|
return '';
|
||
|
|
},
|
||
|
|
whereItemsQuery: function(where, options, binding) {
|
||
|
|
if (
|
||
|
|
(Array.isArray(where) && where.length === 0) ||
|
||
|
|
(_.isPlainObject(where) && _.isEmpty(where)) ||
|
||
|
|
where === null ||
|
||
|
|
where === undefined
|
||
|
|
) {
|
||
|
|
// NO OP
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_.isString(where)) {
|
||
|
|
throw new Error('where: "raw query" has been removed, please use where ["raw query", [replacements]]');
|
||
|
|
}
|
||
|
|
|
||
|
|
var self = this
|
||
|
|
, items = [];
|
||
|
|
|
||
|
|
binding = binding || 'AND';
|
||
|
|
if (binding.substr(0, 1) !== ' ') binding = ' '+binding+' ';
|
||
|
|
|
||
|
|
if (_.isPlainObject(where)) {
|
||
|
|
_.forOwn(where, function (value, key) {
|
||
|
|
items.push(self.whereItemQuery(key, value, options));
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
items.push(self.whereItemQuery(undefined, where, options));
|
||
|
|
}
|
||
|
|
|
||
|
|
return items.length && items.filter(function (item) {
|
||
|
|
return item && item.length;
|
||
|
|
}).join(binding) || '';
|
||
|
|
},
|
||
|
|
whereItemQuery: function(key, value, options) {
|
||
|
|
options = options || {};
|
||
|
|
|
||
|
|
var self = this
|
||
|
|
, binding
|
||
|
|
, outerBinding
|
||
|
|
, comparatorMap
|
||
|
|
, aliasMap
|
||
|
|
, comparator = '='
|
||
|
|
, field = options.field || options.model && options.model.rawAttributes && options.model.rawAttributes[key] || options.model && options.model.fieldRawAttributesMap && options.model.fieldRawAttributesMap[key]
|
||
|
|
, fieldType = options.type || (field && field.type)
|
||
|
|
, tmp;
|
||
|
|
|
||
|
|
if (key && typeof key === 'string' && key.indexOf('.') !== -1 && options.model) {
|
||
|
|
if (options.model.rawAttributes[key.split('.')[0]] && options.model.rawAttributes[key.split('.')[0]].type instanceof DataTypes.JSON) {
|
||
|
|
field = options.model.rawAttributes[key.split('.')[0]];
|
||
|
|
fieldType = field.type;
|
||
|
|
tmp = value;
|
||
|
|
value = {};
|
||
|
|
|
||
|
|
Dottie.set(value, key.split('.').slice(1), tmp);
|
||
|
|
key = key.split('.')[0];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
comparatorMap = {
|
||
|
|
$eq: '=',
|
||
|
|
$ne: '!=',
|
||
|
|
$gte: '>=',
|
||
|
|
$gt: '>',
|
||
|
|
$lte: '<=',
|
||
|
|
$lt: '<',
|
||
|
|
$not: 'IS NOT',
|
||
|
|
$is: 'IS',
|
||
|
|
$like: 'LIKE',
|
||
|
|
$notLike: 'NOT LIKE',
|
||
|
|
$iLike: 'ILIKE',
|
||
|
|
$notILike: 'NOT ILIKE',
|
||
|
|
$between: 'BETWEEN',
|
||
|
|
$notBetween: 'NOT BETWEEN',
|
||
|
|
$overlap: '&&',
|
||
|
|
$contains: '@>',
|
||
|
|
$contained: '<@'
|
||
|
|
};
|
||
|
|
|
||
|
|
// Maintain BC
|
||
|
|
aliasMap = {
|
||
|
|
'ne': '$ne',
|
||
|
|
'in': '$in',
|
||
|
|
'not': '$not',
|
||
|
|
'notIn': '$notIn',
|
||
|
|
'gte': '$gte',
|
||
|
|
'gt': '$gt',
|
||
|
|
'lte': '$lte',
|
||
|
|
'lt': '$lt',
|
||
|
|
'like': '$like',
|
||
|
|
'ilike': '$iLike',
|
||
|
|
'$ilike': '$iLike',
|
||
|
|
'nlike': '$notLike',
|
||
|
|
'$notlike': '$notLike',
|
||
|
|
'notilike': '$notILike',
|
||
|
|
'..': '$between',
|
||
|
|
'between': '$between',
|
||
|
|
'!..': '$notBetween',
|
||
|
|
'notbetween': '$notBetween',
|
||
|
|
'nbetween': '$notBetween',
|
||
|
|
'overlap': '$overlap',
|
||
|
|
'&&': '$overlap',
|
||
|
|
'@>': '$contains',
|
||
|
|
'<@': '$contained'
|
||
|
|
};
|
||
|
|
|
||
|
|
key = aliasMap[key] || key;
|
||
|
|
if (_.isPlainObject(value)) {
|
||
|
|
_.forOwn(value, function (item, key) {
|
||
|
|
if (aliasMap[key]) {
|
||
|
|
value[aliasMap[key]] = item;
|
||
|
|
delete value[key];
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (key === undefined) {
|
||
|
|
if (value instanceof Utils.or || value instanceof Utils.and) {
|
||
|
|
key = value instanceof Utils.or ? '$or' : '$and';
|
||
|
|
value = value.args;
|
||
|
|
} else if (typeof value === 'string') {
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (value && value._isSequelizeMethod && !(key !== undefined && value instanceof Utils.fn)) {
|
||
|
|
return this.handleSequelizeMethod(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Convert where: [] to $and if possible, else treat as literal/replacements
|
||
|
|
if (key === undefined && Array.isArray(value)) {
|
||
|
|
if (Utils.canTreatArrayAsAnd(value)) {
|
||
|
|
key = '$and';
|
||
|
|
} else {
|
||
|
|
return Utils.format(value, this.dialect);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// OR/AND/NOT grouping logic
|
||
|
|
if (key === '$or' || key === '$and' || key === '$not') {
|
||
|
|
|
||
|
|
binding = (key === '$or') ?' OR ' : ' AND ';
|
||
|
|
|
||
|
|
outerBinding = '';
|
||
|
|
if (key === '$not') outerBinding = 'NOT ';
|
||
|
|
|
||
|
|
if (Array.isArray(value)) {
|
||
|
|
value = value.map(function (item) {
|
||
|
|
var itemQuery = self.whereItemsQuery(item, options, ' AND ');
|
||
|
|
if ((Array.isArray(item) || _.isPlainObject(item)) &&_.size(item) > 1) {
|
||
|
|
itemQuery = '('+itemQuery+')';
|
||
|
|
}
|
||
|
|
return itemQuery;
|
||
|
|
}).filter(function (item) {
|
||
|
|
return item && item.length;
|
||
|
|
});
|
||
|
|
|
||
|
|
return value.length ? outerBinding + '('+value.join(binding)+')' : undefined;
|
||
|
|
} else {
|
||
|
|
value = self.whereItemsQuery(value, options, binding);
|
||
|
|
return value ? outerBinding + '('+value+')' : undefined;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (value && (value.$or || value.$and)) {
|
||
|
|
binding = value.$or ? ' OR ' : ' AND ';
|
||
|
|
value = value.$or || value.$and;
|
||
|
|
|
||
|
|
if (_.isPlainObject(value)) {
|
||
|
|
value = _.reduce(value, function (result, _value, key) {
|
||
|
|
result.push(_.zipObject([key], [_value]));
|
||
|
|
return result;
|
||
|
|
}, []);
|
||
|
|
}
|
||
|
|
|
||
|
|
value = value.map(function (_value) {
|
||
|
|
return self.whereItemQuery(key, _value, options);
|
||
|
|
}).filter(function (item) {
|
||
|
|
return item && item.length;
|
||
|
|
});
|
||
|
|
|
||
|
|
return value.length ? '('+value.join(binding)+')' : undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_.isPlainObject(value) && fieldType instanceof DataTypes.JSON && options.json !== false) {
|
||
|
|
return (function () {
|
||
|
|
var $items = []
|
||
|
|
, result
|
||
|
|
, traverse;
|
||
|
|
|
||
|
|
traverse = function (prop, item, path) {
|
||
|
|
var $where = {}
|
||
|
|
, $key
|
||
|
|
, $cast
|
||
|
|
, $baseKey
|
||
|
|
, $tmp;
|
||
|
|
|
||
|
|
if (path[path.length - 1].indexOf('::') > -1) {
|
||
|
|
$tmp = path[path.length - 1].split('::');
|
||
|
|
$cast = $tmp[1];
|
||
|
|
path[path.length - 1] = $tmp[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
$baseKey = self.quoteIdentifier(key)+'#>>\'{'+path.join(', ')+'}\'';
|
||
|
|
|
||
|
|
if (options.prefix) {
|
||
|
|
if (options.prefix instanceof Utils.literal) {
|
||
|
|
$baseKey = self.handleSequelizeMethod(options.prefix)+'.'+$baseKey;
|
||
|
|
} else {
|
||
|
|
$baseKey = self.quoteTable(options.prefix)+'.'+$baseKey;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$baseKey = '('+$baseKey+')';
|
||
|
|
|
||
|
|
if (_.isPlainObject(item)) {
|
||
|
|
_.forOwn(item, function ($item, $prop) {
|
||
|
|
if ($prop.indexOf('$') === 0) {
|
||
|
|
$where[$prop] = $item;
|
||
|
|
$key = $baseKey;
|
||
|
|
|
||
|
|
if (!$cast) {
|
||
|
|
if (typeof $item === 'number') {
|
||
|
|
$cast = 'double precision';
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($item instanceof Date) {
|
||
|
|
$cast = 'timestamptz';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($cast) {
|
||
|
|
$key += '::'+$cast;
|
||
|
|
}
|
||
|
|
|
||
|
|
$items.push(self.whereItemQuery(new Utils.literal($key), $where/*, _.pick(options, 'prefix')*/));
|
||
|
|
} else {
|
||
|
|
traverse($prop, $item, path.concat([$prop]));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
$where.$eq = item;
|
||
|
|
|
||
|
|
$key = new Utils.literal($baseKey);
|
||
|
|
$items.push(self.whereItemQuery($key, $where/*, _.pick(options, 'prefix')*/));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
_.forOwn(value, function (item, prop) {
|
||
|
|
if (prop.indexOf('$') === 0) {
|
||
|
|
var $where = {};
|
||
|
|
$where[prop] = item;
|
||
|
|
$items.push(self.whereItemQuery(key, $where, _.assign({}, options, {json: false})));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
traverse(prop, item, [prop]);
|
||
|
|
});
|
||
|
|
|
||
|
|
result = $items.join(' AND ');
|
||
|
|
return $items.length > 1 ? '('+result+')' : result;
|
||
|
|
})();
|
||
|
|
}
|
||
|
|
|
||
|
|
// If multiple keys we combine the different logic conditions
|
||
|
|
if (_.isPlainObject(value) && Object.keys(value).length > 1) {
|
||
|
|
return (function () {
|
||
|
|
var $items = [];
|
||
|
|
_.forOwn(value, function (item, logic) {
|
||
|
|
var $where = {};
|
||
|
|
$where[logic] = item;
|
||
|
|
$items.push(self.whereItemQuery(key, $where, options));
|
||
|
|
});
|
||
|
|
|
||
|
|
return '('+$items.join(' AND ')+')';
|
||
|
|
})();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Do [] to $in/$notIn normalization
|
||
|
|
if (value && (!fieldType || !(fieldType instanceof DataTypes.ARRAY))) {
|
||
|
|
if (Array.isArray(value)) {
|
||
|
|
value = {
|
||
|
|
$in: value
|
||
|
|
};
|
||
|
|
} else if (value && Array.isArray(value.$not)) {
|
||
|
|
value.$notIn = value.$not;
|
||
|
|
delete value.$not;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// normalize $not: non-bool|non-null to $ne
|
||
|
|
if (value && typeof value.$not !== 'undefined' && [null, true, false].indexOf(value.$not) < 0) {
|
||
|
|
value.$ne = value.$not;
|
||
|
|
delete value.$not;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Setup keys and comparators
|
||
|
|
if (Array.isArray(value) && fieldType instanceof DataTypes.ARRAY) {
|
||
|
|
value = this.escape(value, field);
|
||
|
|
} else if (value && (value.$in || value.$notIn)) {
|
||
|
|
comparator = 'IN';
|
||
|
|
if (value.$notIn) comparator = 'NOT IN';
|
||
|
|
|
||
|
|
if ((value.$in || value.$notIn) instanceof Utils.literal) {
|
||
|
|
value = (value.$in || value.$notIn).val;
|
||
|
|
} else if ((value.$in || value.$notIn).length) {
|
||
|
|
value = '('+(value.$in || value.$notIn).map(function (item) {
|
||
|
|
return self.escape(item);
|
||
|
|
}).join(', ')+')';
|
||
|
|
} else {
|
||
|
|
value = '(NULL)';
|
||
|
|
}
|
||
|
|
} else if (value && (value.$any || value.$all)) {
|
||
|
|
comparator = value.$any ? '= ANY' : '= ALL';
|
||
|
|
if (value.$any && value.$any.$values || value.$all && value.$all.$values) {
|
||
|
|
value = '(VALUES '+(value.$any && value.$any.$values || value.$all && value.$all.$values).map(function (value) {
|
||
|
|
return '('+this.escape(value)+')';
|
||
|
|
}.bind(this)).join(', ')+')';
|
||
|
|
} else {
|
||
|
|
value = '('+this.escape(value.$any || value.$all, field)+')';
|
||
|
|
}
|
||
|
|
} else if (value && (value.$between || value.$notBetween)) {
|
||
|
|
comparator = 'BETWEEN';
|
||
|
|
if (value.$notBetween) comparator = 'NOT BETWEEN';
|
||
|
|
|
||
|
|
value = (value.$between || value.$notBetween).map(function (item) {
|
||
|
|
return self.escape(item);
|
||
|
|
}).join(' AND ');
|
||
|
|
} else {
|
||
|
|
if (_.isPlainObject(value)) {
|
||
|
|
_.forOwn(value, function (item, key) {
|
||
|
|
if (comparatorMap[key]) {
|
||
|
|
comparator = comparatorMap[key];
|
||
|
|
value = item;
|
||
|
|
if (_.isPlainObject(value) && value.$any){
|
||
|
|
comparator += ' ANY';
|
||
|
|
value = value.$any;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (comparator === '=' && value === null) {
|
||
|
|
comparator = 'IS';
|
||
|
|
} else if (comparator === '!=' && value === null) {
|
||
|
|
comparator = 'IS NOT';
|
||
|
|
}
|
||
|
|
|
||
|
|
value = this.escape(value, field);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (key._isSequelizeMethod) {
|
||
|
|
key = this.handleSequelizeMethod(key);
|
||
|
|
} else {
|
||
|
|
key = this.quoteIdentifier(key);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.prefix) {
|
||
|
|
if (options.prefix instanceof Utils.literal) {
|
||
|
|
key = [this.handleSequelizeMethod(options.prefix), key].join('.');
|
||
|
|
} else {
|
||
|
|
key = [this.quoteTable(options.prefix), key].join('.');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return [key, value].join(' '+comparator+' ');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*
|
||
|
|
Takes something and transforms it into values of a where condition.
|
||
|
|
*/
|
||
|
|
getWhereConditions: function(smth, tableName, factory, options, prepend) {
|
||
|
|
var result = null
|
||
|
|
, where = {}
|
||
|
|
, self = this;
|
||
|
|
|
||
|
|
if (Array.isArray(tableName)) {
|
||
|
|
tableName = tableName[0];
|
||
|
|
if (Array.isArray(tableName)) {
|
||
|
|
tableName = tableName[1];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
options = options || {};
|
||
|
|
|
||
|
|
if (typeof prepend === 'undefined') {
|
||
|
|
prepend = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (smth && smth._isSequelizeMethod === true) { // Checking a property is cheaper than a lot of instanceof calls
|
||
|
|
if (smth instanceof Utils.and || smth instanceof Utils.and) {
|
||
|
|
return self.whereItemsQuery(smth, {
|
||
|
|
model: factory,
|
||
|
|
prefix: prepend && tableName
|
||
|
|
});
|
||
|
|
}
|
||
|
|
result = this.handleSequelizeMethod(smth, tableName, factory, options, prepend);
|
||
|
|
} else if (Utils._.isPlainObject(smth)) {
|
||
|
|
return self.whereItemsQuery(smth, {
|
||
|
|
model: factory,
|
||
|
|
prefix: prepend && tableName
|
||
|
|
});
|
||
|
|
} else if (typeof smth === 'number') {
|
||
|
|
var primaryKeys = !!factory ? Object.keys(factory.primaryKeys) : [];
|
||
|
|
|
||
|
|
if (primaryKeys.length > 0) {
|
||
|
|
// Since we're just a number, assume only the first key
|
||
|
|
primaryKeys = primaryKeys[0];
|
||
|
|
} else {
|
||
|
|
primaryKeys = 'id';
|
||
|
|
}
|
||
|
|
|
||
|
|
where[primaryKeys] = smth;
|
||
|
|
|
||
|
|
return self.whereItemsQuery(where, {
|
||
|
|
model: factory,
|
||
|
|
prefix: prepend && tableName
|
||
|
|
});
|
||
|
|
} else if (typeof smth === 'string') {
|
||
|
|
return self.whereItemsQuery(smth, {
|
||
|
|
model: factory,
|
||
|
|
prefix: prepend && tableName
|
||
|
|
});
|
||
|
|
} else if (Buffer.isBuffer(smth)) {
|
||
|
|
result = this.escape(smth);
|
||
|
|
} else if (Array.isArray(smth)) {
|
||
|
|
if (smth.length === 0) return '1=1';
|
||
|
|
if (Utils.canTreatArrayAsAnd(smth)) {
|
||
|
|
var _smth = self.sequelize.and.apply(null, smth);
|
||
|
|
result = self.getWhereConditions(_smth, tableName, factory, options, prepend);
|
||
|
|
} else {
|
||
|
|
result = Utils.format(smth, this.dialect);
|
||
|
|
}
|
||
|
|
} else if (smth === null) {
|
||
|
|
return self.whereItemsQuery(smth, {
|
||
|
|
model: factory,
|
||
|
|
prefix: prepend && tableName
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return result ? result : '1=1';
|
||
|
|
},
|
||
|
|
|
||
|
|
booleanValue: function(value) {
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
module.exports = QueryGenerator;
|