不要怂,就是干,撸起袖子干!

Commit 03845fa5 by Matt Broadstone

improve index support for mssql dialect

  - bring addIndexQuery in line with abstract dialect
  - use an improved method for showing indices with sp_helpindex
  - actually use handleShowIndexQuery (bringing us closer to the
    abstract dialect for formatResults)
  - temporarily disable tests for a FULLTEXT index
1 parent 3d1ef1ee
......@@ -12,7 +12,7 @@ var MssqlDialect = function(sequelize) {
};
MssqlDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.supports), {
'RETURNING': true,
'RETURNING': true,
'LIMIT ON UPDATE': true,
lock: true,
forShare: 'LOCK IN SHARE MODE',
......@@ -21,7 +21,7 @@ MssqlDialect.prototype.supports = _.merge(_.cloneDeep(Abstract.prototype.support
length: false,
parser: false,
type: true,
using: 1,
using: false,
}
});
......
......@@ -50,9 +50,11 @@ module.exports = (function() {
createSchema: function(schema){
return SqlGenerator.getCreateSchemaSql(schema);
},
showSchemasQuery: function(){
return 'SELECT name FROM sys.Tables;';
},
/*
Returns a query for creating a table.
Parameters:
......@@ -338,11 +340,79 @@ module.exports = (function() {
},
addIndexQuery: function(tableName, attributes, options, rawTablename) {
return SqlGenerator.addIndexSql(tableName, attributes, options, rawTablename);
options = options || {};
var transformedAttributes = attributes.map(function(attribute) {
if (typeof attribute === 'string') {
return this.quoteIdentifier(attribute);
} else {
var result = '';
if (!attribute.attribute) {
throw new Error('The following index attribute has no attribute: ' + util.inspect(attribute));
}
result += this.quoteIdentifier(attribute.attribute);
if (this._dialect.supports.index.collate && attribute.collate) {
result += ' COLLATE ' + this.quoteIdentifier(attribute.collate);
}
if (this._dialect.supports.index.length && attribute.length) {
result += '(' + attribute.length + ')';
}
if (attribute.order) {
result += ' ' + attribute.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.fields = options.fields || attributes;
options = this.nameIndexes([options], rawTablename)[0];
}
options = Utils._.defaults(options, {
type: '',
indicesType: options.type || '',
indexType: options.method || undefined,
indexName: options.name,
parser: null
});
if (options.indicesType.toLowerCase() === 'unique') {
options.unique = true;
delete options.indicesType;
}
if (!this._dialect.supports.index.type) {
delete options.indicesType;
}
return Utils._.compact([
'CREATE',
options.unique ? 'UNIQUE' : '',
options.indicesType, 'INDEX',
this._dialect.supports.index.concurrently && options.concurrently ? 'CONCURRENTLY' : undefined,
this.quoteIdentifiers(options.indexName),
this._dialect.supports.index.using === 1 && options.indexType ? 'USING ' + options.indexType : '',
'ON', this.quoteIdentifiers(tableName),
this._dialect.supports.index.using === 2 && options.indexType ? 'USING ' + options.indexType : '',
'(' + transformedAttributes.join(', ') + ')',
(this._dialect.supports.index.parser && options.parser ? 'WITH PARSER ' + options.parser : undefined)
]).join(' ');
},
showIndexQuery: function(tableName, options) {
return SqlGenerator.showIndexSql(tableName, options);
var sql = "EXEC sys.sp_helpindex @objname = N'[<%= tableName %>]'";
return Utils._.template(sql)({
tableName: tableName
});
},
removeIndexQuery: function(tableName, indexNameOrAttributes) {
......
......@@ -113,6 +113,7 @@ module.exports = (function() {
*/
Query.prototype.formatResults = function(data) {
var result = this.callee;
if (data) {
if (this.isInsertQuery(data)) {
this.handleInsertQuery(data);
......@@ -133,7 +134,7 @@ module.exports = (function() {
};
});
} else if (this.isShowIndexesQuery()) {
result = data;
result = this.handleShowIndexesQuery(data);
}
} else if (this.isSelectQuery()) {
result = this.handleSelectQuery(data);
......@@ -182,40 +183,48 @@ module.exports = (function() {
result = result || (this.sql.toLowerCase().indexOf("select c.column_name as 'name', c.data_type as 'type', c.is_nullable as 'isnull'") === 0);
result = result || (this.sql.toLowerCase().indexOf('select tablename = t.name, name = ind.name,') === 0);
result = result || (this.sql.toLowerCase().indexOf('exec sys.sp_helpindex @objname') === 0);
return result;
};
Query.prototype.isShowIndexesQuery = function () {
return this.sql.toLowerCase().indexOf('select tablename = t.name, name = ind.name,') === 0;
return this.sql.toLowerCase().indexOf('exec sys.sp_helpindex @objname') === 0;
};
Query.prototype.handleShowIndexesQuery = function (data) {
// Group by index name, and collect all fields
data = Utils._.foldl(data, function (acc, item) {
if (!(item.Key_name in acc)) {
acc[item.Key_name] = item;
if (!(item.index_name in acc)) {
acc[item.index_name] = item;
item.fields = [];
}
acc[item.Key_name].fields[item.Seq_in_index - 1] = {
attribute: item.Column_name,
length: item.Sub_part || undefined,
order: item.Collation === 'A' ? 'ASC' : undefined
};
delete item.column_name;
Utils._.forEach(item.index_keys.split(','), function(column) {
var columnName = column.trim();
if (columnName.indexOf('(-)') !== -1) {
columnName = columnName.replace('(-)','');
}
acc[item.index_name].fields.push({
attribute: columnName,
length: undefined,
order: (column.indexOf('(-)') !== -1 ? 'DESC' : 'ASC'),
collate: undefined
});
});
delete item.index_keys;
return acc;
}, {});
return Utils._.map(data, function(item) {
return {
primary: item.Key_name === 'PRIMARY',
primary: (item.index_name.toLowerCase().indexOf('pk') === 0),
fields: item.fields,
name: item.Key_name,
tableName: item.Table,
unique: (item.Non_unique !== 1),
type: item.Index_type,
name: item.index_name,
tableName: undefined,
unique: (item.index_description.toLowerCase().indexOf('unique') !== -1),
type: undefined,
};
});
};
......
......@@ -156,14 +156,13 @@ function loadColumn(attributes){
function addTableExistsWrapper(query, exists){
return [
"IF (",
(exists ? "" : "NOT"), " EXISTS (",
"SELECT * FROM INFORMATION_SCHEMA.TABLES",
"WHERE TABLE_NAME='<%= unquotedTable %>'))",
"BEGIN",
query,
"END"
].join(" ");
'IF (',
(exists ? '' : 'NOT '), 'EXISTS (',
'SELECT * FROM INFORMATION_SCHEMA.TABLES',
" WHERE TABLE_NAME='<%= unquotedTable %>')",
')',
' BEGIN ', query, ' END'
].join('');
}
//select stuff
......@@ -491,54 +490,6 @@ module.exports = {
return Utils._.template(query)(values);
},
showIndexSql: function(tableName, options){
var sql = ["SELECT",
"TableName = t.name,",
"name = ind.name,",
"IndexId = ind.index_id,",
"ColumnId = ic.index_column_id,",
"ColumnName = col.name",
"FROM",
"sys.indexes ind",
"INNER JOIN",
"sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id",
"INNER JOIN",
"sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id",
"INNER JOIN",
"sys.tables t ON ind.object_id = t.object_id",
"WHERE t.name = '<%= tableName %>'<%= options %>"
].join(" ");
return Utils._.template(sql)({
tableName: tableName,
options: (options || {}).database ? ' FROM \'' + options.database + '\'' : ''
});
},
addIndexSql: function(tableName, attributes, options, rawTablename){
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.fields = options.fields || attributes;
options = nameIndexes([options], rawTablename)[0];
}
options = Utils._.defaults(options, {
type: '',
indicesType: options.type || '',
indexType: options.method || undefined,
indexName: options.name,
parser: null
});
var attrStr = loadColumn(attributes);
return Utils._.compact([
'CREATE',
options.unique ? 'UNIQUE' : '',
options.indicesType, 'INDEX',
quoteIdentifiers(options.indexName),
'ON', quoteIdentifiers(tableName),
'(' + attrStr.join(', ') + ')'
]).join(' ');
},
removeIndexSql: function(tableName, indexNameOrAttributes){
var sql = 'DROP INDEX <%= indexName %> ON <%= tableName %>'
, indexName = indexNameOrAttributes;
......
......@@ -409,24 +409,27 @@ describe(Support.getTestDialectTeaser("Model"), function () {
})
it('should allow the user to specify indexes in options', function () {
var indices = [{
name: 'a_b_uniq',
unique: true,
method: 'BTREE',
fields: ['fieldB', {attribute:'fieldA', collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', order: 'DESC', length: 5}]
}];
if (dialect !== 'mssql') {
indices.push({
type: 'FULLTEXT',
fields: ['fieldC'],
concurrently: true
});
}
var Model = this.sequelize.define('model', {
fieldA: Sequelize.STRING,
fieldB: Sequelize.INTEGER,
fieldC: Sequelize.STRING
}, {
indexes: [
{
name: 'a_b_uniq',
unique: true,
method: 'BTREE',
fields: ['fieldB', {attribute:'fieldA', collate: dialect === 'sqlite' ? 'RTRIM' : 'en_US', order: 'DESC', length: 5}]
},
{
type: 'FULLTEXT',
fields: ['fieldC'],
concurrently: true
},
],
indexes: indices,
engine: 'MyISAM'
})
......@@ -450,6 +453,13 @@ describe(Support.getTestDialectTeaser("Model"), function () {
expect(idx2.fields).to.deep.equal([
{ attribute: 'fieldC', length: undefined, order: undefined}
]);
} else if (dialect === 'mssql') {
idx1 = arguments[0];
expect(idx1.fields).to.deep.equal([
{ attribute: 'fieldB', length: undefined, order: 'ASC', collate: undefined},
{ attribute: 'fieldA', length: undefined, order: 'DESC', collate: undefined},
]);
} else if (dialect === 'postgres' || dialect === 'mssql') {
// Postgres returns indexes in alphabetical order
primary = arguments[2];
......@@ -488,8 +498,10 @@ describe(Support.getTestDialectTeaser("Model"), function () {
expect(idx1.name).to.equal('a_b_uniq');
expect(idx1.unique).to.be.ok;
expect(idx2.name).to.equal('models_field_c');
expect(idx2.unique).not.to.be.ok;
if (dialect !== 'mssql') {
expect(idx2.name).to.equal('models_field_c');
expect(idx2.unique).not.to.be.ok;
}
});
});
})
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!