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,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