pg_saurus 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.markdown +341 -0
  3. data/lib/colorized_text.rb +33 -0
  4. data/lib/core_ext/active_record/connection_adapters/abstract/schema_statements.rb +155 -0
  5. data/lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb +191 -0
  6. data/lib/core_ext/active_record/errors.rb +6 -0
  7. data/lib/core_ext/active_record/schema_dumper.rb +42 -0
  8. data/lib/pg_saurus/connection_adapters/abstract_adapter/comment_methods.rb +80 -0
  9. data/lib/pg_saurus/connection_adapters/abstract_adapter/foreigner_methods.rb +67 -0
  10. data/lib/pg_saurus/connection_adapters/abstract_adapter/index_methods.rb +6 -0
  11. data/lib/pg_saurus/connection_adapters/abstract_adapter/schema_methods.rb +20 -0
  12. data/lib/pg_saurus/connection_adapters/abstract_adapter.rb +20 -0
  13. data/lib/pg_saurus/connection_adapters/foreign_key_definition.rb +5 -0
  14. data/lib/pg_saurus/connection_adapters/index_definition.rb +8 -0
  15. data/lib/pg_saurus/connection_adapters/postgresql_adapter/comment_methods.rb +114 -0
  16. data/lib/pg_saurus/connection_adapters/postgresql_adapter/extension_methods.rb +124 -0
  17. data/lib/pg_saurus/connection_adapters/postgresql_adapter/foreigner_methods.rb +221 -0
  18. data/lib/pg_saurus/connection_adapters/postgresql_adapter/index_methods.rb +42 -0
  19. data/lib/pg_saurus/connection_adapters/postgresql_adapter/schema_methods.rb +58 -0
  20. data/lib/pg_saurus/connection_adapters/postgresql_adapter/translate_exception.rb +20 -0
  21. data/lib/pg_saurus/connection_adapters/postgresql_adapter/view_methods.rb +17 -0
  22. data/lib/pg_saurus/connection_adapters/postgresql_adapter.rb +28 -0
  23. data/lib/pg_saurus/connection_adapters/table/comment_methods.rb +58 -0
  24. data/lib/pg_saurus/connection_adapters/table/foreigner_methods.rb +51 -0
  25. data/lib/pg_saurus/connection_adapters/table.rb +17 -0
  26. data/lib/pg_saurus/connection_adapters.rb +9 -0
  27. data/lib/pg_saurus/create_index_concurrently.rb +227 -0
  28. data/lib/pg_saurus/engine.rb +57 -0
  29. data/lib/pg_saurus/errors.rb +6 -0
  30. data/lib/pg_saurus/migration/command_recorder/comment_methods.rb +68 -0
  31. data/lib/pg_saurus/migration/command_recorder/extension_methods.rb +25 -0
  32. data/lib/pg_saurus/migration/command_recorder/foreigner_methods.rb +31 -0
  33. data/lib/pg_saurus/migration/command_recorder/schema_methods.rb +59 -0
  34. data/lib/pg_saurus/migration/command_recorder/view_methods.rb +31 -0
  35. data/lib/pg_saurus/migration/command_recorder.rb +17 -0
  36. data/lib/pg_saurus/migration.rb +4 -0
  37. data/lib/pg_saurus/schema_dumper/comment_methods.rb +51 -0
  38. data/lib/pg_saurus/schema_dumper/extension_methods.rb +29 -0
  39. data/lib/pg_saurus/schema_dumper/foreigner_methods.rb +63 -0
  40. data/lib/pg_saurus/schema_dumper/schema_methods.rb +27 -0
  41. data/lib/pg_saurus/schema_dumper/view_methods.rb +32 -0
  42. data/lib/pg_saurus/schema_dumper.rb +28 -0
  43. data/lib/pg_saurus/tools.rb +104 -0
  44. data/lib/pg_saurus/version.rb +4 -0
  45. data/lib/pg_saurus.rb +18 -0
  46. data/lib/tasks/pg_saurus_tasks.rake +4 -0
  47. metadata +226 -0
@@ -0,0 +1,191 @@
1
+ module ActiveRecord # :nodoc:
2
+ module ConnectionAdapters # :nodoc:
3
+ # Patched version: 3.1.3
4
+ # Patched methods::
5
+ # * indexes
6
+ class PostgreSQLAdapter
7
+ # Regex to find columns used in index statements
8
+ INDEX_COLUMN_EXPRESSION = /ON \w+(?: USING \w+ )?\((.+)\)/
9
+ # Regex to find where clause in index statements
10
+ INDEX_WHERE_EXPRESION = /WHERE (.+)$/
11
+
12
+ # Returns the list of all tables in the schema search path or a specified schema.
13
+ #
14
+ # == Patch:
15
+ # If current user is not `postgres` original method return all tables from all schemas
16
+ # without schema prefix. This disables such behavior by querying only default schema.
17
+ # Tables with schemas will be queried later.
18
+ #
19
+ def tables(name = nil)
20
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
21
+ SELECT tablename
22
+ FROM pg_tables
23
+ WHERE schemaname = ANY (ARRAY['public'])
24
+ SQL
25
+ end
26
+
27
+ # Checks if index exists for given table.
28
+ #
29
+ # == Patch:
30
+ # Search using provided schema if table_name includes schema name.
31
+ #
32
+ def index_name_exists?(table_name, index_name, default)
33
+ schema, table = Utils.extract_schema_and_table(table_name)
34
+ schemas = schema ? "ARRAY['#{schema}']" : 'current_schemas(false)'
35
+
36
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
37
+ SELECT COUNT(*)
38
+ FROM pg_class t
39
+ INNER JOIN pg_index d ON t.oid = d.indrelid
40
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
41
+ WHERE i.relkind = 'i'
42
+ AND i.relname = '#{index_name}'
43
+ AND t.relname = '#{table}'
44
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (#{schemas}) )
45
+ SQL
46
+ end
47
+
48
+ # Returns an array of indexes for the given table.
49
+ #
50
+ # == Patch 1 reason:
51
+ # Since {ActiveRecord::SchemaDumper#tables} is patched to process tables
52
+ # with a schema prefix, the {#indexes} method receives table_name as
53
+ # "<schema>.<table>". This patch allows it to handle table names with
54
+ # a schema prefix.
55
+ #
56
+ # == Patch 1:
57
+ # Search using provided schema if table_name includes schema name.
58
+ #
59
+ # == Patch 2 reason:
60
+ # {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#indexes} is patched
61
+ # to support partial indexes using :where clause.
62
+ #
63
+ # == Patch 2:
64
+ # Search the postgres indexdef for the where clause and pass the output to
65
+ # the custom {PgSaurus::ConnectionAdapters::IndexDefinition}
66
+ #
67
+ def indexes(table_name, name = nil)
68
+ schema, table = Utils.extract_schema_and_table(table_name)
69
+ schemas = schema ? "ARRAY['#{schema}']" : 'current_schemas(false)'
70
+
71
+ result = query(<<-SQL, name)
72
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid, am.amname
73
+ FROM pg_class t
74
+ INNER JOIN pg_index d ON t.oid = d.indrelid
75
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
76
+ INNER JOIN pg_am am ON i.relam = am.oid
77
+ WHERE i.relkind = 'i'
78
+ AND d.indisprimary = 'f'
79
+ AND t.relname = '#{table}'
80
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (#{schemas}) )
81
+ ORDER BY i.relname
82
+ SQL
83
+
84
+ result.map do |row|
85
+ index = {
86
+ :name => row[0],
87
+ :unique => row[1] == 't',
88
+ :keys => row[2].split(" "),
89
+ :definition => row[3],
90
+ :id => row[4],
91
+ :access_method => row[5]
92
+ }
93
+
94
+ column_names = find_column_names(table_name, index)
95
+
96
+ unless column_names.empty?
97
+ where = find_where_statement(index)
98
+ lengths = find_lengths(index)
99
+
100
+ PgSaurus::ConnectionAdapters::IndexDefinition.new(table_name, index[:name], index[:unique], column_names, lengths, where, index[:access_method])
101
+ end
102
+ end.compact
103
+ end
104
+
105
+ # Find column names from index attributes. If the columns are virtual (ie
106
+ # this is an expression index) then it will try to return the functions
107
+ # that represent each column
108
+ #
109
+ # @param [String] table_name the name of the table
110
+ # @param [Hash] index index attributes
111
+ # @return [Array]
112
+ def find_column_names(table_name, index)
113
+ columns = Hash[query(<<-SQL, "Columns for index #{index[:name]} on #{table_name}")]
114
+ SELECT a.attnum, a.attname
115
+ FROM pg_attribute a
116
+ WHERE a.attrelid = #{index[:id]}
117
+ AND a.attnum IN (#{index[:keys].join(",")})
118
+ SQL
119
+
120
+ column_names = columns.values_at(*index[:keys]).compact
121
+
122
+ if column_names.empty?
123
+ definition = index[:definition].sub(INDEX_WHERE_EXPRESION, '')
124
+ if column_expression = definition.match(INDEX_COLUMN_EXPRESSION)[1]
125
+ column_names = split_expression(column_expression).map do |functional_name|
126
+ remove_type(functional_name)
127
+ end
128
+ end
129
+ end
130
+
131
+ column_names
132
+ end
133
+
134
+ # Splits only on commas outside of parens
135
+ def split_expression(expression)
136
+ result = []
137
+ parens = 0
138
+ buffer = ""
139
+
140
+ expression.chars do |char|
141
+ case char
142
+ when ','
143
+ if parens == 0
144
+ result.push(buffer)
145
+ buffer = ""
146
+ next
147
+ end
148
+ when '('
149
+ parens += 1
150
+ when ')'
151
+ parens -= 1
152
+ end
153
+
154
+ buffer << char
155
+ end
156
+
157
+ result << buffer unless buffer.empty?
158
+ result
159
+ end
160
+
161
+ # Find where statement from index definition
162
+ #
163
+ # @param [Hash] index index attributes
164
+ # @return [String] where statement
165
+ def find_where_statement(index)
166
+ index[:definition].scan(INDEX_WHERE_EXPRESION).flatten[0]
167
+ end
168
+
169
+ # Find length of index
170
+ # TODO Update lengths once we merge in ActiveRecord code that supports it. -dresselm 20120305
171
+ #
172
+ # @param [Hash] index index attributes
173
+ # @return [Array]
174
+ def find_lengths(index)
175
+ []
176
+ end
177
+
178
+ # Remove type specification from stored Postgres index definitions
179
+ #
180
+ # @param [String] column_with_type the name of the column with type
181
+ # @return [String]
182
+ #
183
+ # @example
184
+ # remove_type("((col)::text")
185
+ # => "col"
186
+ def remove_type(column_with_type)
187
+ column_with_type.sub(/\((\w+)\)::\w+/, '\1')
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,6 @@
1
+ module ActiveRecord
2
+ # Raised when an DB operation cannot be carried out because the current
3
+ # database user lacks the required privileges.
4
+ class InsufficientPrivilege < WrappedDatabaseException
5
+ end
6
+ end
@@ -0,0 +1,42 @@
1
+ module ActiveRecord #:nodoc:
2
+ # Patched version: 3.1.3
3
+ # Patched methods::
4
+ # * indexes
5
+ class SchemaDumper #:nodoc:
6
+ # Writes out index-related details to the schema stream
7
+ #
8
+ # == Patch reason:
9
+ # {ActiveRecord::SchemaDumper#indexes} does not support writing out
10
+ # details related to partial indexes.
11
+ #
12
+ # == Patch:
13
+ # Append :where clause if there's a partial index
14
+ #
15
+ def indexes(table, stream)
16
+ if (indexes = @connection.indexes(table)).any?
17
+ add_index_statements = indexes.map do |index|
18
+ statement_parts = [
19
+ ('add_index ' + index.table.inspect),
20
+ index.columns.inspect,
21
+ (':name => ' + index.name.inspect),
22
+ ]
23
+ statement_parts << ':unique => true' if index.unique
24
+
25
+ index_lengths = (index.lengths || []).compact
26
+ statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
27
+
28
+ # Patch
29
+ # Append :where clause if a partial index
30
+ statement_parts << (':where => ' + index.where.inspect) if index.where
31
+
32
+ statement_parts << (':using => ' + index.access_method.inspect) unless index.access_method.downcase == 'btree'
33
+
34
+ ' ' + statement_parts.join(', ')
35
+ end
36
+
37
+ stream.puts add_index_statements.sort.join("\n")
38
+ stream.puts
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,80 @@
1
+ # Extends ActiveRecord::ConnectionAdapters::AbstractAdapter with
2
+ # empty methods for comments feature.
3
+ module PgSaurus::ConnectionAdapters::AbstractAdapter::CommentMethods
4
+ def supports_comments?
5
+ false
6
+ end
7
+
8
+ # Sets a comment on the given table.
9
+ #
10
+ # ===== Example
11
+ # ====== Creating a comment on phone_numbers table
12
+ # set_table_comment :phone_numbers, 'This table stores phone numbers that conform to the North American Numbering Plan.'
13
+ def set_table_comment(table_name, comment)
14
+ # Does nothing
15
+ end
16
+
17
+ # Sets a comment on a given column of a given table.
18
+ #
19
+ # ===== Example
20
+ # ====== Creating a comment on npa column of table phone_numbers
21
+ # set_column_comment :phone_numbers, :npa, 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.'
22
+ def set_column_comment(table_name, column_name, comment)
23
+ # Does nothing
24
+ end
25
+
26
+ # Sets comments on multiple columns. 'comments' is a hash of column_name => comment pairs.
27
+ #
28
+ # ===== Example
29
+ # ====== Setting comments on the columns of the phone_numbers table
30
+ # set_column_comments :phone_numbers, :npa => 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.',
31
+ # :nxx => 'Central Office Number'
32
+ def set_column_comments(table_name, comments)
33
+
34
+ end
35
+
36
+ # Sets the comment on the given index
37
+ #
38
+ # ===== Example
39
+ # ====== Setting comment on the index_pets_on_breed_id index
40
+ # set_index_comment 'index_pets_on_breed_id', 'Index on breed_id'
41
+ def set_index_comment(index_name, comment)
42
+
43
+ end
44
+
45
+ # Removes any comment from the given table.
46
+ #
47
+ # ===== Example
48
+ # ====== Removing comment from phone numbers table
49
+ # remove_table_comment :phone_numbers
50
+ def remove_table_comment(table_name)
51
+
52
+ end
53
+
54
+ # Removes any comment from the given column of a given table.
55
+ #
56
+ # ===== Example
57
+ # ====== Removing comment from the npa column of table phone_numbers
58
+ # remove_column_comment :phone_numbers, :npa
59
+ def remove_column_comment(table_name, column_name)
60
+
61
+ end
62
+
63
+ # Removes any comment from the given columns of a given table.
64
+ #
65
+ # ===== Example
66
+ # ====== Removing comment from the npa and nxx columns of table phone_numbers
67
+ # remove_column_comments :phone_numbers, :npa, :nxx
68
+ def remove_column_comments(table_name, *column_names)
69
+
70
+ end
71
+
72
+ # Removes the comment from the given index
73
+ #
74
+ # ===== Example
75
+ # ====== Removing comment from the index_pets_on_breed_id index
76
+ # remove_index_comment :index_pets_on_breed_id
77
+ def remove_index_comment(index_name)
78
+
79
+ end
80
+ end
@@ -0,0 +1,67 @@
1
+ # Extends ActiveRecord::ConnectionAdapters::AbstractAdapter with
2
+ # empty methods for foreign keys feature.
3
+ module PgSaurus::ConnectionAdapters::AbstractAdapter::ForeignerMethods
4
+ def supports_foreign_keys?
5
+ false
6
+ end
7
+
8
+ # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
9
+ #
10
+ # The foreign key will be named after the from and to tables unless you pass
11
+ # <tt>:name</tt> as an option.
12
+ #
13
+ # ===== Examples
14
+ # ====== Creating a foreign key
15
+ # add_foreign_key(:comments, :posts)
16
+ # generates
17
+ # ALTER TABLE `comments` ADD CONSTRAINT
18
+ # `comments_post_id_fk` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
19
+ #
20
+ # ====== Creating a named foreign key
21
+ # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
22
+ # generates
23
+ # ALTER TABLE `comments` ADD CONSTRAINT
24
+ # `comments_belongs_to_posts` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
25
+ #
26
+ # ====== Creating a cascading foreign_key on a custom column
27
+ # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
28
+ # generates
29
+ # ALTER TABLE `people` ADD CONSTRAINT
30
+ # `people_best_friend_id_fk` FOREIGN KEY (`best_friend_id`) REFERENCES `people` (`id`)
31
+ # ON DELETE SET NULL
32
+ #
33
+ # === Supported options
34
+ # [:column]
35
+ # Specify the column name on the from_table that references the to_table. By default this is guessed
36
+ # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
37
+ # as the default <tt>:column</tt>.
38
+ # [:primary_key]
39
+ # Specify the column name on the to_table that is referenced by this foreign key. By default this is
40
+ # assumed to be "id".
41
+ # [:name]
42
+ # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
43
+ # [:dependent]
44
+ # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
45
+ # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
46
+ # [:options]
47
+ # Any extra options you want appended to the foreign key definition.
48
+ def add_foreign_key(from_table, to_table, options = {})
49
+ end
50
+
51
+ # Remove the given foreign key from the table.
52
+ #
53
+ # ===== Examples
54
+ # ====== Remove the suppliers_company_id_fk in the suppliers table.
55
+ # remove_foreign_key :suppliers, :companies
56
+ # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
57
+ # remove_foreign_key :accounts, :column => :branch_id
58
+ # ====== Remove the foreign key named party_foreign_key in the accounts table.
59
+ # remove_foreign_key :accounts, :name => :party_foreign_key
60
+ def remove_foreign_key(from_table, options)
61
+ end
62
+
63
+ # Return the foreign keys for the schema_dumper
64
+ def foreign_keys(table_name)
65
+ []
66
+ end
67
+ end
@@ -0,0 +1,6 @@
1
+ # Extends ActiveRecord::ConnectionAdapters::AbstractAdapter.
2
+ module PgSaurus::ConnectionAdapters::AbstractAdapter::IndexMethods
3
+ def supports_partial_index?
4
+ false
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ # Extends ActiveRecord::ConnectionAdapters::AbstractAdapter
2
+ # with methods for multi-schema support.
3
+ module PgSaurus::ConnectionAdapters::AbstractAdapter::SchemaMethods
4
+
5
+ # Provide :schema option to +create_table+ method.
6
+ def create_table_with_schema_option(table_name, options = {}, &block)
7
+ options = options.dup
8
+ schema_name = options.delete(:schema)
9
+ table_name = "#{schema_name}.#{table_name}" if schema_name
10
+ create_table_without_schema_option(table_name, options, &block)
11
+ end
12
+
13
+ # Provide :schema option to +drop_table+ method.
14
+ def drop_table_with_schema_option(table_name, options = {})
15
+ options = options.dup
16
+ schema_name = options.delete(:schema)
17
+ table_name = "#{schema_name}.#{table_name}" if schema_name
18
+ drop_table_without_schema_option(table_name, options)
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # Extends ActiveRecord::ConnectionAdapters::AbstractAdapter class.
2
+ module PgSaurus::ConnectionAdapters::AbstractAdapter
3
+ extend ActiveSupport::Autoload
4
+ extend ActiveSupport::Concern
5
+
6
+ autoload :CommentMethods
7
+ autoload :ForeignerMethods
8
+ autoload :SchemaMethods
9
+ autoload :IndexMethods
10
+
11
+ include CommentMethods
12
+ include ForeignerMethods
13
+ include SchemaMethods
14
+ include IndexMethods
15
+
16
+ included do
17
+ alias_method_chain :create_table, :schema_option
18
+ alias_method_chain :drop_table , :schema_option
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ module PgSaurus::ConnectionAdapters
2
+ # Structure to store information about foreign keys related to from_table.
3
+ class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options)
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module PgSaurus::ConnectionAdapters
2
+ # Structure to store index parameters
3
+ # Overrides ActiveRecord::ConnectionAdapters::IndexDefinition
4
+ # with the additional :where parameter.
5
+ class IndexDefinition < Struct.new( :table, :name, :unique, :columns,
6
+ :lengths, :where, :access_method )
7
+ end
8
+ end
@@ -0,0 +1,114 @@
1
+ # Provides methods to extend {ActiveRecord::ConnectionAdapters::PostgreSQLAdapter}
2
+ # to support comments feature.
3
+ module PgSaurus::ConnectionAdapters::PostgreSQLAdapter::CommentMethods
4
+ def supports_comments?
5
+ true
6
+ end
7
+
8
+ # Executes SQL to set comment on table
9
+ # @param [String, Symbol] table_name name of table to set a comment on
10
+ # @param [String] comment
11
+ def set_table_comment(table_name, comment)
12
+ sql = "COMMENT ON TABLE #{quote_table_name(table_name)} IS $$#{comment}$$;"
13
+ execute sql
14
+ end
15
+
16
+ # Executes SQL to set comment on column.
17
+ # @param [String, Symbol] table_name
18
+ # @param [String, Symbol] column_name
19
+ # @param [String] comment
20
+ def set_column_comment(table_name, column_name, comment)
21
+ sql = "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS $$#{comment}$$;"
22
+ execute sql
23
+ end
24
+
25
+ # Sets comments on columns of passed table.
26
+ # @param [String, Symbol] table_name
27
+ # @param [Hash] comments every key is a column name and value is a comment.
28
+ def set_column_comments(table_name, comments)
29
+ comments.each_pair do |column_name, comment|
30
+ set_column_comment table_name, column_name, comment
31
+ end
32
+ end
33
+
34
+ # Sets the given comment on the given index
35
+ # @param [String, Symbol] index_name The name of the index
36
+ # @param [String, Symbol] comment The comment to set on the index
37
+ def set_index_comment(index_name, comment)
38
+ sql = "COMMENT ON INDEX #{quote_string(index_name)} IS $$#{comment}$$;"
39
+ execute sql
40
+ end
41
+
42
+ # Executes SQL to remove comment on passed table.
43
+ # @param [String, Symbol] table_name
44
+ def remove_table_comment(table_name)
45
+ sql = "COMMENT ON TABLE #{quote_table_name(table_name)} IS NULL;"
46
+ execute sql
47
+ end
48
+
49
+ # Executes SQL to remove comment on column.
50
+ # @param [String, Symbol] table_name
51
+ # @param [String, Symbol] column_name
52
+ def remove_column_comment(table_name, column_name)
53
+ sql = "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS NULL;"
54
+ execute sql
55
+ end
56
+
57
+ # Remove comments on passed table columns.
58
+ def remove_column_comments(table_name, *column_names)
59
+ column_names.each do |column_name|
60
+ remove_column_comment table_name, column_name
61
+ end
62
+ end
63
+
64
+ # Removes any comment from the given index
65
+ # @param [String, Symbol] index_name The name of the index
66
+ def remove_index_comment(index_name)
67
+ sql = "COMMENT ON INDEX #{quote_string(index_name)} IS NULL;"
68
+ execute sql
69
+ end
70
+
71
+ # Fetches all comments related to passed table.
72
+ # I returns table comment and column comments as well.
73
+ # ===Example
74
+ # comments("users") # => [[ "" , "Comment on table" ],
75
+ # ["id" , "Comment on id column" ],
76
+ # ["email", "Comment on email column"]]
77
+ def comments(table_name)
78
+ relation_name, schema_name = table_name.split(".", 2).reverse
79
+ schema_name ||= :public
80
+
81
+ com = select_all <<-SQL
82
+ SELECT a.attname AS column_name, d.description AS comment
83
+ FROM pg_description d
84
+ JOIN pg_class c on c.oid = d.objoid
85
+ LEFT OUTER JOIN pg_attribute a ON c.oid = a.attrelid AND a.attnum = d.objsubid
86
+ JOIN pg_namespace ON c.relnamespace = pg_namespace.oid
87
+ WHERE c.relkind = 'r' AND c.relname = '#{relation_name}' AND
88
+ pg_namespace.nspname = '#{schema_name}'
89
+ SQL
90
+ com.map do |row|
91
+ [ row['column_name'], row['comment'] ]
92
+ end
93
+ end
94
+
95
+ # Fetches index comments
96
+ # returns an Array of Arrays, each element representing a single index with comment as
97
+ # [ 'schema_name', 'index_name', 'comment' ]
98
+ def index_comments
99
+ query = <<-SQL
100
+ SELECT c.relname AS index_name, d.description AS comment, pg_namespace.nspname AS schema_name
101
+ FROM pg_description d
102
+ JOIN pg_class c ON c.oid = d.objoid
103
+ JOIN pg_namespace ON c.relnamespace = pg_namespace.oid
104
+ WHERE c.relkind = 'i'
105
+ ORDER BY schema_name, index_name
106
+ SQL
107
+
108
+ com = select_all(query)
109
+
110
+ com.map do |row|
111
+ [ row['schema_name'], row['index_name'], row['comment'] ]
112
+ end
113
+ end
114
+ end