sequelize
Version:
Multi dialect ORM for Node.JS/io.js
878 lines (731 loc) • 28.6 kB
JavaScript
;
/* jshint -W110 */
var Utils = require('../../utils')
, DataTypes = require('../../data-types')
, AbstractQueryGenerator = require('../abstract/query-generator')
, randomBytes = require('crypto').randomBytes
, semver = require('semver');
/* 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() {
// Uses string manipulation to convert the MS Maj.Min.Patch.Build to semver Maj.Min.Patch
return [
'DECLARE @ms_ver NVARCHAR(20);',
"SET @ms_ver = REVERSE(CONVERT(NVARCHAR(20), SERVERPROPERTY('ProductVersion')));",
"SELECT REVERSE(SUBSTRING(@ms_ver, CHARINDEX('.', @ms_ver)+1, 20)) AS 'version'"
].join(' ');
},
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.CHARACTER_MAXIMUM_LENGTH AS 'Length',",
"c.IS_NULLABLE as 'IsNull',",
"COLUMN_DEFAULT AS 'Default',",
"tc.CONSTRAINT_TYPE AS 'Constraint'",
'FROM',
'INFORMATION_SCHEMA.TABLES t',
'INNER JOIN',
'INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA',
'LEFT JOIN',
'INFORMATION_SCHEMA.KEY_COLUMN_USAGE cu ON t.TABLE_NAME = cu.TABLE_NAME AND cu.COLUMN_NAME = c.COLUMN_NAME AND t.TABLE_SCHEMA = cu.TABLE_SCHEMA',
'LEFT JOIN',
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc ON t.TABLE_NAME = tc.TABLE_NAME AND cu.COLUMN_NAME = c.COLUMN_NAME AND tc.CONSTRAINT_TYPE = \'PRIMARY KEY\'',
'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 COLUMN <%= attributeName %>;';
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
attributeName: this.quoteIdentifier(attributeName)
});
},
changeColumnQuery: function(tableName, attributes) {
var query = 'ALTER TABLE <%= tableName %> <%= query %>;';
var attrString = [], constraintString = [];
for (var attributeName in attributes) {
var definition = attributes[attributeName];
if (definition.match(/REFERENCES/)) {
constraintString.push(Utils._.template('<%= fkName %> FOREIGN KEY (<%= attrName %>) <%= definition %>')({
fkName: this.quoteIdentifier(attributeName + '_foreign_idx'),
attrName: this.quoteIdentifier(attributeName),
definition: definition.replace(/.+?(?=REFERENCES)/,'')
}));
} else {
attrString.push(Utils._.template('<%= attrName %> <%= definition %>')({
attrName: this.quoteIdentifier(attributeName),
definition: definition
}));
}
}
var finalQuery = '';
if (attrString.length) {
finalQuery += 'ALTER COLUMN ' + attrString.join(', ');
finalQuery += constraintString.length ? ' ' : '';
}
if (constraintString.length) {
finalQuery += 'ADD CONSTRAINT ' + constraintString.join(', ');
}
return Utils._.template(query)({
tableName: this.quoteTable(tableName),
query: finalQuery
});
},
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) {
options = options || {};
attributes = 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);
var firstAttr = attributes[fields[0]];
if (fields.length === 1 && firstAttr && firstAttr.autoIncrement && attrValueHash[fields[0]] === null) {
allQueries.push(emptyQuery);
return;
}
// normal case
Utils._.forOwn(attrValueHash, function(value, key) {
if (value !== null && attributes[key] && 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;
},
upsertQuery: function(tableName, insertValues, updateValues, where, rawAttributes, options) {
var query = 'MERGE INTO <%= tableNameQuoted %> WITH(HOLDLOCK) AS <%= targetTableAlias %> USING (<%= sourceTableQuery %>) AS <%= sourceTableAlias%>(<%=insertKeysQuoted%>) ON <%= joinCondition %>';
query += ' WHEN MATCHED THEN UPDATE SET <%= updateSnippet %> WHEN NOT MATCHED THEN INSERT <%= insertSnippet %> OUTPUT $action, INSERTED.*;';
var targetTableAlias = this.quoteTable(tableName + '_target')
, sourceTableAlias = this.quoteTable(tableName + '_source')
, primaryKeysAttrs = []
, identityAttrs = []
, uniqueAttrs = []
, tableNameQuoted = this.quoteTable(tableName)
, joinCondition
, needIdentityInsertWrapper = false;
//Obtain primaryKeys, uniquekeys and identity attrs from rawAttributes as model is not passed
for (var key in rawAttributes) {
if (rawAttributes[key].primaryKey) {
primaryKeysAttrs.push(rawAttributes[key].field || key);
}
if (rawAttributes[key].unique) {
uniqueAttrs.push(rawAttributes[key].field || key);
}
if (rawAttributes[key].autoIncrement) {
identityAttrs.push(rawAttributes[key].field || key);
}
}
var updateKeys = Object.keys(updateValues)
, insertKeys = Object.keys(insertValues);
var insertKeysQuoted = Utils._.map(insertKeys, function(key) {
return this.quoteIdentifier(key);
}.bind(this)).join(', ');
var insertValuesEscaped = Utils._.map(insertKeys, function(key) {
return this.escape(insertValues[key]);
}.bind(this)).join(', ');
var sourceTableQuery = 'VALUES(' + insertValuesEscaped + ')'; //Virtual Table
//IDENTITY_INSERT Condition
identityAttrs.forEach(function(key) {
if (updateValues[key] && updateValues[key] !== null) {
needIdentityInsertWrapper = true;
/*
* IDENTITY_INSERT Column Cannot be updated, only inserted
* http://stackoverflow.com/a/30176254/2254360
*/
}
});
//Filter NULL Clauses
var clauses = where.$or.filter(function(clause) {
var valid = true;
/*
* Exclude NULL Composite PK/UK. Partial Composite clauses should also be excluded as it doesn't guarantee a single row
*/
for (var key in clause) {
if (!clause[key]) {
valid = false;
break;
}
}
return valid;
});
/*
* Generate ON condition using PK(s).
* If not, generate using UK(s). Else throw error
*/
var getJoinSnippet = function(array) {
return Utils._.map(array, function(key) {
key = this.quoteIdentifier(key);
return targetTableAlias + '.' + key + ' = ' + sourceTableAlias + '.' + key;
}.bind(this));
};
if (clauses.length === 0) {
throw new Error('Primary Key or Unique key should be passed to upsert query');
} else {
// Search for primary key attribute in clauses -- Model can have two separate unique keys
for (key in clauses) {
var keys = Object.keys(clauses[key]);
if (primaryKeysAttrs.indexOf(keys[0]) !== -1) {
joinCondition = getJoinSnippet.bind(this)(primaryKeysAttrs).join(' AND ');
break;
}
}
if (!joinCondition) {
joinCondition = getJoinSnippet.bind(this)(uniqueAttrs).join(' AND ');
}
}
// Remove the IDENTITY_INSERT Column from update
var updateSnippet = updateKeys.filter(function(key) {
if (identityAttrs.indexOf(key) === -1) {
return true;
} else {
return false;
}
});
updateSnippet = Utils._.map(updateSnippet, function(key) {
var value = this.escape(updateValues[key]);
key = this.quoteIdentifier(key);
return targetTableAlias + '.' + key + ' = ' + value;
}.bind(this)).join(', ');
var insertSnippet = '(' + insertKeysQuoted + ') VALUES(' + insertValuesEscaped + ')';
var replacements = {
tableNameQuoted: tableNameQuoted,
targetTableAlias: targetTableAlias,
sourceTableQuery: sourceTableQuery,
sourceTableAlias: sourceTableAlias,
insertKeysQuoted: insertKeysQuoted,
joinCondition: joinCondition,
updateSnippet: updateSnippet,
insertSnippet: insertSnippet
};
query = Utils._.template(query)(replacements);
if (needIdentityInsertWrapper) {
query = [
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'ON;',
query,
'SET IDENTITY_INSERT', this.quoteTable(tableName), 'OFF;',
].join(' ');
}
return query;
},
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) {
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;
},
getForeignKeyQuery: function(table, attributeName) {
var tableName = table.tableName || table;
var sql = [
'SELECT',
'constraint_name = TC.CONSTRAINT_NAME',
'FROM',
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC',
'JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE CCU',
'ON TC.CONSTRAINT_NAME = CCU.CONSTRAINT_NAME',
"WHERE TC.CONSTRAINT_TYPE = 'FOREIGN KEY'",
'AND TC.TABLE_NAME =', wrapSingleQuote(tableName),
'AND CCU.COLUMN_NAME =', wrapSingleQuote(attributeName),
].join(' ');
if (table.schema) {
sql += ' AND TC.TABLE_SCHEMA =' + wrapSingleQuote(table.schema);
}
return sql;
},
getPrimaryKeyConstraintQuery: function(table, attributeName) {
var tableName = wrapSingleQuote(table.tableName || table);
return [
'SELECT K.TABLE_NAME AS tableName,',
'K.COLUMN_NAME AS columnName,',
'K.CONSTRAINT_NAME AS constraintName',
'FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C',
'JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K',
'ON C.TABLE_NAME = K.TABLE_NAME',
'AND C.CONSTRAINT_CATALOG = K.CONSTRAINT_CATALOG',
'AND C.CONSTRAINT_SCHEMA = K.CONSTRAINT_SCHEMA',
'AND C.CONSTRAINT_NAME = K.CONSTRAINT_NAME',
'WHERE C.CONSTRAINT_TYPE = \'PRIMARY KEY\'',
'AND K.COLUMN_NAME =',
wrapSingleQuote(attributeName),
'AND K.TABLE_NAME =',
tableName + ';',
].join(' ');
},
dropForeignKeyQuery: function(tableName, foreignKey) {
return Utils._.template('ALTER TABLE <%= table %> DROP <%= key %>')({
table: this.quoteTable(tableName),
key: this.quoteIdentifier(foreignKey)
});
},
getDefaultConstraintQuery: function (tableName, attributeName) {
var sql = "SELECT name FROM SYS.DEFAULT_CONSTRAINTS " +
"WHERE PARENT_OBJECT_ID = OBJECT_ID('<%= table %>', 'U') " +
"AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('<%= column %>') " +
"AND object_id = OBJECT_ID('<%= table %>', 'U'));";
return Utils._.template(sql)({
table: this.quoteTable(tableName),
column: attributeName
});
},
dropConstraintQuery: function (tableName, constraintName) {
var sql = 'ALTER TABLE <%= table %> DROP CONSTRAINT <%= constraint %>;';
return Utils._.template(sql)({
table: this.quoteTable(tableName),
constraint: this.quoteIdentifier(constraintName)
});
},
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 + ';';
},
generateTransactionId: function () {
return randomBytes(10).toString('hex');
},
startTransactionQuery: function(transaction, options) {
if (transaction.parent) {
return 'SAVE TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
}
return 'BEGIN TRANSACTION;';
},
commitTransactionQuery: function(transaction) {
if (transaction.parent) {
return;
}
return 'COMMIT TRANSACTION;';
},
rollbackTransactionQuery: function(transaction, options) {
if (transaction.parent) {
return 'ROLLBACK TRANSACTION ' + this.quoteIdentifier(transaction.name) + ';';
}
return 'ROLLBACK TRANSACTION;';
},
selectFromTableFragment: function(options, model, attributes, tables, mainTableAs, where) {
var topFragment = '';
var mainFragment = 'SELECT ' + attributes.join(', ') + ' FROM ' + tables;
// Handle SQL Server 2008 with TOP instead of LIMIT
if (semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) {
if (options.limit) {
topFragment = 'TOP ' + options.limit + ' ';
}
if (options.offset) {
var offset = options.offset || 0
, isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation
, orders = { mainQueryOrder: [] };
if (options.order) {
orders = this.getQueryOrders(options, model, isSubQuery);
}
if(!orders.mainQueryOrder.length) {
orders.mainQueryOrder.push(this.quoteIdentifier(model.primaryKeyField));
}
var tmpTable = (mainTableAs) ? mainTableAs : 'OffsetTable';
var whereFragment = (where) ? ' WHERE ' + where : '';
/*
* For earlier versions of SQL server, we need to nest several queries
* in order to emulate the OFFSET behavior.
*
* 1. The outermost query selects all items from the inner query block.
* This is due to a limitation in SQL server with the use of computed
* columns (e.g. SELECT ROW_NUMBER()...AS x) in WHERE clauses.
* 2. The next query handles the LIMIT and OFFSET behavior by getting
* the TOP N rows of the query where the row number is > OFFSET
* 3. The innermost query is the actual set we want information from
*/
var fragment = 'SELECT TOP 100 PERCENT ' + attributes.join(', ') + ' FROM ' +
'(SELECT ' + topFragment + '*' +
' FROM (SELECT ROW_NUMBER() OVER (ORDER BY ' + orders.mainQueryOrder.join(', ') + ') as row_num, * ' +
' FROM ' + tables + ' AS ' + tmpTable + whereFragment + ')' +
' AS ' + tmpTable + ' WHERE row_num > ' + offset + ')' +
' AS ' + tmpTable;
return fragment;
} else {
mainFragment = 'SELECT ' + topFragment + attributes.join(', ') + ' FROM ' + tables;
}
}
if(mainTableAs) {
mainFragment += ' AS ' + mainTableAs;
}
return mainFragment;
},
addLimitAndOffset: function(options, model) {
// Skip handling of limit and offset as postfixes for older SQL Server versions
if(semver.valid(this.sequelize.options.databaseVersion) && semver.lt(this.sequelize.options.databaseVersion, '11.0.0')) {
return '';
}
var fragment = '';
var offset = options.offset || 0
, isSubQuery = options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation;
var orders = {};
if (options.order) {
orders = this.getQueryOrders(options, model, isSubQuery);
}
if (options.limit || options.offset) {
if (!options.order || (options.include && !orders.subQueryOrder.length)) {
fragment += (options.order && !isSubQuery) ? ', ' : ' ORDER BY ';
fragment += this.quoteIdentifier(model.primaryKeyField);
}
if (options.offset || options.limit) {
fragment += ' OFFSET ' + this.escape(offset) + ' ROWS';
}
if (options.limit) {
fragment += ' FETCH NEXT ' + this.escape(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);