377 lines
12 KiB
JavaScript
377 lines
12 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
var Utils = require('../../utils')
|
||
|
|
, DataTypes = require('../../data-types');
|
||
|
|
|
||
|
|
var QueryGenerator = {
|
||
|
|
dialect: 'mysql',
|
||
|
|
|
||
|
|
createSchema: function() {
|
||
|
|
var query = 'SHOW TABLES';
|
||
|
|
return Utils._.template(query)({});
|
||
|
|
},
|
||
|
|
|
||
|
|
showSchemasQuery: function() {
|
||
|
|
return 'SHOW TABLES';
|
||
|
|
},
|
||
|
|
|
||
|
|
versionQuery: function() {
|
||
|
|
return 'SELECT VERSION() as `version`';
|
||
|
|
},
|
||
|
|
|
||
|
|
createTableQuery: function(tableName, attributes, options) {
|
||
|
|
options = Utils._.extend({
|
||
|
|
engine: 'InnoDB',
|
||
|
|
charset: null
|
||
|
|
}, options || {});
|
||
|
|
|
||
|
|
var self = this;
|
||
|
|
|
||
|
|
var query = 'CREATE TABLE IF NOT EXISTS <%= table %> (<%= attributes%>) ENGINE=<%= engine %><%= comment %><%= charset %><%= collation %><%= initialAutoIncrement %>'
|
||
|
|
, primaryKeys = []
|
||
|
|
, foreignKeys = {}
|
||
|
|
, attrStr = [];
|
||
|
|
|
||
|
|
for (var attr in attributes) {
|
||
|
|
if (attributes.hasOwnProperty(attr)) {
|
||
|
|
var dataType = attributes[attr]
|
||
|
|
, match;
|
||
|
|
|
||
|
|
if (Utils._.includes(dataType, 'PRIMARY KEY')) {
|
||
|
|
primaryKeys.push(attr);
|
||
|
|
|
||
|
|
if (Utils._.includes(dataType, 'REFERENCES')) {
|
||
|
|
// MySQL doesn't support inline REFERENCES declarations: move to the end
|
||
|
|
match = dataType.match(/^(.+) (REFERENCES.*)$/);
|
||
|
|
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1].replace(/PRIMARY KEY/, ''));
|
||
|
|
foreignKeys[attr] = match[2];
|
||
|
|
} else {
|
||
|
|
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType.replace(/PRIMARY KEY/, ''));
|
||
|
|
}
|
||
|
|
} else if (Utils._.includes(dataType, 'REFERENCES')) {
|
||
|
|
// MySQL doesn't support inline REFERENCES declarations: move to the end
|
||
|
|
match = dataType.match(/^(.+) (REFERENCES.*)$/);
|
||
|
|
attrStr.push(this.quoteIdentifier(attr) + ' ' + match[1]);
|
||
|
|
foreignKeys[attr] = match[2];
|
||
|
|
} else {
|
||
|
|
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var values = {
|
||
|
|
table: this.quoteTable(tableName),
|
||
|
|
attributes: attrStr.join(', '),
|
||
|
|
comment: options.comment && Utils._.isString(options.comment) ? ' COMMENT ' + this.escape(options.comment) : '',
|
||
|
|
engine: options.engine,
|
||
|
|
charset: (options.charset ? ' DEFAULT CHARSET=' + options.charset : ''),
|
||
|
|
collation: (options.collate ? ' COLLATE ' + options.collate : ''),
|
||
|
|
initialAutoIncrement: (options.initialAutoIncrement ? ' AUTO_INCREMENT=' + options.initialAutoIncrement : '')
|
||
|
|
}
|
||
|
|
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
|
||
|
|
|
||
|
|
if (!!options.uniqueKeys) {
|
||
|
|
Utils._.each(options.uniqueKeys, function(columns, indexName) {
|
||
|
|
if (!columns.singleField) { // If it's a single field its handled in column def, not as an index
|
||
|
|
if (!Utils._.isString(indexName)) {
|
||
|
|
indexName = 'uniq_' + tableName + '_' + columns.fields.join('_');
|
||
|
|
}
|
||
|
|
values.attributes += ', UNIQUE ' + self.quoteIdentifier(indexName) + ' (' + Utils._.map(columns.fields, self.quoteIdentifier).join(', ') + ')';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pkString.length > 0) {
|
||
|
|
values.attributes += ', PRIMARY KEY (' + pkString + ')';
|
||
|
|
}
|
||
|
|
|
||
|
|
for (var fkey in foreignKeys) {
|
||
|
|
if (foreignKeys.hasOwnProperty(fkey)) {
|
||
|
|
values.attributes += ', FOREIGN KEY (' + this.quoteIdentifier(fkey) + ') ' + foreignKeys[fkey];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return Utils._.template(query)(values).trim() + ';';
|
||
|
|
},
|
||
|
|
|
||
|
|
showTablesQuery: function() {
|
||
|
|
return 'SHOW TABLES;';
|
||
|
|
},
|
||
|
|
|
||
|
|
addColumnQuery: function(table, key, dataType) {
|
||
|
|
var query = 'ALTER TABLE <%= table %> ADD <%= attribute %>;'
|
||
|
|
, attribute = Utils._.template('<%= key %> <%= definition %>')({
|
||
|
|
key: this.quoteIdentifier(key),
|
||
|
|
definition: this.attributeToSQL(dataType, {
|
||
|
|
context: 'addColumn'
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
return Utils._.template(query)({
|
||
|
|
table: this.quoteTable(table),
|
||
|
|
attribute: attribute
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
removeColumnQuery: function(tableName, attributeName) {
|
||
|
|
var query = 'ALTER TABLE <%= tableName %> DROP <%= attributeName %>;';
|
||
|
|
return Utils._.template(query)({
|
||
|
|
tableName: this.quoteTable(tableName),
|
||
|
|
attributeName: this.quoteIdentifier(attributeName)
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
changeColumnQuery: function(tableName, attributes) {
|
||
|
|
var query = 'ALTER TABLE <%= tableName %> CHANGE <%= attributes %>;';
|
||
|
|
var attrString = [];
|
||
|
|
|
||
|
|
for (var attrName in attributes) {
|
||
|
|
var definition = attributes[attrName];
|
||
|
|
|
||
|
|
attrString.push(Utils._.template('`<%= attrName %>` `<%= attrName %>` <%= definition %>')({
|
||
|
|
attrName: attrName,
|
||
|
|
definition: definition
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributes: attrString.join(', ') });
|
||
|
|
},
|
||
|
|
|
||
|
|
renameColumnQuery: function(tableName, attrBefore, attributes) {
|
||
|
|
var query = 'ALTER TABLE <%= tableName %> CHANGE <%= attributes %>;';
|
||
|
|
var attrString = [];
|
||
|
|
|
||
|
|
for (var attrName in attributes) {
|
||
|
|
var definition = attributes[attrName];
|
||
|
|
|
||
|
|
attrString.push(Utils._.template('`<%= before %>` `<%= after %>` <%= definition %>')({
|
||
|
|
before: attrBefore,
|
||
|
|
after: attrName,
|
||
|
|
definition: definition
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
return Utils._.template(query)({ tableName: this.quoteTable(tableName), attributes: attrString.join(', ') });
|
||
|
|
},
|
||
|
|
|
||
|
|
upsertQuery: function (tableName, insertValues, updateValues, where, rawAttributes, options) {
|
||
|
|
options.onDuplicate = 'UPDATE ';
|
||
|
|
|
||
|
|
options.onDuplicate += Object.keys(updateValues).map(function (key) {
|
||
|
|
key = this.quoteIdentifier(key);
|
||
|
|
return key + '=VALUES(' + key +')';
|
||
|
|
}, this).join(', ');
|
||
|
|
|
||
|
|
return this.insertQuery(tableName, insertValues, rawAttributes, options);
|
||
|
|
},
|
||
|
|
|
||
|
|
bulkInsertQuery: function(tableName, attrValueHashes, options) {
|
||
|
|
var query = 'INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %><%= onDuplicateKeyUpdate %>;'
|
||
|
|
, tuples = []
|
||
|
|
, allAttributes = []
|
||
|
|
, onDuplicateKeyUpdate = '';
|
||
|
|
|
||
|
|
Utils._.forEach(attrValueHashes, function(attrValueHash) {
|
||
|
|
Utils._.forOwn(attrValueHash, function(value, key) {
|
||
|
|
if (allAttributes.indexOf(key) === -1) allAttributes.push(key);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
Utils._.forEach(attrValueHashes, function(attrValueHash) {
|
||
|
|
tuples.push('(' +
|
||
|
|
allAttributes.map(function(key) {
|
||
|
|
return this.escape(attrValueHash[key]);
|
||
|
|
}.bind(this)).join(',') +
|
||
|
|
')');
|
||
|
|
}.bind(this));
|
||
|
|
|
||
|
|
if (options && options.updateOnDuplicate) {
|
||
|
|
onDuplicateKeyUpdate += ' ON DUPLICATE KEY UPDATE ' + options.updateOnDuplicate.map(function(attr) {
|
||
|
|
var key = this.quoteIdentifier(attr);
|
||
|
|
return key + '=VALUES(' + key + ')';
|
||
|
|
}.bind(this)).join(',');
|
||
|
|
}
|
||
|
|
|
||
|
|
var replacements = {
|
||
|
|
ignoreDuplicates: options && options.ignoreDuplicates ? ' IGNORE' : '',
|
||
|
|
table: this.quoteTable(tableName),
|
||
|
|
attributes: allAttributes.map(function(attr) {
|
||
|
|
return this.quoteIdentifier(attr);
|
||
|
|
}.bind(this)).join(','),
|
||
|
|
tuples: tuples,
|
||
|
|
onDuplicateKeyUpdate: onDuplicateKeyUpdate
|
||
|
|
};
|
||
|
|
|
||
|
|
return Utils._.template(query)(replacements);
|
||
|
|
},
|
||
|
|
|
||
|
|
deleteQuery: function(tableName, where, options) {
|
||
|
|
options = options || {};
|
||
|
|
|
||
|
|
var table = this.quoteTable(tableName);
|
||
|
|
if (options.truncate === true) {
|
||
|
|
// Truncate does not allow LIMIT and WHERE
|
||
|
|
return 'TRUNCATE ' + table;
|
||
|
|
}
|
||
|
|
|
||
|
|
where = this.getWhereConditions(where);
|
||
|
|
var limit = '';
|
||
|
|
|
||
|
|
if (Utils._.isUndefined(options.limit)) {
|
||
|
|
options.limit = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!!options.limit) {
|
||
|
|
limit = ' LIMIT ' + this.escape(options.limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
var query = 'DELETE FROM ' + table;
|
||
|
|
if (where) query += ' WHERE ' + where;
|
||
|
|
query += limit;
|
||
|
|
|
||
|
|
return query;
|
||
|
|
},
|
||
|
|
|
||
|
|
showIndexesQuery: function(tableName, options) {
|
||
|
|
var sql = 'SHOW INDEX FROM <%= tableName %><%= options %>';
|
||
|
|
return Utils._.template(sql)({
|
||
|
|
tableName: this.quoteTable(tableName),
|
||
|
|
options: (options || {}).database ? ' FROM `' + options.database + '`' : ''
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
removeIndexQuery: function(tableName, indexNameOrAttributes) {
|
||
|
|
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
|
||
|
|
, indexName = indexNameOrAttributes;
|
||
|
|
|
||
|
|
if (typeof indexName !== 'string') {
|
||
|
|
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
|
||
|
|
}
|
||
|
|
|
||
|
|
return Utils._.template(sql)({ tableName: this.quoteIdentifiers(tableName), indexName: indexName });
|
||
|
|
},
|
||
|
|
|
||
|
|
attributeToSQL: function(attribute) {
|
||
|
|
if (!Utils._.isPlainObject(attribute)) {
|
||
|
|
attribute = {
|
||
|
|
type: attribute
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
var template;
|
||
|
|
|
||
|
|
if (attribute.type instanceof DataTypes.ENUM) {
|
||
|
|
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
|
||
|
|
template = 'ENUM(' + Utils._.map(attribute.values, function(value) {
|
||
|
|
return this.escape(value);
|
||
|
|
}.bind(this)).join(', ') + ')';
|
||
|
|
} else {
|
||
|
|
template = attribute.type.toString();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.allowNull === false) {
|
||
|
|
template += ' NOT NULL';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.autoIncrement) {
|
||
|
|
template += ' auto_increment';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Blobs/texts cannot have a defaultValue
|
||
|
|
if (attribute.type !== 'TEXT' && attribute.type._binary !== true && Utils.defaultValueSchemable(attribute.defaultValue)) {
|
||
|
|
template += ' DEFAULT ' + this.escape(attribute.defaultValue);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.unique === true) {
|
||
|
|
template += ' UNIQUE';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.primaryKey) {
|
||
|
|
template += ' PRIMARY KEY';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.after) {
|
||
|
|
template += ' AFTER ' + this.quoteIdentifier(attribute.after);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.references) {
|
||
|
|
attribute = Utils.formatReferences(attribute);
|
||
|
|
template += ' REFERENCES ' + this.quoteTable(attribute.references.model);
|
||
|
|
|
||
|
|
if (attribute.references.key) {
|
||
|
|
template += ' (' + this.quoteIdentifier(attribute.references.key) + ')';
|
||
|
|
} else {
|
||
|
|
template += ' (' + this.quoteIdentifier('id') + ')';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.onDelete) {
|
||
|
|
template += ' ON DELETE ' + attribute.onDelete.toUpperCase();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attribute.onUpdate) {
|
||
|
|
template += ' ON UPDATE ' + attribute.onUpdate.toUpperCase();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return template;
|
||
|
|
},
|
||
|
|
|
||
|
|
attributesToSQL: function(attributes, options) {
|
||
|
|
var result = {}
|
||
|
|
, key
|
||
|
|
, attribute;
|
||
|
|
|
||
|
|
for (key in attributes) {
|
||
|
|
attribute = attributes[key];
|
||
|
|
result[attribute.field || key] = this.attributeToSQL(attribute, options);
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
},
|
||
|
|
|
||
|
|
findAutoIncrementField: function(factory) {
|
||
|
|
var fields = [];
|
||
|
|
|
||
|
|
for (var name in factory.attributes) {
|
||
|
|
if (factory.attributes.hasOwnProperty(name)) {
|
||
|
|
var definition = factory.attributes[name];
|
||
|
|
|
||
|
|
if (definition && definition.autoIncrement) {
|
||
|
|
fields.push(name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return fields;
|
||
|
|
},
|
||
|
|
|
||
|
|
quoteIdentifier: function(identifier) {
|
||
|
|
if (identifier === '*') return identifier;
|
||
|
|
return Utils.addTicks(identifier, '`');
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* 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.
|
||
|
|
*/
|
||
|
|
getForeignKeysQuery: function(tableName, schemaName) {
|
||
|
|
return "SELECT CONSTRAINT_NAME as constraint_name FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE where TABLE_NAME = '" + tableName + /* jshint ignore: line */
|
||
|
|
"' AND CONSTRAINT_NAME!='PRIMARY' AND CONSTRAINT_SCHEMA='" + schemaName + "' AND REFERENCED_TABLE_NAME IS NOT NULL;"; /* jshint ignore: line */
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 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.
|
||
|
|
*/
|
||
|
|
dropForeignKeyQuery: function(tableName, foreignKey) {
|
||
|
|
return 'ALTER TABLE ' + this.quoteTable(tableName) + ' DROP FOREIGN KEY ' + this.quoteIdentifier(foreignKey) + ';';
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
module.exports = Utils._.extend(Utils._.clone(require('../abstract/query-generator')), QueryGenerator);
|