BE-SPAN/node_modules/sequelize-oracle/lib/dialects/mssql/query-generator.js

610 lines
18 KiB
JavaScript
Raw Permalink Normal View History

2024-10-20 15:04:16 +00:00
'use strict';
/* jshint -W110 */
var Utils = require('../../utils')
, DataTypes = require('../../data-types')
, Model = require('../../model')
, AbstractQueryGenerator = require('../abstract/query-generator');
/* 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: {},
dialect: 'mssql',
createSchema: function(schema) {
return [
'IF NOT EXISTS (SELECT schema_name',
'FROM information_schema.schemata',
'WHERE schema_name =', wrapSingleQuote(schema), ')',
'BEGIN',
"EXEC sp_executesql N'CREATE SCHEMA",
this.quoteIdentifier(schema),
";'",
'END;'
].join(' ');
},
showSchemasQuery: function() {
return [
'SELECT "name" as "schema_name" FROM sys.schemas as s',
'WHERE "s"."name" NOT IN (',
"'INFORMATION_SCHEMA', 'dbo', 'guest', 'sys', 'archive'",
')', 'AND', '"s"."name" NOT LIKE', "'db_%'"
].join(' ');
},
versionQuery: function() {
return "SELECT @@VERSION as 'version'";
},
createTableQuery: function(tableName, attributes, options) {
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NULL CREATE TABLE <%= table %> (<%= attributes %>)"
, primaryKeys = []
, foreignKeys = {}
, attrStr = []
, self = this;
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')) {
// MSSQL 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')) {
// MSSQL 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(', '),
}
, pkString = primaryKeys.map(function(pk) { return this.quoteIdentifier(pk); }.bind(this)).join(', ');
if (!!options.uniqueKeys) {
Utils._.each(options.uniqueKeys, function(columns, indexName) {
if (!Utils._.isString(indexName)) {
indexName = 'uniq_' + tableName + '_' + columns.fields.join('_');
}
values.attributes += ', CONSTRAINT ' + self.quoteIdentifier(indexName) + ' UNIQUE (' + 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() + ';';
},
describeTableQuery: function(tableName, schema) {
var sql = [
'SELECT',
"c.COLUMN_NAME AS 'Name',",
"c.DATA_TYPE AS 'Type',",
"c.IS_NULLABLE as 'IsNull',",
"COLUMN_DEFAULT AS 'Default'",
'FROM',
'INFORMATION_SCHEMA.TABLES t',
'INNER JOIN',
'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME',
'WHERE t.TABLE_NAME =', wrapSingleQuote(tableName)
].join(' ');
if (schema) {
sql += 'AND t.TABLE_SCHEMA =' + wrapSingleQuote(schema);
}
return sql;
},
renameTableQuery: function(before, after) {
var query = 'EXEC sp_rename <%= before %>, <%= after %>;';
return Utils._.template(query)({
before: this.quoteTable(before),
after: this.quoteTable(after)
});
},
showTablesQuery: function () {
return 'SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES;';
},
dropTableQuery: function(tableName) {
var query = "IF OBJECT_ID('<%= table %>', 'U') IS NOT NULL DROP TABLE <%= table %>";
var values = {
table: this.quoteTable(tableName)
};
return Utils._.template(query)(values).trim() + ';';
},
addColumnQuery: function(table, key, dataType) {
// FIXME: attributeToSQL SHOULD be using attributes in addColumnQuery
// but instead we need to pass the key along as the field here
dataType.field = key;
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 %> ALTER COLUMN <%= attributes %>;';
var attrString = [];
for (var attrName in attributes) {
var definition = attributes[attrName];
attrString.push(Utils._.template('<%= attrName %> <%= definition %>')({
attrName: this.quoteIdentifier(attrName),
definition: definition
}));
}
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributes: attrString.join(', ')
});
},
renameColumnQuery: function(tableName, attrBefore, attributes) {
var query = "EXEC sp_rename '<%= tableName %>.<%= before %>', '<%= after %>', 'COLUMN';"
, newName = Object.keys(attributes)[0];
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
before: attrBefore,
after: newName
});
},
bulkInsertQuery: function(tableName, attrValueHashes, options, attributes) {
var query = 'INSERT INTO <%= table %> (<%= attributes %>)<%= output %> VALUES <%= tuples %>;'
, emptyQuery = 'INSERT INTO <%= table %><%= output %> DEFAULT VALUES'
, tuples = []
, allAttributes = []
, needIdentityInsertWrapper = false
, allQueries = []
, outputFragment;
if (options.returning) {
outputFragment = ' OUTPUT INSERTED.*';
}
Utils._.forEach(attrValueHashes, function(attrValueHash) {
// special case for empty objects with primary keys
var fields = Object.keys(attrValueHash);
if (fields.length === 1 && attributes[fields[0]].autoIncrement && attrValueHash[fields[0]] === null) {
allQueries.push(emptyQuery);
return;
}
// normal case
Utils._.forOwn(attrValueHash, function(value, key) {
if (value !== null && attributes[key].autoIncrement) {
needIdentityInsertWrapper = true;
}
if (allAttributes.indexOf(key) === -1) {
if (value === null && attributes[key].autoIncrement)
return;
allAttributes.push(key);
}
});
});
if (allAttributes.length > 0) {
Utils._.forEach(attrValueHashes, function(attrValueHash) {
tuples.push('(' +
allAttributes.map(function(key) {
return this.escape(attrValueHash[key]);
}.bind(this)).join(',') +
')');
}.bind(this));
allQueries.push(query);
}
var replacements = {
table: this.quoteTable(tableName),
attributes: allAttributes.map(function(attr) {
return this.quoteIdentifier(attr);
}.bind(this)).join(','),
tuples: tuples,
output: outputFragment
};
var generatedQuery = Utils._.template(allQueries.join(';'))(replacements);
if (needIdentityInsertWrapper) {
generatedQuery = [
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'ON;',
generatedQuery,
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'OFF;',
].join(' ');
}
return generatedQuery;
},
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 ' + table;
}
where = this.getWhereConditions(where);
var limit = ''
, query = 'DELETE<%= limit %> FROM <%= table %><%= where %>; ' +
'SELECT @@ROWCOUNT AS AFFECTEDROWS;';
if (Utils._.isUndefined(options.limit)) {
options.limit = 1;
}
if (!!options.limit) {
limit = ' TOP(' + this.escape(options.limit) + ')';
}
var replacements = {
limit: limit,
table: table,
where: where,
};
if (replacements.where) {
replacements.where = ' WHERE ' + replacements.where;
}
return Utils._.template(query)(replacements);
},
showIndexesQuery: function(tableName) {
var sql = "EXEC sys.sp_helpindex @objname = N'<%= tableName %>';";
return Utils._.template(sql)({
tableName: this.quoteTable(tableName)
});
},
removeIndexQuery: function(tableName, indexNameOrAttributes) {
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
, indexName = indexNameOrAttributes;
if (typeof indexName !== 'string') {
indexName = Utils.inflection.underscore(tableName + '_' + indexNameOrAttributes.join('_'));
}
var values = {
tableName: this.quoteIdentifiers(tableName),
indexName: indexName
};
return Utils._.template(sql)(values);
},
attributeToSQL: function(attribute) {
if (!Utils._.isPlainObject(attribute)) {
attribute = {
type: attribute
};
}
// handle self referential constraints
if (attribute.references) {
attribute = Utils.formatReferences(attribute);
if (attribute.Model && attribute.Model.tableName === attribute.references.model) {
this.sequelize.log('MSSQL does not support self referencial constraints, '
+ 'we will remove it but we recommend restructuring your query');
attribute.onDelete = '';
attribute.onUpdate = '';
}
}
var template;
if (attribute.type instanceof DataTypes.ENUM) {
if (attribute.type.values && !attribute.values) attribute.values = attribute.type.values;
// enums are a special case
template = attribute.type.toSql();
template += ' CHECK (' + attribute.field + ' IN(' + Utils._.map(attribute.values, function(value) {
return this.escape(value);
}.bind(this)).join(', ') + '))';
return template;
} else {
template = attribute.type.toString();
}
if (attribute.allowNull === false) {
template += ' NOT NULL';
} else if (!attribute.primaryKey && !Utils.defaultValueSchemable(attribute.defaultValue)) {
template += ' NULL';
}
if (attribute.autoIncrement) {
template += ' IDENTITY(1,1)';
}
// 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.references) {
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
, existingConstraints = [];
for (key in attributes) {
attribute = attributes[key];
if (attribute.references) {
attribute = Utils.formatReferences(attributes[key]);
if (existingConstraints.indexOf(attribute.references.model.toString()) !== -1) {
// no cascading constraints to a table more than once
attribute.onDelete = '';
attribute.onUpdate = '';
} else {
existingConstraints.push(attribute.references.model.toString());
// NOTE: this really just disables cascading updates for all
// definitions. Can be made more robust to support the
// few cases where MSSQL actually supports them
attribute.onUpdate = '';
}
}
if (key && !attribute.field) attribute.field = 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;
},
createTrigger: function() {
throwMethodUndefined('createTrigger');
},
dropTrigger: function() {
throwMethodUndefined('dropTrigger');
},
renameTrigger: function() {
throwMethodUndefined('renameTrigger');
},
createFunction: function() {
throwMethodUndefined('createFunction');
},
dropFunction: function() {
throwMethodUndefined('dropFunction');
},
renameFunction: function() {
throwMethodUndefined('renameFunction');
},
quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier;
return '[' + identifier.replace(/[\[\]']+/g,'') + ']';
},
getForeignKeysQuery: function(table, databaseName) {
var tableName = table.tableName || table;
var sql = [
'SELECT',
'constraint_name = C.CONSTRAINT_NAME',
'FROM',
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS C',
"WHERE C.CONSTRAINT_TYPE = 'FOREIGN KEY'",
'AND C.TABLE_NAME =', wrapSingleQuote(tableName)
].join(' ');
if (table.schema) {
sql += ' AND C.TABLE_SCHEMA =' + wrapSingleQuote(table.schema);
}
return sql;
},
dropForeignKeyQuery: function(tableName, foreignKey) {
return Utils._.template('ALTER TABLE <%= table %> DROP <%= key %>')({
table: this.quoteTable(tableName),
key: this.quoteIdentifier(foreignKey)
});
},
setAutocommitQuery: function(value) {
return '';
// return 'SET IMPLICIT_TRANSACTIONS ' + (!!value ? 'OFF' : 'ON') + ';';
},
setIsolationLevelQuery: function(value, options) {
if (options.parent) {
return;
}
return 'SET TRANSACTION ISOLATION LEVEL ' + value + ';';
},
startTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'SAVE TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
}
return 'BEGIN TRANSACTION;';
},
commitTransactionQuery: function(options) {
if (options.parent) {
return;
}
return 'COMMIT TRANSACTION;';
},
rollbackTransactionQuery: function(transaction, options) {
if (options.parent) {
return 'ROLLBACK TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
}
return 'ROLLBACK TRANSACTION;';
},
addLimitAndOffset: function(options, model) {
var fragment = '';
var offset = options.offset || 0
, isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
// FIXME: This is ripped from selectQuery to determine whether there is already
// an ORDER BY added for a subquery. Should be refactored so we dont' need
// the duplication. Also consider moving this logic inside the options.order
// check, so that we aren't compiling this twice for every invocation.
var mainQueryOrder = [];
var subQueryOrder = [];
if (options.order) {
if (Array.isArray(options.order)) {
options.order.forEach(function(t) {
if (!Array.isArray(t)) {
if (isSubQuery && !(t instanceof Model) && !(t.model instanceof Model)) {
subQueryOrder.push(this.quote(t, model));
}
} else {
if (isSubQuery && !(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(options.order);
}
}
if (options.limit || options.offset) {
if (!options.order || (options.include && !subQueryOrder.length)) {
fragment += (options.order && !isSubQuery) ? ', ' : ' ORDER BY ';
fragment += this.quoteIdentifier(model.primaryKeyAttribute);
}
if (options.offset || options.limit) {
fragment += ' OFFSET ' + offset + ' ROWS';
}
if (options.limit) {
fragment += ' FETCH NEXT ' + options.limit + ' ROWS ONLY';
}
}
return fragment;
},
booleanValue: function(value) {
return !!value ? 1 : 0;
}
};
// private methods
function wrapSingleQuote(identifier){
return Utils.addTicks(identifier, "'");
}
module.exports = Utils._.extend(Utils._.clone(AbstractQueryGenerator), QueryGenerator);