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

Commit 9f2c9db8 by Mirko Jotic Committed by Sushant

feat(model): column level comments (#9573)

1 parent 17a4c156
......@@ -66,6 +66,13 @@ const Foo = sequelize.define('foo', {
// This declares when to check the foreign key constraint. PostgreSQL only.
deferrable: Sequelize.Deferrable.INITIALLY_IMMEDIATE
}
},
// It is possible to add coments on columns for MySQL, PostgreSQL and MSSQL only
commentMe: {
type: Sequelize.INTEGER,
comment: 'This is a column name that has a comment'
}
})
```
......
......@@ -85,13 +85,32 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
const query = "IF OBJECT_ID('<%= table %>', 'U') IS NULL CREATE TABLE <%= table %> (<%= attributes %>)",
primaryKeys = [],
foreignKeys = {},
attrStr = [];
attrStr = [],
commentTemplate = ' EXEC sp_addextendedproperty ' +
'@name = N\'MS_Description\', @value = <%= comment %>, ' +
'@level0type = N\'Schema\', @level0name = \'dbo\', ' +
'@level1type = N\'Table\', @level1name = <%= table %>, ' +
'@level2type = N\'Column\', @level2name = <%= column %>;';
let commentStr = '';
for (const attr in attributes) {
if (attributes.hasOwnProperty(attr)) {
const dataType = attributes[attr];
let dataType = attributes[attr];
let match;
if (_.includes(dataType, 'COMMENT')) {
const commentMatch = dataType.match(/^(.+) (COMMENT.*)$/);
const commentText = commentMatch[2].replace(/COMMENT/, '').trim();
commentStr += _.template(commentTemplate, this._templateSettings)({
table: this.quoteIdentifier(tableName),
comment: this.escape(commentText),
column: this.quoteIdentifier(attr)
});
// remove comment related substring from dataType
dataType = commentMatch[1];
}
if (_.includes(dataType, 'PRIMARY KEY')) {
primaryKeys.push(attr);
......@@ -111,6 +130,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
} else {
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
}
}
}
......@@ -141,7 +161,7 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
}
}
return _.template(query, this._templateSettings)(values).trim() + ';';
return _.template(query, this._templateSettings)(values).trim() + ';' + commentStr;
}
describeTableQuery(tableName, schema) {
......@@ -153,7 +173,8 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
"c.IS_NULLABLE as 'IsNull',",
"COLUMN_DEFAULT AS 'Default',",
"pk.CONSTRAINT_TYPE AS 'Constraint',",
"COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity'",
"COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity',",
"prop.value AS 'Comment'",
'FROM',
'INFORMATION_SCHEMA.TABLES t',
'INNER JOIN',
......@@ -168,6 +189,11 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
'ON pk.table_schema=c.table_schema ',
'AND pk.table_name=c.table_name ',
'AND pk.column_name=c.column_name ',
'INNER JOIN sys.columns AS sc',
"ON sc.object_id = object_id(t.table_schema + '.' + t.table_name) AND sc.name = c.column_name",
'LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id',
'AND prop.minor_id = sc.column_id',
"AND prop.name = 'MS_Description'",
'WHERE t.TABLE_NAME =', wrapSingleQuote(tableName)
].join(' ');
......@@ -606,6 +632,10 @@ class MSSQLQueryGenerator extends AbstractQueryGenerator {
}
}
if (attribute.comment && _.isString(attribute.comment)) {
template += ' COMMENT ' + attribute.comment;
}
return template;
}
......
......@@ -209,7 +209,8 @@ class Query extends AbstractQuery {
allowNull: _result.IsNull === 'YES' ? true : false,
defaultValue: _result.Default,
primaryKey: _result.Constraint === 'PRIMARY KEY',
autoIncrement: _result.IsIdentity === 1
autoIncrement: _result.IsIdentity === 1,
comment: _result.Comment
};
}
} else if (this.isShowIndexesQuery()) {
......
......@@ -103,6 +103,19 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
return _.template(query, this._templateSettings)(values).trim() + ';';
}
describeTableQuery(tableName, schema, schemaDelimiter) {
const table = this.quoteTable(
this.addSchema({
tableName,
_schema: schema,
_schemaDelimiter: schemaDelimiter
})
);
return `SHOW FULL COLUMNS FROM ${table};`;
}
showTablesQuery() {
return 'SHOW TABLES;';
}
......@@ -336,6 +349,10 @@ class MySQLQueryGenerator extends AbstractQueryGenerator {
template += ' PRIMARY KEY';
}
if (attribute.comment) {
template += ' COMMENT ' + this.escape(attribute.comment);
}
if (attribute.first) {
template += ' FIRST';
}
......
......@@ -144,7 +144,8 @@ class Query extends AbstractQuery {
type: enumRegex.test(_result.Type) ? _result.Type.replace(enumRegex, 'ENUM') : _result.Type.toUpperCase(),
allowNull: _result.Null === 'YES',
defaultValue: _result.Default,
primaryKey: _result.Key === 'PRI'
primaryKey: _result.Key === 'PRI',
comment: _result.Comment ? _result.Comment : null
};
}
} else if (this.isShowIndexesQuery()) {
......
......@@ -41,27 +41,31 @@ class PostgresQueryGenerator extends AbstractQueryGenerator {
const databaseVersion = _.get(this, 'sequelize.options.databaseVersion', 0);
const attrStr = [];
let comments = '';
let columnComments = '';
if (options.comment && _.isString(options.comment)) {
comments += '; COMMENT ON TABLE <%= table %> IS ' + this.escape(options.comment);
}
for (const attr in attributes) {
const quotedAttr = this.quoteIdentifier(attr);
const i = attributes[attr].indexOf('COMMENT');
if (i !== -1) {
// Move comment to a separate query
comments += '; ' + attributes[attr].substring(i);
const escapedCommentText = this.escape(attributes[attr].substring(i + 8));
columnComments += `; COMMENT ON COLUMN <%= table %>.${quotedAttr} IS ${escapedCommentText}`;
attributes[attr] = attributes[attr].substring(0, i);
}
const dataType = this.dataTypeMapping(tableName, attr, attributes[attr]);
attrStr.push(this.quoteIdentifier(attr) + ' ' + dataType);
attrStr.push(quotedAttr + ' ' + dataType);
}
const values = {
table: this.quoteTable(tableName),
attributes: attrStr.join(', '),
comments: _.template(comments, this._templateSettings)({ table: this.quoteTable(tableName) })
comments: _.template(comments, this._templateSettings)({ table: this.quoteTable(tableName) }),
columnComments: _.template(columnComments, this._templateSettings)({ table: this.quoteTable(tableName) })
};
if (options.uniqueKeys) {
......@@ -83,7 +87,7 @@ class PostgresQueryGenerator extends AbstractQueryGenerator {
values.attributes += `, PRIMARY KEY (${pks})`;
}
return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${values.table} (${values.attributes})${values.comments};`;
return `CREATE TABLE ${databaseVersion === 0 || semver.gte(databaseVersion, '9.1.0') ? 'IF NOT EXISTS ' : ''}${values.table} (${values.attributes})${values.comments}${values.columnComments};`;
}
dropTableQuery(tableName, options) {
......@@ -104,7 +108,10 @@ class PostgresQueryGenerator extends AbstractQueryGenerator {
'(CASE WHEN c.udt_name = \'hstore\' THEN c.udt_name ELSE c.data_type END) || (CASE WHEN c.character_maximum_length IS NOT NULL THEN \'(\' || c.character_maximum_length || \')\' ELSE \'\' END) as "Type", ' +
'(SELECT array_agg(e.enumlabel) ' +
'FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON t.oid=e.enumtypid ' +
'WHERE t.typname=c.udt_name) AS "special" ' +
'WHERE t.typname=c.udt_name) AS "special", ' +
'(SELECT pgd.description FROM pg_catalog.pg_statio_all_tables AS st ' +
'INNER JOIN pg_catalog.pg_description pgd on (pgd.objoid=st.relid) ' +
'WHERE c.ordinal_position=pgd.objsubid AND c.table_name=st.relname) AS "Comment" ' +
'FROM information_schema.columns c ' +
'LEFT JOIN (SELECT tc.table_schema, tc.table_name, ' +
'cu.column_name, tc.constraint_type ' +
......@@ -540,6 +547,10 @@ class PostgresQueryGenerator extends AbstractQueryGenerator {
}
}
if (attribute.comment && _.isString(attribute.comment)) {
sql += ' COMMENT ' + attribute.comment;
}
return sql;
}
......
......@@ -211,6 +211,7 @@ class Query extends AbstractQuery {
type: row.Type.toUpperCase(),
allowNull: row.Null === 'YES',
defaultValue: row.Default,
comment: row.Comment,
special: row.special ? this.sequelize.getQueryInterface().QueryGenerator.fromArray(row.special) : [],
primaryKey: row.Constraint === 'PRIMARY KEY'
};
......
......@@ -150,7 +150,8 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
username: DataTypes.STRING,
city: {
type: DataTypes.STRING,
defaultValue: null
defaultValue: null,
comment: 'Users City'
},
isAdmin: DataTypes.BOOLEAN,
enumVals: DataTypes.ENUM('hello', 'world')
......@@ -217,6 +218,11 @@ describe(Support.getTestDialectTeaser('QueryInterface'), () => {
} else if (dialect === 'mysql') {
expect(enumVals.type).to.eql('ENUM(\'hello\',\'world\')');
}
if (dialect === 'postgres' || dialect === 'mysql' || dialect === 'mssql') {
expect(city.comment).to.equal('Users City');
expect(username.comment).to.equal(null);
}
});
});
});
......
......@@ -15,6 +15,17 @@ if (current.dialect.name === 'mssql') {
});
});
it('createTableQuery', function() {
expectsql(this.queryGenerator.createTableQuery('myTable', { int: 'INTEGER' }, {}), {
mssql: "IF OBJECT_ID('[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([int] INTEGER);"
});
});
it('createTableQuery with comments', function() {
expectsql(this.queryGenerator.createTableQuery('myTable', { int: 'INTEGER COMMENT Foo Bar', varchar: 'VARCHAR(50) UNIQUE COMMENT Bar Foo' }, {}), {
mssql: "IF OBJECT_ID('[myTable]', 'U') IS NULL CREATE TABLE [myTable] ([int] INTEGER, [varchar] VARCHAR(50) UNIQUE); EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Foo Bar', @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [int]; EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'Bar Foo', @level0type = N'Schema', @level0name = 'dbo', @level1type = N'Table', @level1name = [myTable], @level2type = N'Column', @level2name = [varchar];" });
});
it('getDefaultConstraintQuery', function() {
expectsql(this.queryGenerator.getDefaultConstraintQuery({tableName: 'myTable', schema: 'mySchema'}, 'myColumn'), {
mssql: "SELECT name FROM sys.default_constraints WHERE PARENT_OBJECT_ID = OBJECT_ID('[mySchema].[myTable]', 'U') AND PARENT_COLUMN_ID = (SELECT column_id FROM sys.columns WHERE NAME = ('myColumn') AND object_id = OBJECT_ID('[mySchema].[myTable]', 'U'));"
......
......@@ -68,6 +68,11 @@ if (dialect === 'mysql') {
expectation: {id: 'INTEGER DEFAULT 0'}
},
{
title: 'Add column level comment',
arguments: [{id: {type: 'INTEGER', comment: 'Test'}}],
expectation: {id: 'INTEGER COMMENT \'Test\''}
},
{
arguments: [{id: {type: 'INTEGER', unique: true}}],
expectation: {id: 'INTEGER UNIQUE'}
},
......
......@@ -84,6 +84,10 @@ if (dialect.match(/^postgres/)) {
arguments: [{id: {type: 'INTEGER', unique: true}}],
expectation: {id: 'INTEGER UNIQUE'}
},
{
arguments: [{id: {type: 'INTEGER', unique: true, comment: 'This is my comment'}}],
expectation: {id: 'INTEGER UNIQUE COMMENT This is my comment'}
},
// New references style
{
......@@ -145,6 +149,10 @@ if (dialect.match(/^postgres/)) {
expectation: 'CREATE TABLE IF NOT EXISTS \"myTable\" (\"serial\" SERIAL, \"bigserial\" BIGSERIAL, \"smallserial\" SMALLSERIAL);'
},
{
arguments: ['myTable', {int: 'INTEGER COMMENT Test', foo: 'INTEGER COMMENT Foo Comment'}],
expectation: 'CREATE TABLE IF NOT EXISTS \"myTable\" (\"int\" INTEGER , \"foo\" INTEGER ); COMMENT ON COLUMN \"myTable\".\"int\" IS \'Test\'; COMMENT ON COLUMN \"myTable\".\"foo\" IS \'Foo Comment\';'
},
{
arguments: ['myTable', {title: 'VARCHAR(255)', name: 'VARCHAR(255)'}],
expectation: 'CREATE TABLE IF NOT EXISTS \"myTable\" (\"title\" VARCHAR(255), \"name\" VARCHAR(255));'
},
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!