pg_saurus 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,227 @@
1
+ # Adds ability to configure in migration how index will be created.
2
+ # See more details how to create index concurrently in PostgreSQL at
3
+ # (see http://www.postgresql.org/docs/9.2/static/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY).
4
+ #
5
+ # There are several things you should be aware when use option to create index
6
+ # concurrently.
7
+ # Index can not be created concurrently inside transaction and such indexes
8
+ # creation will be postponed till migration transaction will be closed.
9
+ # In case of migration failure and transaction was rolled back indexes will not
10
+ # be created concurrently. But if indexes which should be created concurrently
11
+ # run with errors migration's transaction won't be rolled back. Error in that
12
+ # case will be raised and migration process will be stoped.
13
+ #
14
+ # Migrations can not ensure that all indexes that tend to be created
15
+ # concurrently were created even if the query for such index creation run
16
+ # without errors. Such indexes creation are deferred because of its nature.
17
+ # So, it's up to you to ensure that indexes was really created or remove
18
+ # partially created invalid indexes.
19
+ #
20
+ # :concurrent_index option conflicts with :exclude_index option in method
21
+ # `add_foreign_key`. So, if you put them together exception will be raised.
22
+ #
23
+ # @example
24
+ #
25
+ # class AddIndexToNameForUsers < ActiveRecord::Migration
26
+ # def change
27
+ # add_index :users, :name, :concurrently => true
28
+ # end
29
+ # end
30
+ #
31
+ # # or with foreign key
32
+ #
33
+ # class AddForeignKeyToRoleIdForUsers < ActiveRecord::Migration
34
+ # def change
35
+ # add_foreign_key :users, :roles, :concurrent_index => true
36
+ # end
37
+ # end
38
+ #
39
+ module PgSaurus::CreateIndexConcurrently
40
+ # Provides ability to postpone index creation queries in migrations.
41
+ #
42
+ # Overrides `add_index` and `add_foreign_key` methods for migration to be
43
+ # able to prevent indexes creation inside scope of transaction if they have to
44
+ # be created concurrently.
45
+ # Allows to run creation of postponed indexes.
46
+ #
47
+ # This module included into ActiveRecord::Migration class to extend it with
48
+ # new features.
49
+ #
50
+ # All postponed index creation queries are stored inside migration instance.
51
+ module Migration
52
+ # @attribute postponed_queries
53
+ # @return [Array] list of arguments to call `add_index` method.
54
+ # @private
55
+ attr_accessor :postponed_queries
56
+ private :postponed_queries, :postponed_queries=
57
+
58
+
59
+ # Add a new index to the table. +column_name+ can be a single Symbol, or
60
+ # an Array of Symbols.
61
+ #
62
+ # @param [Symbol, String] table_name
63
+ # @param [Symbol, String, Array<Symbol, String>] column_name
64
+ # @param [optional, Hash] options
65
+ # @option options [Boolean] :unique
66
+ # @option options [Boolean] :concurrently
67
+ # @option options [String] :where
68
+ #
69
+ # @return [nil]
70
+ #
71
+ # @see ActiveRecord::ConnectionAdapters::SchemaStatements.add_index in pg_saurus gem
72
+ def add_index(table_name, column_name, options = {}, &block)
73
+ table_name = ::ActiveRecord::Migrator.proper_table_name(table_name)
74
+ # GOTCHA:
75
+ # checks if index should be created concurretnly then put it into
76
+ # the queue to wait till queue processing will be called (should be
77
+ # happended after closing transaction).
78
+ # Otherwise just delegate call to PgSaurus's `add_index`.
79
+ # Block is given for future compatibility.
80
+ # -- zekefast 2012-09-12
81
+ unless options[:concurrently]
82
+ return connection.add_index(table_name, column_name, options, &block)
83
+ end
84
+
85
+ enque(table_name, column_name, options, &block)
86
+ nil
87
+ end
88
+
89
+ # Add a foreign key.
90
+ #
91
+ # == Options:
92
+ # * :column
93
+ # * :primary_key
94
+ # * :dependent
95
+ # * :exclude_index [Boolean]
96
+ # * :concurrent_index [Boolean]
97
+ #
98
+ # @param [String, Symbol] from_table
99
+ # @param [String, Symbol] to_table
100
+ # @param [Hash] options
101
+ # @option options [String, Symbol] :column
102
+ # @option options [String, Symbol] :primary_key
103
+ # @option options [Hash] :dependent
104
+ # @option options [Boolean] :exclude_index
105
+ # @option options [Boolean] :concurrent_index
106
+ #
107
+ # @raise [ArgumentError] in case of conflicted option were set
108
+ #
109
+ # @see ::PgSaurus::ConnectionAdapters::PostgreSQLAdapter::ForeignerMethods.add_foreign_key
110
+ def add_foreign_key(from_table, to_table, options = {}, &block)
111
+ from_table = ::ActiveRecord::Migrator.proper_table_name(from_table)
112
+ concurrent_index = options[:concurrent_index]
113
+
114
+ if concurrent_index then
115
+ if options[:exclude_index]
116
+ raise ArgumentError, 'Conflicted options(exclude_index, concurrent_index) was found, both are set to true.'
117
+ end
118
+
119
+ options[:column] ||= connection.id_column_name_from_table_name(to_table)
120
+ options = options.merge(:concurrently => concurrent_index)
121
+
122
+ index_options = { :concurrently => true }
123
+ enque(from_table, options[:column], index_options)
124
+ end
125
+
126
+ # GOTCHA:
127
+ # proceed foreign key creation, but giving :concurrent_index => true
128
+ # prevent normal index creation in PgSaurus's `add_foreign_key`.
129
+ # So, postponed creation could be done after transaction.
130
+ # -- zekefast 2012-09-12
131
+ connection.add_foreign_key(from_table, to_table, options, &block)
132
+ end
133
+
134
+ # Execute all postponed index creation.
135
+ #
136
+ # @return [::PgSaurus::CreateIndexConcurrently::Migration]
137
+ def process_postponed_queries
138
+ Array(@postponed_queries).each do |arguments, block|
139
+ connection.add_index(*arguments, &block)
140
+ end
141
+
142
+ clear_queue
143
+
144
+ self
145
+ end
146
+
147
+ # Clean postponed queries queue.
148
+ #
149
+ # @return [::PgSaurus::CreateIndexConcurrently::Migration] migration
150
+ def clear_queue
151
+ @postponed_queries = []
152
+
153
+ self
154
+ end
155
+ private :clear_queue
156
+
157
+ # Add to the queue add_index call parameters to be able execute call later.
158
+ #
159
+ # @param [Array] arguments
160
+ # @param [Proc] block
161
+ #
162
+ # @return [::PgSaurus::CreateIndexConcurrently::Migration]
163
+ def enque(*arguments, &block)
164
+ @postponed_queries ||= []
165
+ @postponed_queries << [arguments, block]
166
+
167
+ self
168
+ end
169
+ private :enque
170
+ end
171
+
172
+ # Allows `process_postponed_queries` to be called on MigrationProxy instances.
173
+ # So, (see ::PgSaurus::CreateIndexConcurrently::Migrator) could run index
174
+ # creation concurrently.
175
+ #
176
+ # Default delegation in (see ActiveRecord::MigrationProxy) allows to call
177
+ # only several methods.
178
+ module MigrationProxy
179
+ # :nodoc:
180
+ def self.included(klass)
181
+ klass.delegate :process_postponed_queries, :to => :migration
182
+ end
183
+ end
184
+
185
+ # Run postponed index creation for each migration.
186
+ #
187
+ # This module included into (see ::ActiveRecord::Migrator) class to make possible
188
+ # to execute queries for postponed index creation after closing migration's
189
+ # transaction.
190
+ #
191
+ # @see ::ActiveRecord::Migrator.migrate
192
+ # @see ::ActiveRecord::Migrator.ddl_transaction
193
+ module Migrator
194
+ extend ActiveSupport::Concern
195
+
196
+ # :nodoc:
197
+ def self.included(klass)
198
+ klass.alias_method_chain :ddl_transaction, :postponed_queries
199
+ end
200
+
201
+ # Override (see ::ActiveRecord::Migrator.ddl_transaction) to call
202
+ # (see ::PgSaurus::CreateIndexConcurrently::Migration.process_postponed_queries)
203
+ # immediately after transaction.
204
+ #
205
+ # @see ::ActiveRecord::Migrator.ddl_transaction
206
+ def ddl_transaction_with_postponed_queries(*args, &block)
207
+ ddl_transaction_without_postponed_queries(*args, &block)
208
+
209
+ # GOTCHA:
210
+ # This might be a bit tricky, but I've decided that this is the best
211
+ # way to retrieve migration instance after closing transaction.
212
+ # The problem that (see ::ActiveRecord::Migrator) doesn't provide any
213
+ # access to recently launched migration. All logic to iterate through
214
+ # set of migrations incapsulated in (see ::ActiveRecord::Migrator.migrate)
215
+ # method.
216
+ # So, to get access to migration you need to override `migrate` method
217
+ # and duplicated all logic inside it, plus add call to
218
+ # `process_postponed_queries`.
219
+ # I've decided this is less forward compatible then retrieving
220
+ # value of `migration` variable in context where block
221
+ # given to `ddl_transaction` method was created.
222
+ # -- zekefast 2012-09-12
223
+ migration = block.binding.eval('migration')
224
+ migration.process_postponed_queries
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,57 @@
1
+ module PgSaurus
2
+ # :nodoc:
3
+ class Engine < Rails::Engine
4
+
5
+ initializer 'pg_saurus' do
6
+ ActiveSupport.on_load(:active_record) do
7
+ # load monkey patches
8
+ ['schema_dumper',
9
+ 'errors',
10
+ 'connection_adapters/postgresql_adapter',
11
+ 'connection_adapters/abstract/schema_statements'].each do |path|
12
+ require ::PgSaurus::Engine.root + 'lib/core_ext/active_record/' + path
13
+ end
14
+
15
+ ActiveRecord::SchemaDumper.class_eval { include ::PgSaurus::SchemaDumper }
16
+
17
+ if defined?(ActiveRecord::Migration::CommandRecorder)
18
+ ActiveRecord::Migration::CommandRecorder.class_eval do
19
+ include ::PgSaurus::Migration::CommandRecorder
20
+ end
21
+ end
22
+
23
+ # Follow three include statements add support for concurrently
24
+ # index creation in migrations.
25
+ ActiveRecord::Migration.class_eval do
26
+ include ::PgSaurus::CreateIndexConcurrently::Migration
27
+ end
28
+ ActiveRecord::Migrator.class_eval do
29
+ include ::PgSaurus::CreateIndexConcurrently::Migrator
30
+ end
31
+ ActiveRecord::MigrationProxy.class_eval do
32
+ include ::PgSaurus::CreateIndexConcurrently::MigrationProxy
33
+ end
34
+
35
+ ActiveRecord::ConnectionAdapters::Table.module_eval do
36
+ include ::PgSaurus::ConnectionAdapters::Table
37
+ end
38
+
39
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
40
+ include ::PgSaurus::ConnectionAdapters::AbstractAdapter
41
+ end
42
+
43
+ if defined?(ActiveRecord::ConnectionAdapters::JdbcAdapter)
44
+ sql_adapter_class = ActiveRecord::ConnectionAdapters::JdbcAdapter
45
+ else
46
+ sql_adapter_class = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
47
+ end
48
+
49
+ sql_adapter_class.class_eval do
50
+ include ::PgSaurus::ConnectionAdapters::PostgreSQLAdapter
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ module PgSaurus # :nodoc:
2
+
3
+ # Raised when an unexpected index exists
4
+ class IndexExistsError < StandardError; end
5
+
6
+ end
@@ -0,0 +1,68 @@
1
+ # Provides methods to extend ActiveRecord::Migration::CommandRecorder to
2
+ # support comments feature.
3
+ module PgSaurus::Migration::CommandRecorder::CommentMethods
4
+ # :nodoc:
5
+ def set_table_comment(*args)
6
+ record(:set_table_comment, args)
7
+ end
8
+
9
+ # :nodoc:
10
+ def remove_table_comment(*args)
11
+ record(:remove_table_comment, args)
12
+ end
13
+
14
+ # :nodoc:
15
+ def set_column_comment(*args)
16
+ record(:set_column_comment, args)
17
+ end
18
+
19
+ # :nodoc:
20
+ def set_column_comments(*args)
21
+ record(:set_column_comments, args)
22
+ end
23
+
24
+ # :nodoc:
25
+ def remove_column_comment(*args)
26
+ record(:remove_column_comment, args)
27
+ end
28
+
29
+ # :nodoc:
30
+ def remove_column_comments(*args)
31
+ record(:remove_column_comments, args)
32
+ end
33
+
34
+ # :nodoc:
35
+ def set_index_comment(*args)
36
+ record(:set_index_comment, args)
37
+ end
38
+
39
+ # :nodoc:
40
+ def remove_index_comment(*args)
41
+ record(:remove_index_comment, args)
42
+ end
43
+
44
+ # :nodoc:
45
+ def invert_set_table_comment(args)
46
+ table_name = args.first
47
+ [:remove_table_comment, [table_name]]
48
+ end
49
+
50
+ # :nodoc:
51
+ def invert_set_column_comment(args)
52
+ table_name = args[0]
53
+ column_name = args[1]
54
+ [:remove_column_comment, [table_name, column_name]]
55
+ end
56
+
57
+ # :nodoc:
58
+ def invert_set_column_comments(args)
59
+ i_args = [args[0]] + args[1].collect{|name, value| name }
60
+ [:remove_column_comments, i_args]
61
+ end
62
+
63
+ # :nodoc:
64
+ def invert_set_index_comment(args)
65
+ index_name = args.first
66
+ [:remove_index_comment, [index_name]]
67
+ end
68
+ end
@@ -0,0 +1,25 @@
1
+ # Provides methods to extend ActiveRecord::Migration::CommandRecorder to
2
+ # support extensions feature.
3
+ module PgSaurus::Migration::CommandRecorder::ExtensionMethods
4
+ # :nodoc:
5
+ def create_extension(*args)
6
+ record(:create_extension, args)
7
+ end
8
+
9
+ # :nodoc:
10
+ def drop_extension(*args)
11
+ record(:drop_extension, args)
12
+ end
13
+
14
+ # :nodoc:
15
+ def invert_create_extension(args)
16
+ extension_name = args.first
17
+ [:drop_extension, [extension_name]]
18
+ end
19
+
20
+ # :nodoc:
21
+ def invert_drop_extension(args)
22
+ extension_name = args.first
23
+ [:create_extension, [extension_name]]
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # Provides methods to extend ActiveRecord::Migration::CommandRecorder to
2
+ # support foreign keys feature.
3
+ module PgSaurus::Migration::CommandRecorder::ForeignerMethods
4
+ # :nodoc:
5
+ def add_foreign_key(*args)
6
+ record(:add_foreign_key, args)
7
+ end
8
+
9
+ # :nodoc:
10
+ def remove_foreign_key(*args)
11
+ record(:remove_foreign_key, args)
12
+ end
13
+
14
+ # :nodoc:
15
+ def invert_add_foreign_key(args)
16
+ from_table, to_table, add_options = *args
17
+ add_options ||= {}
18
+ add_name_option = add_options[:name]
19
+ add_column_option = add_options[:column]
20
+
21
+ if add_name_option then
22
+ options = {:name => add_name_option}
23
+ elsif add_column_option
24
+ options = {:column => add_column_option}
25
+ else
26
+ options = to_table
27
+ end
28
+
29
+ [:remove_foreign_key, [from_table, options]]
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ # Provides methods to extend ActiveRecord::Migration::CommandRecorder to
2
+ # support multi schemas feature.
3
+ module PgSaurus::Migration::CommandRecorder::SchemaMethods
4
+ # :nodoc:
5
+ def create_schema(*args)
6
+ record(:create_schema, args)
7
+ end
8
+
9
+ # :nodoc:
10
+ def drop_schema(*args)
11
+ record(:drop_schema, args)
12
+ end
13
+
14
+ # :nodoc:
15
+ def move_table_to_schema(*args)
16
+ record(:move_table_to_schema, args)
17
+ end
18
+
19
+ # :nodoc:
20
+ def create_schema_if_not_exists(*args)
21
+ record(:create_schema_if_not_exists, args)
22
+ end
23
+
24
+ # :nodoc:
25
+ def drop_schema_if_exists(*args)
26
+ record(:drop_schema_if_exists, args)
27
+ end
28
+
29
+ # :nodoc:
30
+ def invert_create_schema(args)
31
+ [:drop_schema, [args.first]]
32
+ end
33
+
34
+ # :nodoc:
35
+ def invert_drop_schema(args)
36
+ [:create_schema, [args.first]]
37
+ end
38
+
39
+ # :nodoc:
40
+ def invert_move_table_to_schema(args)
41
+ table_name = args.first
42
+ current_schema = args.second
43
+
44
+ new_schema, table = ::PgSaurus::Tools.to_schema_and_table(table_name)
45
+
46
+ invert_args = ["#{current_schema}.#{table}", new_schema]
47
+ [:move_table_to_schema, invert_args]
48
+ end
49
+
50
+ # :nodoc:
51
+ def invert_create_schema_if_not_exists(*args)
52
+ [:drop_schema_if_exists, args]
53
+ end
54
+
55
+ # :nodoc:
56
+ def invert_drop_schema_if_exists(*args)
57
+ [:create_schema_if_not_exists, args]
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ # Provides methods to extend ActiveRecord::Migration::CommandRecorder to
2
+ # support view feature.
3
+ module PgSaurus::Migration::CommandRecorder::ViewMethods
4
+ # Create a PostgreSQL view.
5
+ #
6
+ # @param args [Array] view_name and view_definition
7
+ #
8
+ # @return [view]
9
+ def create_view(*args)
10
+ record(:create_view, args)
11
+ end
12
+
13
+ # Drop a view in the DB.
14
+ #
15
+ # @param args [Array] first argument is view_name
16
+ #
17
+ # @return [void]
18
+ def drop_view(*args)
19
+ record(:drop_view, args)
20
+ end
21
+
22
+ # Invert the creation of a view in the DB.
23
+ #
24
+ # @param args [Array] first argument is supposed to be name of view
25
+ #
26
+ # @return [void]
27
+ def invert_create_view(args)
28
+ [:drop_view, [args.first]]
29
+ end
30
+
31
+ end
@@ -0,0 +1,17 @@
1
+ # Provides methods to extend ActiveRecord::Migration::CommandRecorder to
2
+ # support pg_saurus features.
3
+ module PgSaurus::Migration::CommandRecorder
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :ExtensionMethods
7
+ autoload :SchemaMethods
8
+ autoload :CommentMethods
9
+ autoload :ForeignerMethods
10
+ autoload :ViewMethods
11
+
12
+ include ExtensionMethods
13
+ include SchemaMethods
14
+ include CommentMethods
15
+ include ForeignerMethods
16
+ include ViewMethods
17
+ end
@@ -0,0 +1,4 @@
1
+ module PgSaurus::Migration # :nodoc:
2
+ extend ActiveSupport::Autoload
3
+ autoload :CommandRecorder
4
+ end
@@ -0,0 +1,51 @@
1
+ # Extends ActiveRecord::SchemaDumper class to dump comments on tables and columns.
2
+ module PgSaurus::SchemaDumper::CommentMethods
3
+ # Hook ActiveRecord::SchemaDumper#table method to dump comments on
4
+ # table and columns.
5
+ def tables_with_comments(stream)
6
+ tables_without_comments(stream)
7
+
8
+ # Dump table and column comments
9
+ @connection.tables.sort.each do |table_name|
10
+ dump_comments(table_name, stream)
11
+ end
12
+
13
+ # Now dump index comments
14
+ unless (index_comments = @connection.index_comments).empty?
15
+ index_comments.each do |schema_name, table_name, raw_comment|
16
+ index_name = schema_name == 'public' ? "'#{table_name}'" : "'#{schema_name}.#{table_name}'"
17
+ comment = format_comment(raw_comment)
18
+ stream.puts " set_index_comment #{index_name}, '#{comment}'"
19
+ end
20
+ stream.puts
21
+ end
22
+ end
23
+
24
+ # Find all comments related to passed table and write appropriate
25
+ # statements to stream.
26
+ def dump_comments(table_name, stream)
27
+ unless (comments = @connection.comments(table_name)).empty?
28
+ comment_statements = comments.map do |row|
29
+ column_name = row[0]
30
+ comment = format_comment(row[1])
31
+
32
+ if column_name
33
+ " set_column_comment '#{table_name}', '#{column_name}', '#{comment}'"
34
+ else
35
+ " set_table_comment '#{table_name}', '#{comment}'"
36
+ end
37
+
38
+ end
39
+
40
+ stream.puts comment_statements.join("\n")
41
+ stream.puts
42
+ end
43
+ end
44
+ private :dump_comments
45
+
46
+ # Escape single quotes from comments.
47
+ def format_comment(comment)
48
+ comment.gsub(/'/, "\\\\'")
49
+ end
50
+ private :format_comment
51
+ end
@@ -0,0 +1,29 @@
1
+ # Extends ActiveRecord::SchemaDumper class to dump comments on tables and columns.
2
+ module PgSaurus::SchemaDumper::ExtensionMethods
3
+ # Hook ActiveRecord::SchemaDumper#header method to dump extensions in all
4
+ # schemas except for pg_catalog.
5
+ def header_with_extensions(stream)
6
+ header_without_extensions(stream)
7
+ dump_extensions(stream)
8
+ stream
9
+ end
10
+
11
+ # Dump current database extensions recreation commands to the given stream.
12
+ #
13
+ # @param [#puts] stream Stream to write to
14
+ def dump_extensions(stream)
15
+ extensions = @connection.pg_extensions
16
+ commands = extensions.map do |extension_name, options|
17
+ result = [%Q|create_extension "#{extension_name}"|]
18
+ result << %Q|:schema_name => "#{options[:schema_name]}"| unless options[:schema_name] == 'public'
19
+ result << %Q|:version => "#{options[:version]}"|
20
+ result.join(', ')
21
+ end
22
+
23
+ commands.each do |command|
24
+ stream.puts(" #{command}")
25
+ end
26
+
27
+ stream.puts
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ # Provides methods to extend ActiveRecord::SchemaDumper to dump
2
+ # foreign keys.
3
+ module PgSaurus::SchemaDumper::ForeignerMethods
4
+ # Hooks ActiveRecord::SchemaDumper#table method to dump foreign keys.
5
+ def tables_with_foreign_keys(stream)
6
+ tables_without_foreign_keys(stream)
7
+
8
+ table_names = @connection.tables.sort
9
+
10
+ table_names.sort.each do |table|
11
+ next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
12
+ case ignored
13
+ when String; table == ignored
14
+ when Regexp; table =~ ignored
15
+ else
16
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
17
+ end
18
+ end
19
+ foreign_keys(table, stream)
20
+ end
21
+ end
22
+
23
+
24
+ # Find all foreign keys on passed table and writes appropriate
25
+ # statements to stream.
26
+ def foreign_keys(table_name, stream)
27
+ if (foreign_keys = @connection.foreign_keys(table_name)).any?
28
+ add_foreign_key_statements = foreign_keys.map do |foreign_key|
29
+ options = foreign_key.options
30
+ table_from_key = foreign_key.to_table
31
+ statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ]
32
+ statement_parts << table_from_key.inspect
33
+ statement_parts << (':name => ' + options[:name].inspect)
34
+
35
+ column_from_options = options[:column]
36
+ primary_key_from_options = options[:primary_key]
37
+ dependent_from_options = options[:dependent]
38
+
39
+ if column_from_options != "#{table_from_key.singularize}_id"
40
+ statement_parts << (":column => #{column_from_options.inspect}")
41
+ end
42
+ if primary_key_from_options != 'id'
43
+ statement_parts << (":primary_key => #{primary_key_from_options.inspect}")
44
+ end
45
+ if dependent_from_options.present?
46
+ statement_parts << (":dependent => #{dependent_from_options.inspect}")
47
+ end
48
+
49
+ # Always exclude the index
50
+ # If an index was created in a migration, it will get dumped to the schema
51
+ # separately from the foreign key. This will raise an exception if
52
+ # add_foreign_key is run without :exclude_index => true.
53
+ statement_parts << (':exclude_index => true')
54
+
55
+ ' ' + statement_parts.join(', ')
56
+ end
57
+
58
+ stream.puts add_foreign_key_statements.sort.join("\n")
59
+ stream.puts
60
+ end
61
+ end
62
+ private :foreign_keys
63
+ end