mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add support for specifying comments for tables, columns, and indexes.
Comments are specified in migrations, stored in database itself (in its schema), and dumped into db/schema.rb file. This allows to generate good documentation and explain columns and tables' purpose to everyone from new developers to database administrators. For PostgreSQL and MySQL only. SQLite does not support comments at the moment. See docs for PostgreSQL: http://www.postgresql.org/docs/current/static/sql-comment.html See docs for MySQL: http://dev.mysql.com/doc/refman/5.7/en/create-table.html
This commit is contained in:
parent
39e087cbf5
commit
c690b9ce39
15 changed files with 286 additions and 37 deletions
|
@ -1,3 +1,20 @@
|
|||
* Add support for database schema comments for tables, columns and indexes
|
||||
for PostgreSQL and MySQL.
|
||||
|
||||
It allows to specify commentaries for database objects in migrations and
|
||||
store them in database itself, allowing to see them with DBA tools and
|
||||
in `db/schema.rb` file and thus automatically documents database schema:
|
||||
|
||||
create_table "pages", force: :cascade, comment: 'Arbitrary content pages' do |t|
|
||||
# ...
|
||||
t.string "path", comment: "Path fragment of page URL used for routing"
|
||||
t.string "locale", comment: "RFC 3066 locale code of website language section"
|
||||
t.index ["locale", "path"], name: 'page_uri_index' comment: "Main index used to lookup page by it's URI."
|
||||
# ...
|
||||
end
|
||||
|
||||
*Andrey Novikov*
|
||||
|
||||
* Add `quoted_time` for truncating the date part of a TIME column value.
|
||||
This fixes queries on TIME column on MariaDB, as it doesn't ignore the
|
||||
date part of the string when it coerces to time.
|
||||
|
|
|
@ -54,6 +54,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
create_sql << "(#{statements.join(', ')}) " if statements.present?
|
||||
add_table_options!(create_sql, table_options(o))
|
||||
create_sql << "#{o.options}"
|
||||
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
||||
create_sql
|
||||
|
@ -82,6 +83,16 @@ module ActiveRecord
|
|||
"DROP CONSTRAINT #{quote_column_name(name)}"
|
||||
end
|
||||
|
||||
def table_options(o)
|
||||
options = {}
|
||||
options[:comment] = o.comment
|
||||
options
|
||||
end
|
||||
|
||||
def add_table_options!(sql, _options)
|
||||
sql
|
||||
end
|
||||
|
||||
def column_options(o)
|
||||
column_options = {}
|
||||
column_options[:null] = o.null unless o.null.nil?
|
||||
|
@ -92,6 +103,7 @@ module ActiveRecord
|
|||
column_options[:auto_increment] = o.auto_increment
|
||||
column_options[:primary_key] = o.primary_key
|
||||
column_options[:collation] = o.collation
|
||||
column_options[:comment] = o.comment
|
||||
column_options
|
||||
end
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@ module ActiveRecord
|
|||
# Abstract representation of an index definition on a table. Instances of
|
||||
# this type are typically created and returned by methods in database
|
||||
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
|
||||
end
|
||||
|
||||
# Abstract representation of a column definition. Instances of this type
|
||||
# are typically created by methods in TableDefinition, and added to the
|
||||
# +columns+ attribute of said TableDefinition object, in order to be used
|
||||
# for generating a number of table creation or table changing SQL statements.
|
||||
class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
|
||||
class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
|
||||
|
||||
def primary_key?
|
||||
primary_key || type.to_sym == :primary_key
|
||||
|
@ -207,9 +207,9 @@ module ActiveRecord
|
|||
include ColumnMethods
|
||||
|
||||
attr_accessor :indexes
|
||||
attr_reader :name, :temporary, :options, :as, :foreign_keys
|
||||
attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
|
||||
|
||||
def initialize(name, temporary, options, as = nil)
|
||||
def initialize(name, temporary, options, as = nil, comment = nil)
|
||||
@columns_hash = {}
|
||||
@indexes = {}
|
||||
@foreign_keys = []
|
||||
|
@ -218,6 +218,7 @@ module ActiveRecord
|
|||
@options = options
|
||||
@as = as
|
||||
@name = name
|
||||
@comment = comment
|
||||
end
|
||||
|
||||
def primary_keys(name = nil) # :nodoc:
|
||||
|
@ -373,6 +374,7 @@ module ActiveRecord
|
|||
column.auto_increment = options[:auto_increment]
|
||||
column.primary_key = type == :primary_key || options[:primary_key]
|
||||
column.collation = options[:collation]
|
||||
column.comment = options[:comment]
|
||||
column
|
||||
end
|
||||
|
||||
|
|
|
@ -46,12 +46,14 @@ module ActiveRecord
|
|||
spec[:collation] = collation
|
||||
end
|
||||
|
||||
spec[:comment] = column.comment.inspect if column.comment
|
||||
|
||||
spec
|
||||
end
|
||||
|
||||
# Lists the valid migration options
|
||||
def migration_keys
|
||||
[:name, :limit, :precision, :scale, :default, :null, :collation]
|
||||
[:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -18,6 +18,11 @@ module ActiveRecord
|
|||
nil
|
||||
end
|
||||
|
||||
# Returns comment associated with given table in database
|
||||
def table_comment(table_name)
|
||||
nil
|
||||
end
|
||||
|
||||
# Truncates a table alias according to the limits of the current adapter.
|
||||
def table_alias_for(table_name)
|
||||
table_name[0...table_alias_length].tr('.', '_')
|
||||
|
@ -255,7 +260,7 @@ module ActiveRecord
|
|||
#
|
||||
# See also TableDefinition#column for details on how to create columns.
|
||||
def create_table(table_name, options = {})
|
||||
td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
|
||||
td = create_table_definition table_name, options[:temporary], options[:options], options[:as], options[:comment]
|
||||
|
||||
if options[:id] != false && !options[:as]
|
||||
pk = options.fetch(:primary_key) do
|
||||
|
@ -265,7 +270,7 @@ module ActiveRecord
|
|||
if pk.is_a?(Array)
|
||||
td.primary_keys pk
|
||||
else
|
||||
td.primary_key pk, options.fetch(:id, :primary_key), options
|
||||
td.primary_key pk, options.fetch(:id, :primary_key), options.except(:comment)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -283,6 +288,13 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
if supports_comments? && !supports_comments_in_create?
|
||||
change_table_comment(table_name, options[:comment]) if options[:comment]
|
||||
td.columns.each do |column|
|
||||
change_column_comment(table_name, column.name, column.comment) if column.comment
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
|
@ -1078,7 +1090,7 @@ module ActiveRecord
|
|||
def add_index_options(table_name, column_name, options = {}) #:nodoc:
|
||||
column_names = Array(column_name)
|
||||
|
||||
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
|
||||
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :comment)
|
||||
|
||||
index_type = options[:type].to_s if options.key?(:type)
|
||||
index_type ||= options[:unique] ? "UNIQUE" : ""
|
||||
|
@ -1106,13 +1118,25 @@ module ActiveRecord
|
|||
end
|
||||
index_columns = quoted_columns_for_index(column_names, options).join(", ")
|
||||
|
||||
[index_name, index_type, index_columns, index_options, algorithm, using]
|
||||
comment = options[:comment] if options.key?(:comment)
|
||||
|
||||
[index_name, index_type, index_columns, index_options, algorithm, using, comment]
|
||||
end
|
||||
|
||||
def options_include_default?(options)
|
||||
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
|
||||
end
|
||||
|
||||
# Adds comment for given table or drops it if +nil+ given
|
||||
def change_table_comment(table_name, comment)
|
||||
raise NotImplementedError, "change_table_comment is not implemented"
|
||||
end
|
||||
|
||||
# Adds comment for given table column or drops it if +nil+ given
|
||||
def change_column_comment(table_name, column_name, comment) #:nodoc:
|
||||
raise NotImplementedError, "change_column_comment is not implemented"
|
||||
end
|
||||
|
||||
protected
|
||||
def add_index_sort_order(option_strings, column_names, options = {})
|
||||
if options.is_a?(Hash) && order = options[:order]
|
||||
|
@ -1194,8 +1218,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
private
|
||||
def create_table_definition(name, temporary = false, options = nil, as = nil)
|
||||
TableDefinition.new(name, temporary, options, as)
|
||||
def create_table_definition(name, temporary = false, options = nil, as = nil, comment = nil)
|
||||
TableDefinition.new(name, temporary, options, as, comment)
|
||||
end
|
||||
|
||||
def create_alter_table(name)
|
||||
|
|
|
@ -278,6 +278,16 @@ module ActiveRecord
|
|||
false
|
||||
end
|
||||
|
||||
# Does adapter supports comments on database objects (tables, columns, indexes)?
|
||||
def supports_comments?
|
||||
false
|
||||
end
|
||||
|
||||
# Can comments for tables, columns, and indexes be specified in create/alter table statements?
|
||||
def supports_comments_in_create?
|
||||
false
|
||||
end
|
||||
|
||||
# This is meant to be implemented by the adapters that support extensions
|
||||
def disable_extension(name)
|
||||
end
|
||||
|
|
|
@ -160,8 +160,8 @@ module ActiveRecord
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def new_column(field, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
|
||||
MySQL::Column.new(field, default, sql_type_metadata, null, table_name, default_function, collation)
|
||||
def new_column(field, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, comment = nil) # :nodoc:
|
||||
MySQL::Column.new(field, default, sql_type_metadata, null, table_name, default_function, collation, comment)
|
||||
end
|
||||
|
||||
# Must return the MySQL error number from the exception, if the exception has an
|
||||
|
@ -373,7 +373,7 @@ module ActiveRecord
|
|||
mysql_index_type = row[:Index_type].downcase.to_sym
|
||||
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
|
||||
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
|
||||
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
|
||||
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
|
||||
end
|
||||
|
||||
indexes.last.columns << row[:Column_name]
|
||||
|
@ -394,10 +394,18 @@ module ActiveRecord
|
|||
else
|
||||
default, default_function = field[:Default], nil
|
||||
end
|
||||
new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation])
|
||||
new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], field[:Comment].presence)
|
||||
end
|
||||
end
|
||||
|
||||
def table_comment(table_name)
|
||||
select_value(<<-SQL.strip_heredoc, 'SCHEMA')
|
||||
SELECT table_comment
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE table_name=#{quote(table_name)};
|
||||
SQL
|
||||
end
|
||||
|
||||
def create_table(table_name, options = {}) #:nodoc:
|
||||
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
||||
end
|
||||
|
@ -482,8 +490,10 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def add_index(table_name, column_name, options = {}) #:nodoc:
|
||||
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
||||
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
|
||||
index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
||||
sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
|
||||
sql << " COMMENT #{quote(comment)}" if comment
|
||||
execute sql
|
||||
end
|
||||
|
||||
def foreign_keys(table_name)
|
||||
|
@ -521,7 +531,12 @@ module ActiveRecord
|
|||
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
|
||||
|
||||
# strip AUTO_INCREMENT
|
||||
raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
||||
raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
||||
|
||||
# strip COMMENT
|
||||
raw_table_options.sub!(/ COMMENT='.+'/, '')
|
||||
|
||||
raw_table_options
|
||||
end
|
||||
|
||||
# Maps logical Rails types to MySQL-specific data types.
|
||||
|
@ -866,8 +881,8 @@ module ActiveRecord
|
|||
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
||||
end
|
||||
|
||||
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
|
||||
MySQL::TableDefinition.new(name, temporary, options, as)
|
||||
def create_table_definition(name, temporary = false, options = nil, as = nil, comment = nil) # :nodoc:
|
||||
MySQL::TableDefinition.new(name, temporary, options, as, comment)
|
||||
end
|
||||
|
||||
def integer_to_sql(limit) # :nodoc:
|
||||
|
|
|
@ -5,7 +5,7 @@ module ActiveRecord
|
|||
module ConnectionAdapters
|
||||
# An abstract definition of a column in a table.
|
||||
class Column
|
||||
attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation
|
||||
attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment
|
||||
|
||||
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
|
||||
|
||||
|
@ -15,7 +15,7 @@ module ActiveRecord
|
|||
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
|
||||
# +sql_type_metadata+ is various information about the type of the column
|
||||
# +null+ determines if this column allows +NULL+ values.
|
||||
def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil)
|
||||
def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment = nil)
|
||||
@name = name.freeze
|
||||
@table_name = table_name
|
||||
@sql_type_metadata = sql_type_metadata
|
||||
|
@ -23,6 +23,7 @@ module ActiveRecord
|
|||
@default = default
|
||||
@default_function = default_function
|
||||
@collation = collation
|
||||
@comment = comment
|
||||
end
|
||||
|
||||
def has_default?
|
||||
|
|
|
@ -2,6 +2,9 @@ module ActiveRecord
|
|||
module ConnectionAdapters
|
||||
module MySQL
|
||||
class SchemaCreation < AbstractAdapter::SchemaCreation
|
||||
delegate :quote, to: :@conn
|
||||
private :quote
|
||||
|
||||
private
|
||||
|
||||
def visit_DropForeignKey(name)
|
||||
|
@ -22,6 +25,13 @@ module ActiveRecord
|
|||
add_column_position!(change_column_sql, column_options(o.column))
|
||||
end
|
||||
|
||||
def add_table_options!(sql, options)
|
||||
super
|
||||
if options[:comment]
|
||||
sql << "COMMENT #{quote(options[:comment])} "
|
||||
end
|
||||
end
|
||||
|
||||
def column_options(o)
|
||||
column_options = super
|
||||
column_options[:charset] = o.charset
|
||||
|
@ -35,7 +45,11 @@ module ActiveRecord
|
|||
if options[:collation]
|
||||
sql << " COLLATE #{options[:collation]}"
|
||||
end
|
||||
super
|
||||
new_sql = super
|
||||
if options[:comment]
|
||||
new_sql << " COMMENT #{quote(options[:comment])}"
|
||||
end
|
||||
new_sql
|
||||
end
|
||||
|
||||
def add_column_position!(sql, options)
|
||||
|
@ -48,8 +62,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def index_in_create(table_name, column_name, options)
|
||||
index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
|
||||
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
|
||||
index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
|
||||
index_option = " COMMENT #{quote(comment)}" if comment
|
||||
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_option} "
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,6 +45,14 @@ module ActiveRecord
|
|||
!mariadb? && version >= '5.7.8'
|
||||
end
|
||||
|
||||
def supports_comments?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_comments_in_create?
|
||||
true
|
||||
end
|
||||
|
||||
# HELPER METHODS ===========================================
|
||||
|
||||
def each_hash(result) # :nodoc:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'active_support/core_ext/string/strip'
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module PostgreSQL
|
||||
|
@ -172,7 +174,8 @@ module ActiveRecord
|
|||
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
||||
|
||||
result = query(<<-SQL, 'SCHEMA')
|
||||
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
||||
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
||||
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
||||
FROM pg_class t
|
||||
INNER JOIN pg_index d ON t.oid = d.indrelid
|
||||
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
||||
|
@ -190,6 +193,7 @@ module ActiveRecord
|
|||
indkey = row[2].split(" ").map(&:to_i)
|
||||
inddef = row[3]
|
||||
oid = row[4]
|
||||
comment = row[5]
|
||||
|
||||
columns = Hash[query(<<-SQL, "SCHEMA")]
|
||||
SELECT a.attnum, a.attname
|
||||
|
@ -207,7 +211,7 @@ module ActiveRecord
|
|||
where = inddef.scan(/WHERE (.+)$/).flatten[0]
|
||||
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
|
||||
|
||||
IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
|
||||
IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using, comment)
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
@ -215,18 +219,33 @@ module ActiveRecord
|
|||
# Returns the list of all column definitions for a table.
|
||||
def columns(table_name) # :nodoc:
|
||||
table_name = table_name.to_s
|
||||
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
|
||||
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
|
||||
oid = oid.to_i
|
||||
fmod = fmod.to_i
|
||||
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
|
||||
default_value = extract_value_from_default(default)
|
||||
default_function = extract_default_function(default_value, default)
|
||||
new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation)
|
||||
new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment)
|
||||
end
|
||||
end
|
||||
|
||||
def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
|
||||
PostgreSQLColumn.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
|
||||
def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil, comment = nil) # :nodoc:
|
||||
PostgreSQLColumn.new(name, default, sql_type_metadata, null, table_name, default_function, collation, comment)
|
||||
end
|
||||
|
||||
# Returns a comment stored in database for given table
|
||||
def table_comment(table_name) # :nodoc:
|
||||
name = Utils.extract_schema_qualified_name(table_name.to_s)
|
||||
return nil unless name.identifier
|
||||
|
||||
select_value(<<-SQL.strip_heredoc, 'SCHEMA')
|
||||
SELECT pg_catalog.obj_description(c.oid, 'pg_class')
|
||||
FROM pg_catalog.pg_class c
|
||||
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE c.relname = '#{name.identifier}'
|
||||
AND c.relkind IN ('r') -- (r)elation/table
|
||||
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
|
||||
SQL
|
||||
end
|
||||
|
||||
# Returns the current database name.
|
||||
|
@ -445,6 +464,7 @@ module ActiveRecord
|
|||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
clear_cache!
|
||||
super
|
||||
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
|
@ -466,6 +486,7 @@ module ActiveRecord
|
|||
|
||||
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
|
||||
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
|
||||
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
||||
end
|
||||
|
||||
# Changes the default value of a table column.
|
||||
|
@ -494,6 +515,18 @@ module ActiveRecord
|
|||
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
|
||||
end
|
||||
|
||||
# Adds comment for given table column or drops it if +comment+ is a +nil+
|
||||
def change_column_comment(table_name, column_name, comment) # :nodoc:
|
||||
clear_cache!
|
||||
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
|
||||
end
|
||||
|
||||
# Adds comment for given table or drops it if +comment+ is a +nil+
|
||||
def change_table_comment(table_name, comment) # :nodoc:
|
||||
clear_cache!
|
||||
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
|
||||
end
|
||||
|
||||
# Renames a column in a table.
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
clear_cache!
|
||||
|
@ -502,8 +535,10 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def add_index(table_name, column_name, options = {}) #:nodoc:
|
||||
index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
||||
execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
|
||||
index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
|
||||
result = execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
|
||||
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
|
||||
result # Result of execute is used in tests in activerecord/test/cases/adapters/postgresql/active_schema_test.rb
|
||||
end
|
||||
|
||||
def remove_index(table_name, options = {}) #:nodoc:
|
||||
|
|
|
@ -159,6 +159,10 @@ module ActiveRecord
|
|||
postgresql_version >= 90200
|
||||
end
|
||||
|
||||
def supports_comments?
|
||||
true
|
||||
end
|
||||
|
||||
def index_algorithms
|
||||
{ concurrently: 'CONCURRENTLY' }
|
||||
end
|
||||
|
@ -712,7 +716,7 @@ module ActiveRecord
|
|||
# Returns the list of a table's column names, data types, and default values.
|
||||
#
|
||||
# The underlying query is roughly:
|
||||
# SELECT column.name, column.type, default.value
|
||||
# SELECT column.name, column.type, default.value, column.comment
|
||||
# FROM column LEFT JOIN default
|
||||
# ON column.table_id = default.table_id
|
||||
# AND column.num = default.column_num
|
||||
|
@ -732,7 +736,8 @@ module ActiveRecord
|
|||
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
||||
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
||||
(SELECT c.collname FROM pg_collation c, pg_type t
|
||||
WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
|
||||
WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
|
||||
col_description(a.attrelid, a.attnum) AS comment
|
||||
FROM pg_attribute a LEFT JOIN pg_attrdef d
|
||||
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
||||
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
|
||||
|
@ -746,8 +751,8 @@ module ActiveRecord
|
|||
$1.strip if $1
|
||||
end
|
||||
|
||||
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
|
||||
PostgreSQL::TableDefinition.new(name, temporary, options, as)
|
||||
def create_table_definition(name, temporary = false, options = nil, as = nil, comment = nil) # :nodoc:
|
||||
PostgreSQL::TableDefinition.new(name, temporary, options, as, comment)
|
||||
end
|
||||
|
||||
def can_perform_case_insensitive_comparison_for?(column)
|
||||
|
|
|
@ -138,6 +138,9 @@ HEADER
|
|||
table_options = @connection.table_options(table)
|
||||
tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
|
||||
|
||||
comment = @connection.table_comment(table)
|
||||
tbl.print ", comment: #{comment.inspect}" if comment
|
||||
|
||||
tbl.puts " do |t|"
|
||||
|
||||
# then dump all non-primary key columns
|
||||
|
@ -209,6 +212,7 @@ HEADER
|
|||
statement_parts << "where: #{index.where.inspect}" if index.where
|
||||
statement_parts << "using: #{index.using.inspect}" if index.using
|
||||
statement_parts << "type: #{index.type.inspect}" if index.type
|
||||
statement_parts << "comment: #{index.comment.inspect}" if index.comment
|
||||
|
||||
" #{statement_parts.join(', ')}"
|
||||
end
|
||||
|
|
91
activerecord/test/cases/comment_test.rb
Normal file
91
activerecord/test/cases/comment_test.rb
Normal file
|
@ -0,0 +1,91 @@
|
|||
require 'cases/helper'
|
||||
require 'support/schema_dumping_helper'
|
||||
|
||||
class CommentTest < ActiveRecord::TestCase
|
||||
include SchemaDumpingHelper
|
||||
self.use_transactional_tests = false if current_adapter?(:Mysql2Adapter)
|
||||
|
||||
class Commented < ActiveRecord::Base
|
||||
self.table_name = 'commenteds'
|
||||
end
|
||||
|
||||
def setup
|
||||
@connection = ActiveRecord::Base.connection
|
||||
|
||||
@connection.transaction do
|
||||
@connection.create_table('commenteds', comment: 'A table with comment', force: true) do |t|
|
||||
t.string 'name', comment: 'Comment should help clarify the column purpose'
|
||||
t.boolean 'obvious', comment: 'Question is: should you comment obviously named objects?'
|
||||
t.string 'content'
|
||||
t.index 'name', comment: %Q["Very important" index that powers all the performance.\nAnd it's fun!]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
teardown do
|
||||
@connection.drop_table 'commenteds', if_exists: true
|
||||
end
|
||||
|
||||
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
|
||||
|
||||
def test_column_created_in_block
|
||||
Commented.reset_column_information
|
||||
column = Commented.columns_hash['name']
|
||||
assert_equal :string, column.type
|
||||
assert_equal 'Comment should help clarify the column purpose', column.comment
|
||||
end
|
||||
|
||||
def test_add_column_with_comment_later
|
||||
@connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
|
||||
Commented.reset_column_information
|
||||
column = Commented.columns_hash['rating']
|
||||
|
||||
assert_equal :integer, column.type
|
||||
assert_equal 'I am running out of imagination', column.comment
|
||||
end
|
||||
|
||||
def test_add_index_with_comment_later
|
||||
@connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
|
||||
index = @connection.indexes('commenteds').find { |idef| idef.name == 'idx_obvious' }
|
||||
assert_equal 'We need to see obvious comments', index.comment
|
||||
end
|
||||
|
||||
def test_add_comment_to_column
|
||||
@connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
|
||||
|
||||
Commented.reset_column_information
|
||||
column = Commented.columns_hash['content']
|
||||
|
||||
assert_equal :string, column.type
|
||||
assert_equal 'Whoa, content describes itself!', column.comment
|
||||
end
|
||||
|
||||
def test_remove_comment_from_column
|
||||
@connection.change_column :commenteds, :obvious, :string, comment: nil
|
||||
|
||||
Commented.reset_column_information
|
||||
column = Commented.columns_hash['obvious']
|
||||
|
||||
assert_equal :string, column.type
|
||||
assert_nil column.comment
|
||||
end
|
||||
|
||||
def test_schema_dump_with_comments
|
||||
# Do all the stuff from other tests
|
||||
@connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
|
||||
@connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
|
||||
@connection.change_column :commenteds, :obvious, :string, comment: nil
|
||||
@connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
|
||||
# And check that these changes are reflected in dump
|
||||
output = dump_table_schema 'commenteds'
|
||||
assert_match %r[create_table "commenteds",.+\s+comment: "A table with comment"], output
|
||||
assert_match %r[t\.string\s+"name",\s+comment: "Comment should help clarify the column purpose"], output
|
||||
assert_match %r[t\.string\s+"obvious"\n], output
|
||||
assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
|
||||
assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
|
||||
assert_match %r[add_index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
|
||||
assert_match %r[add_index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -355,6 +355,13 @@ end
|
|||
will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
|
||||
(when using MySQL or MariaDB, the default is `ENGINE=InnoDB`).
|
||||
|
||||
Also you can pass the `:comment` option with any description for the table
|
||||
that will be stored in database itself and can be viewed with database administration
|
||||
tools, such as MySQL Workbench or PgAdmin III. It's highly recommended to specify
|
||||
comments in migrations for applications with large databases as it helps people
|
||||
to understand data model and generate documentation.
|
||||
Currently only MySQL and PostgreSQL supports comments.
|
||||
|
||||
### Creating a Join Table
|
||||
|
||||
The migration method `create_join_table` creates an HABTM (has and belongs to
|
||||
|
@ -454,6 +461,7 @@ number of digits after the decimal point.
|
|||
are using a dynamic value (such as a date), the default will only be calculated
|
||||
the first time (i.e. on the date the migration is applied).
|
||||
* `index` Adds an index for the column.
|
||||
* `comment` Adds a comment for the column.
|
||||
|
||||
Some adapters may support additional options; see the adapter specific API docs
|
||||
for further information.
|
||||
|
|
Loading…
Reference in a new issue