dynamic_migrations 3.8.6 → 3.8.7

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/lib/dynamic_migrations/postgres/generator/enum.rb +13 -9
  4. data/lib/dynamic_migrations/postgres/generator/fragment.rb +1 -1
  5. data/lib/dynamic_migrations/postgres/generator/function.rb +13 -13
  6. data/lib/dynamic_migrations/postgres/generator/migration.rb +45 -7
  7. data/lib/dynamic_migrations/postgres/generator/table_migration.rb +2 -0
  8. data/lib/dynamic_migrations/postgres/generator.rb +100 -46
  9. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/extensions.rb +2 -2
  10. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/enums.rb +10 -10
  11. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +11 -11
  12. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +11 -11
  13. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +11 -11
  14. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +11 -11
  15. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +6 -6
  16. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +11 -11
  17. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +11 -11
  18. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +11 -11
  19. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +8 -8
  20. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +3 -3
  21. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +4 -4
  22. data/lib/dynamic_migrations/postgres/server/database/differences.rb +17 -17
  23. data/lib/dynamic_migrations/postgres/server/database/schema/enum.rb +7 -0
  24. data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +61 -1
  25. data/lib/dynamic_migrations/version.rb +1 -1
  26. data/sig/dynamic_migrations/postgres/generator/enum.rbs +2 -0
  27. data/sig/dynamic_migrations/postgres/generator/function.rbs +1 -0
  28. data/sig/dynamic_migrations/postgres/generator/migration.rbs +1 -0
  29. data/sig/dynamic_migrations/postgres/generator/schema_migration.rbs +2 -0
  30. data/sig/dynamic_migrations/postgres/generator/table_migration.rbs +3 -0
  31. data/sig/dynamic_migrations/postgres/generator.rbs +3 -1
  32. data/sig/dynamic_migrations/postgres/server/database/schema/enum.rbs +3 -0
  33. data/sig/dynamic_migrations/postgres/server/database/schema/table.rbs +3 -0
  34. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00405e194209e083433ebb55aa305922a16e24290ead4f240420602c9465bc90
4
- data.tar.gz: 57b7136d7eb1f7efc5382101cf24a4dc5ed4852e66a5f67faf368294e05632c0
3
+ metadata.gz: 443be07abb74aef74bd5d94de87ab2be7f227f81e1c401cb7bf8ebc55bc1c6af
4
+ data.tar.gz: f93c398273cb1ae28cf6d5b257adeda625f3c732952f327692fb3ef076798f7f
5
5
  SHA512:
6
- metadata.gz: bc594cb118caa67c0e9982f839c762e074b789b083328a1b69b3261297bd34055a53739f51cabb6f6e5685ea76f80eb4574b45649fd202bf213a579713842fac
7
- data.tar.gz: 8d42ee7274c26e25086eae4e9c2646f958f930a18fd03eb9c6262f734cf55af380499763e3508a05156a6f1baad4c7bc30007e9d242674d9594bf83e75a7bf1f
6
+ metadata.gz: c35b3c1d1834fea80cfdc2c9b14f611b5d76f005b408e0cbfd7592216780454647c519fbc5113bcd5a946a578320417cde47eeaa785fc097dc5062b79a310522
7
+ data.tar.gz: '09780db07d086da4fc27c5187770248b174d91e7c89a6f3021f1a87e42f97c3b4cf091e7ed510ef7e853e4da360f5b359bbc91a7375b94edd55d09c52e0f905a'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.8.7](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.6...v3.8.7) (2023-10-09)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * added detection of missing extensions and better error messages when normalizing check_constrains and action_conditions ([d82ff57](https://github.com/craigulliott/dynamic_migrations/commit/d82ff57f5b51c8636e87b40b8d6b016d11e6c72a))
9
+ * added enum value length validation ([d82ff57](https://github.com/craigulliott/dynamic_migrations/commit/d82ff57f5b51c8636e87b40b8d6b016d11e6c72a))
10
+ * also drastic performance improvement of migration circular dependency resolution ([2915b8f](https://github.com/craigulliott/dynamic_migrations/commit/2915b8f28c9202bd18f221cfafa2c4352a80fd2c))
11
+ * fixed regex for validating name within migration fragment (it was not allowing a single letter followed by an underscore) ([9b73eca](https://github.com/craigulliott/dynamic_migrations/commit/9b73ecac03816cc74f9b0d23084d0cfb09701012))
12
+ * migration generation now finds and resolves circular dependencies which exist through N migrations (it used to only resolve immediate circular dependencies) ([2915b8f](https://github.com/craigulliott/dynamic_migrations/commit/2915b8f28c9202bd18f221cfafa2c4352a80fd2c))
13
+ * providing the table dependency to all enum and function migrations if there is only one table which they rely on ([3d3708d](https://github.com/craigulliott/dynamic_migrations/commit/3d3708dfb7bdd2a83a477087ac22fd540ae1f881))
14
+ * reducing some log messages from info level to debug level ([cdf1200](https://github.com/craigulliott/dynamic_migrations/commit/cdf12000186a9e373d5085f83d2fb197c7796c6c))
15
+ * table migrations now allow enum related fragments ([b894bf1](https://github.com/craigulliott/dynamic_migrations/commit/b894bf14743c51b505693818407ba6bfb3f31571))
16
+ * updating log level for some log entries ([456a90e](https://github.com/craigulliott/dynamic_migrations/commit/456a90e7fe6da78667a84928b75217f89c344f35))
17
+
3
18
  ## [3.8.6](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.5...v3.8.6) (2023-10-08)
4
19
 
5
20
 
@@ -6,16 +6,8 @@ module DynamicMigrations
6
6
  end
7
7
 
8
8
  def create_enum enum, code_comment = nil
9
- # we only provide a table if the enum has a single column and they
10
- # are in the same schema, otherwise we can't reliable handle dependencies
11
- # so the enum will be created in the schema's migration
12
- enum_table = nil
13
- if enum.columns.count == 1 && enum.schema == enum.columns.first&.table&.schema
14
- enum_table = enum.columns.first&.table
15
- end
16
-
17
9
  add_fragment schema: enum.schema,
18
- table: enum_table,
10
+ table: optional_enum_table(enum),
19
11
  migration_method: :create_enum,
20
12
  object: enum,
21
13
  code_comment: code_comment,
@@ -35,6 +27,7 @@ module DynamicMigrations
35
27
  end
36
28
 
37
29
  add_fragment schema: updated_enum.schema,
30
+ table: optional_enum_table(updated_enum),
38
31
  migration_method: :add_enum_values,
39
32
  object: updated_enum,
40
33
  code_comment: code_comment,
@@ -47,6 +40,7 @@ module DynamicMigrations
47
40
 
48
41
  def drop_enum enum, code_comment = nil
49
42
  add_fragment schema: enum.schema,
43
+ table: optional_enum_table(enum),
50
44
  migration_method: :drop_enum,
51
45
  object: enum,
52
46
  code_comment: code_comment,
@@ -58,6 +52,7 @@ module DynamicMigrations
58
52
  # add a comment to a enum
59
53
  def set_enum_comment enum, code_comment = nil
60
54
  add_fragment schema: enum.schema,
55
+ table: optional_enum_table(enum),
61
56
  migration_method: :set_enum_comment,
62
57
  object: enum,
63
58
  code_comment: code_comment,
@@ -71,6 +66,7 @@ module DynamicMigrations
71
66
  # remove the comment from a enum
72
67
  def remove_enum_comment enum, code_comment = nil
73
68
  add_fragment schema: enum.schema,
69
+ table: optional_enum_table(enum),
74
70
  migration_method: :remove_enum_comment,
75
71
  object: enum,
76
72
  code_comment: code_comment,
@@ -78,6 +74,14 @@ module DynamicMigrations
78
74
  remove_enum_comment :#{enum.name}
79
75
  RUBY
80
76
  end
77
+
78
+ # we only provide a table to these migration fragments if the enum applies only to one table
79
+ # and that take is in the same schema as the enum
80
+ def optional_enum_table enum
81
+ # all the tables which use this enum
82
+ tables = enum.columns.map(&:table).uniq
83
+ (tables.count == 1 && tables.first&.schema == enum.schema) ? tables.first : nil
84
+ end
81
85
  end
82
86
  end
83
87
  end
@@ -15,7 +15,7 @@ module DynamicMigrations
15
15
  attr_reader :dependency_enum_name
16
16
 
17
17
  def initialize schema_name, table_name, migration_method, object_name, code_comment, content
18
- valid_name_regex = /\A[a-z][a-z0-9]+(_[a-z0-9]+)*\z/
18
+ valid_name_regex = /\A[a-z][a-z0-9]*(_[a-z0-9]+)*\z/
19
19
 
20
20
  unless schema_name.nil? || (schema_name.to_s.match valid_name_regex)
21
21
  raise InvalidNameError, "Invalid schema name `#{schema_name}`, must only be lowercase letters, numbers and underscores"
@@ -21,16 +21,8 @@ module DynamicMigrations
21
21
 
22
22
  fn_sql = function.definition.strip
23
23
 
24
- # we only provide a table if the function has a single trigger and they
25
- # are in the same schema, otherwise we can't reliable handle dependencies
26
- # so the function will be created in the schema's migration
27
- function_table = nil
28
- if function.triggers.count == 1 && function.schema == function.triggers.first&.table&.schema
29
- function_table = function.triggers.first&.table
30
- end
31
-
32
24
  add_fragment schema: function.schema,
33
- table: function_table,
25
+ table: optional_function_table(function),
34
26
  migration_method: :create_function,
35
27
  object: function,
36
28
  code_comment: code_comment,
@@ -47,7 +39,7 @@ module DynamicMigrations
47
39
  fn_sql = function.definition.strip
48
40
 
49
41
  add_fragment schema: function.schema,
50
- table: function.triggers.first&.table,
42
+ table: optional_function_table(function),
51
43
  migration_method: :update_function,
52
44
  object: function,
53
45
  code_comment: code_comment,
@@ -62,7 +54,7 @@ module DynamicMigrations
62
54
 
63
55
  def drop_function function, code_comment = nil
64
56
  add_fragment schema: function.schema,
65
- table: function.triggers.first&.table,
57
+ table: optional_function_table(function),
66
58
  migration_method: :drop_function,
67
59
  object: function,
68
60
  code_comment: code_comment,
@@ -74,7 +66,7 @@ module DynamicMigrations
74
66
  # add a comment to a function
75
67
  def set_function_comment function, code_comment = nil
76
68
  add_fragment schema: function.schema,
77
- table: function.triggers.first&.table,
69
+ table: optional_function_table(function),
78
70
  migration_method: :set_function_comment,
79
71
  object: function,
80
72
  code_comment: code_comment,
@@ -88,7 +80,7 @@ module DynamicMigrations
88
80
  # remove the comment from a function
89
81
  def remove_function_comment function, code_comment = nil
90
82
  add_fragment schema: function.schema,
91
- table: function.triggers.first&.table,
83
+ table: optional_function_table(function),
92
84
  migration_method: :remove_function_comment,
93
85
  object: function,
94
86
  code_comment: code_comment,
@@ -96,6 +88,14 @@ module DynamicMigrations
96
88
  remove_function_comment :#{function.name}
97
89
  RUBY
98
90
  end
91
+
92
+ # we only provide a table to these migration fragments if the function applies only to one table
93
+ # and that take is in the same schema as the function
94
+ def optional_function_table function
95
+ # all the tables which use this function
96
+ tables = function.triggers.map(&:table).uniq
97
+ (tables.count == 1 && tables.first&.schema == function.schema) ? tables.first : nil
98
+ end
99
99
  end
100
100
  end
101
101
  end
@@ -67,6 +67,39 @@ module DynamicMigrations
67
67
  @structure_templates = []
68
68
  end
69
69
 
70
+ def to_s
71
+ # because calling these methods will rise an error if there are no
72
+ # fragments, and this method is primarily used for debugging
73
+ if @fragments.any?
74
+ tds = table_dependencies
75
+ eds = enum_dependencies
76
+ fds = function_dependencies
77
+ else
78
+ tds = []
79
+ eds = []
80
+ fds = []
81
+ end
82
+ <<~PREVIEW.strip
83
+ # Migration content preview
84
+ # -------------------------
85
+ # Schema:#{@schema_name ? " #{@schema_name}" : ""}
86
+ # Table:#{@table_name ? " #{@table_name}" : ""}
87
+
88
+ # Table Dependencies (count: #{tds.count}):
89
+ #{(tds.any? ? "# " : "") + tds.map { |d| "Schema: `#{d[:schema_name]}` Table: `#{d[:table_name]}`" }.join("\n# ")}
90
+
91
+ # Enum Dependencies (count: #{eds.count}):
92
+ #{(eds.any? ? "# " : "") + eds.map { |d| "Schema: `#{d[:schema_name]}` Enum: `#{d[:enum_name]}`" }.join("\n# ")}
93
+
94
+ # Function Dependencies (count: #{fds.count}):
95
+ #{(fds.any? ? "# " : "") + fds.map { |d| "Schema: `#{d[:schema_name]}` Function: `#{d[:function_name]}`" }.join("\n# ")}
96
+
97
+ # Fragments (count: #{@fragments.count}):
98
+
99
+ #{@fragments.map(&:to_s).join("\n\n")}
100
+ PREVIEW
101
+ end
102
+
70
103
  # Add a migration fragment to this migration, if the migration is not
71
104
  # configured (via a structure template) to handle the method_name of the
72
105
  # fragment, then am error is raised. An error will also be raised if the
@@ -95,32 +128,37 @@ module DynamicMigrations
95
128
 
96
129
  # Return an array of table dependencies for this migration, this array comes from
97
130
  # combining any table dependencies from each fragment.
98
- # Will raise an error if no fragments have been provided.
131
+ # Will raise an error if no fragments are available.
99
132
  def table_dependencies
100
133
  raise NoFragmentsError if fragments.empty?
101
- @fragments.map(&:table_dependency).compact
134
+ @fragments.map(&:table_dependency).compact.uniq
102
135
  end
103
136
 
104
137
  # Return an array of function dependencies for this migration, this array comes from
105
138
  # combining any function dependencies from each fragment.
106
- # Will raise an error if no fragments have been provided.
139
+ # Will raise an error if no fragments are available.
107
140
  def function_dependencies
108
141
  raise NoFragmentsError if fragments.empty?
109
- @fragments.map(&:function_dependency).compact
142
+ @fragments.map(&:function_dependency).compact.uniq
110
143
  end
111
144
 
112
145
  # Return an array of enum dependencies for this migration, this array comes from
113
146
  # combining any enum dependencies from each fragment.
114
- # Will raise an error if no fragments have been provided.
147
+ # Will raise an error if no fragments are available.
115
148
  def enum_dependencies
116
149
  raise NoFragmentsError if fragments.empty?
117
- @fragments.map(&:enum_dependency).compact
150
+ @fragments.map(&:enum_dependency).compact.uniq
151
+ end
152
+
153
+ # returns the number of fragment within this migration which have the provided dependency
154
+ def fragments_with_table_dependency_count schema_name, table_name
155
+ @fragments.count { |f| f.is_dependent_on_table? schema_name, table_name }
118
156
  end
119
157
 
120
158
  # removes and returns any fragments which have a dependency on the table with the
121
159
  # provided schema_name and table_name, this is used for extracting fragments which
122
160
  # cause circular dependencies so they can be placed into their own migrations
123
- def extract_fragments_with_dependency schema_name, table_name
161
+ def extract_fragments_with_table_dependency schema_name, table_name
124
162
  results = @fragments.filter { |f| f.is_dependent_on_table? schema_name, table_name }
125
163
  # remove any of these from the internal array of fragments
126
164
  @fragments.filter! { |f| !f.is_dependent_on_table?(schema_name, table_name) }
@@ -15,6 +15,8 @@ module DynamicMigrations
15
15
  add_structure_template [:drop_table], "Remove Tables"
16
16
  add_structure_template [:create_table], "Create Table"
17
17
  add_structure_template [:remove_table_comment, :set_table_comment], "Tables"
18
+ add_structure_template [:remove_enum_comment, :drop_enum], "Drop Enums"
19
+ add_structure_template [:create_enum, :add_enum_values, :set_enum_comment], "Enums"
18
20
  add_structure_template [:add_column], "Additional Columns"
19
21
  add_structure_template [:change_column, :remove_column_comment, :set_column_comment], "Update Columns"
20
22
  add_structure_template [:add_primary_key, :set_primary_key_comment, :remove_primary_key_comment], "Primary Key"
@@ -34,10 +34,13 @@ module DynamicMigrations
34
34
 
35
35
  def initialize
36
36
  @fragments = []
37
+ @logger = Logging.logger[self]
37
38
  end
38
39
 
39
40
  # builds the final migrations
40
41
  def migrations
42
+ log.info "Generating migrations"
43
+
41
44
  # a hash to hold the generated migrations orgnized by their schema and table
42
45
  # this makes it easier and faster to work with them within this method
43
46
  database_migrations = {}
@@ -49,6 +52,7 @@ module DynamicMigrations
49
52
  # Process each fragment, and organize them into migrations. We create a shared
50
53
  # Migration for each table, and a single shared migration for any schema migrations
51
54
  # which do not relate to a table.
55
+ log.info " Organizing migration fragments"
52
56
  @fragments.each do |fragment|
53
57
  # The first time this schema is encountered we create an object to hold the migrations
54
58
  # and organize the different migrations.
@@ -87,32 +91,23 @@ module DynamicMigrations
87
91
 
88
92
  # Convert the hash of migrations into an array of migrations, this is
89
93
  # passed to the `circular_dependency?` method below, and any new migrations
90
- # requred to resolve circular dependencies will be added to this array
94
+ # required to resolve circular dependencies will be added to this array
91
95
  all_table_migrations = database_migrations.values.map { |m| m[:table_migrations].values }.flatten
92
96
 
93
- # iterate through all of the table migrations, and fix any circular dependencies caused
94
- # by foreign key constraints
95
- database_migrations.each do |schema_name, schema_migrations|
96
- # we only need to process the TableMigrations, as the SchemaMigration
97
- # never have dependencies
98
- schema_migrations[:table_migrations].values.each do |table_migration|
99
- # recursively test each table migration for circular dependencies
100
- table_migration.table_dependencies.each do |dependency|
101
- if circular_dependency? table_migration.schema_name, table_migration.table_name, dependency, all_table_migrations
102
- # remove the fragment which is causing the circular dependency
103
- removed_fragments = table_migration.extract_fragments_with_dependency dependency[:schema_name], dependency[:table_name]
104
- # create a new table migration for these fragments (there should only
105
- # be one, but we treat them as an array to futiure proof this)
106
- new_migration = TableMigration.new(schema_name, table_migration.table_name)
107
- # place these fragments in their own migration
108
- removed_fragments.each do |removed_fragment|
109
- new_migration.add_fragment removed_fragment
110
- end
111
- # add the new migration to the list of migrations
112
- schema_migrations[:additional_migrations] << new_migration
113
- end
114
- end
115
- end
97
+ # For each migration, we recursively traverse the dependency graph to detect and handle circular
98
+ # dependencies.
99
+ #
100
+ # Initially, all the fragments which pertain to a particular table are grouped together in
101
+ # the same migration. If a circular dependency between migrations is detected, then we simply
102
+ # pop the offending migration fragments out of the dedicated table migration and into a new
103
+ # migration. This allows the migration to be processed later, and resolves the circular dependency.
104
+ log.info " Resolving circular dependencies between migrations"
105
+ completed_table_migrations = []
106
+ all_table_migrations.each do |table_migration|
107
+ # skip it if it's already been processed
108
+ next if completed_table_migrations.include? table_migration
109
+ # recusrsively resolve the circular dependencies for this migration
110
+ resolve_circular_dependencies table_migration, all_table_migrations, database_migrations, completed_table_migrations
116
111
  end
117
112
 
118
113
  # Prepare a dependency sorter, this is used to sort the migrations via rubys included Tsort module
@@ -123,6 +118,7 @@ module DynamicMigrations
123
118
  # migration1 => [migration2, migration3],
124
119
  # migration3 => [migration2]
125
120
  # }
121
+ log.info " Preparing migrations for sorting"
126
122
  dependency_sorter = MigrationDependencySorter.new
127
123
  database_migrations.each do |schema_name, schema_migrations|
128
124
  if schema_migrations[:schema_migration]
@@ -161,13 +157,24 @@ module DynamicMigrations
161
157
  # if there is a schema migration, then it should always come first
162
158
  # so make the table migration depend on it
163
159
  deps << schema_migrations[:schema_migration] if schema_migrations[:schema_migration]
160
+
164
161
  # additional migrations are always dependent on the table migration which they came from
165
162
  table_migration = schema_migrations[:table_migrations][additional_migration.table_name]
166
163
  # if the table migration is not found, then it's safe to assume the table was created
167
164
  # by an earlier set of migrations
168
165
  unless table_migration.nil?
169
166
  deps << table_migration
167
+
168
+ # if the table migration has any dependencies on functions or enums, then add them
169
+ (table_migration.function_dependencies + table_migration.enum_dependencies).each do |dependency|
170
+ # functions are always added to a schema specific migration, if it does not exist then
171
+ # we can assume the function was added in a previous set of migrations
172
+ if (dependencies_schema_migration = database_migrations[dependency[:schema_name]] && database_migrations[dependency[:schema_name]][:schema_migration])
173
+ deps << dependencies_schema_migration
174
+ end
175
+ end
170
176
  end
177
+
171
178
  # if the additional_migration has any dependencies on other tables, then add them too
172
179
  additional_migration.table_dependencies.each do |dependency|
173
180
  # find the table migration which matches the dependency
@@ -178,19 +185,12 @@ module DynamicMigrations
178
185
  deps << dependent_migration
179
186
  end
180
187
  end
181
- # if the table migration has any dependencies on functions or enums, then add them
182
- (table_migration.function_dependencies + table_migration.enum_dependencies).each do |dependency|
183
- # functions are always added to a schema specific migration, if it does not exist then
184
- # we can assume the function was added in a previous set of migrations
185
- if (dependencies_schema_migration = database_migrations[dependency[:schema_name]] && database_migrations[dependency[:schema_name]][:schema_migration])
186
- deps << dependencies_schema_migration
187
- end
188
- end
189
188
  end
190
189
  end
191
190
 
192
191
  # sort the migrations so that they are executed in the correct order
193
192
  # the order is determined by their dependencies
193
+ log.info " Sorting migrations based on their dependencies"
194
194
  final_migrations = dependency_sorter.tsort
195
195
 
196
196
  # if any database only migrations exist, then add them to the front of the array here
@@ -210,23 +210,73 @@ module DynamicMigrations
210
210
 
211
211
  private
212
212
 
213
- def circular_dependency? schema_name, table_name, dependency, all_table_migrations
214
- # if the current dependency (schema_name and table_name) matches the original migration then we have a circular dependency
215
- if dependency[:schema_name] == schema_name && dependency[:table_name] == table_name
216
- true
217
- else
218
- # get all mirations which are for the same schema and table as the dependency
219
- dependent_migrations = all_table_migrations.filter { |m| m.schema_name == dependency[:schema_name] && m.table_name == dependency[:table_name] }
220
- # recursively call this method for all the dependencies for these migrations
221
- dependent_migrations.each do |dependent_migration|
222
- dependent_migration.table_dependencies.each do |next_dependency|
223
- # if we find a dependency which matches the original schema and table name then we have a circular dependency
224
- if circular_dependency?(schema_name, table_name, next_dependency, all_table_migrations)
225
- return true
213
+ # Initially, all the fragments which pertain to a particular table are grouped together in
214
+ # the same migration. If a circular dependency between migrations is detected, then we simply
215
+ # pop the offending migration fragments out of the dedicated table migration and into a new
216
+ # migration. This allows the migration to be processed later, and resolves the circular dependency.
217
+ #
218
+ # Note, "table migrations" are the default migrations which initially contain all the fragments for
219
+ # a particular table.
220
+ #
221
+ # `table_migration` is the current migration which is being processed
222
+ # `all_table_migrations` is all the table migrations in this current set of migrations
223
+ # `database_migrations` is a hash of all the migrations, organized by schema and table, we need this
224
+ # object so that we can add any new migrations which are created to resolve circular dependencies
225
+ # `completed_table_migrations` is an array of all the table migrations which have already been
226
+ # processed, we use this for performance reasons, so that we dont process the same migration twice
227
+ # `stack` is an array of all the migrations which have been processed so far in this current recursive
228
+ # path, this is used to detect circular dependencies.
229
+ def resolve_circular_dependencies table_migration, all_table_migrations, database_migrations, completed_table_migrations, stack = []
230
+ # process all the current dependencies for this migration
231
+ # each dependency is a hash, with the schema_name and table_name
232
+ table_migration.table_dependencies.each do |dependency|
233
+ # look in the list of all table migrations and try and find the migration which
234
+ # matches the current dependency, note that this migration may not exist because
235
+ # the table could have been created in a previous set of migrations
236
+ if (next_table_migration = all_table_migrations.find { |m| m.schema_name == dependency[:schema_name] && m.table_name == dependency[:table_name] })
237
+ # if this migration has already been processed, then we can skip it
238
+ next if completed_table_migrations.include? next_table_migration
239
+
240
+ key = "#{next_table_migration.schema_name}.#{next_table_migration.table_name}"
241
+ # if this migration already exists in the stack, then we have a circular dependency
242
+ if stack.include? key
243
+ log.info " Resolving circular dependency for #{table_migration.schema_name}.#{table_migration.table_name} -> #{next_table_migration.schema_name}.#{next_table_migration.table_name}"
244
+
245
+ # if the number of fragments in the table migration is equal to the number of fragments
246
+ # which would be removed, then there is no need to split the migration
247
+ next if table_migration.fragments.count == table_migration.fragments_with_table_dependency_count(next_table_migration.schema_name, next_table_migration.table_name)
248
+
249
+ # remove the fragments which are causing the circular dependency
250
+ removed_fragments = table_migration.extract_fragments_with_table_dependency next_table_migration.schema_name, next_table_migration.table_name
251
+
252
+ # create a new table migration for these fragments
253
+ new_migration = TableMigration.new(table_migration.schema_name, table_migration.table_name)
254
+
255
+ # place these fragments in their own migration
256
+ removed_fragments.each do |removed_fragment|
257
+ new_migration.add_fragment removed_fragment
226
258
  end
259
+
260
+ # add the new migration to the list of additional (not standard table migrations) for
261
+ # this schema
262
+ database_migrations[table_migration.schema_name][:additional_migrations] << new_migration
263
+
264
+ # continue to the next dependency
265
+ next
227
266
  end
267
+
268
+ # create a new stack, so that each recursive call has it's own copy
269
+ new_stack = stack + [key]
270
+
271
+ # recursively move on to the next migration
272
+ resolve_circular_dependencies next_table_migration, all_table_migrations, database_migrations, completed_table_migrations, new_stack
273
+
274
+ # when the code reaches this point, we have completed the recursive traversal of
275
+ # all the dependencies originating from next_table_migration, so we can add it to
276
+ # the array of completed migrations, note that this array is shared across all
277
+ # recursive calls, so that we can keep track of which migrations have been processed
278
+ completed_table_migrations << next_table_migration
228
279
  end
229
- false
230
280
  end
231
281
  end
232
282
 
@@ -274,6 +324,10 @@ module DynamicMigrations
274
324
  def trim_lines string
275
325
  string.split("\n").map(&:rstrip).join("\n")
276
326
  end
327
+
328
+ def log
329
+ @logger
330
+ end
277
331
  end
278
332
  end
279
333
  end
@@ -11,14 +11,14 @@ module DynamicMigrations
11
11
  # if the extension exists in the configuration but not in the database
12
12
  # then we have to create it
13
13
  if configuration_extension[:exists] == true && !database_extension[:exists]
14
- log.info "Extension `#{extension_name}` exists in configuration but not in the database"
14
+ log.debug "Extension `#{extension_name}` exists in configuration but not in the database"
15
15
  # a migration to create the extension
16
16
  @generator.enable_extension extension_name
17
17
 
18
18
  # if the extension exists in the database but not in the configuration
19
19
  # then we need to delete it
20
20
  elsif database_extension[:exists] == true && !configuration_extension[:exists]
21
- log.info "Extension `#{extension_name}` exists in database but not in the configuration"
21
+ log.debug "Extension `#{extension_name}` exists in database but not in the configuration"
22
22
  # a migration to drop the extension
23
23
  if Postgres.remove_unused_extensions?
24
24
  @generator.disable_extension extension_name
@@ -10,10 +10,10 @@ module DynamicMigrations
10
10
  module Enums
11
11
  def process_enums schema_name, configuration_enums, database_enums
12
12
  # process all the enums
13
- log.info " Processing tables..."
13
+ log.debug " Processing tables"
14
14
  enum_names = (configuration_enums.keys + database_enums.keys).uniq
15
15
  enum_names.each do |enum_name|
16
- log.info " Processing table #{enum_name}..."
16
+ log.debug " Processing table #{enum_name}"
17
17
  process_enum schema_name, enum_name, configuration_enums[enum_name] || {}, database_enums[enum_name] || {}
18
18
  end
19
19
  end
@@ -22,7 +22,7 @@ module DynamicMigrations
22
22
  # If the enum exists in the configuration but not in the database
23
23
  # then we have to create it.
24
24
  if configuration_enum[:exists] == true && !database_enum[:exists]
25
- log.info " Enum `#{enum_name}` exists in configuration but not in the database"
25
+ log.debug " Enum `#{enum_name}` exists in configuration but not in the database"
26
26
 
27
27
  # a migration to create the enum
28
28
  enum = @database.configured_schema(schema_name).enum(enum_name)
@@ -35,7 +35,7 @@ module DynamicMigrations
35
35
  # If the schema exists in the database but not in the configuration
36
36
  # then we need to delete it.
37
37
  elsif database_enum[:exists] == true && !configuration_enum[:exists]
38
- log.info " Enum `#{enum_name}` exists in database but not in the configuration"
38
+ log.debug " Enum `#{enum_name}` exists in database but not in the configuration"
39
39
 
40
40
  # a migration to create the enum
41
41
  enum = @database.loaded_schema(schema_name).enum(enum_name)
@@ -44,9 +44,9 @@ module DynamicMigrations
44
44
  # If the enum exists in both the configuration and database representations
45
45
  # but the values is different then we need to update the values.
46
46
  elsif configuration_enum[:values][:matches] == false
47
- log.info " Enum `#{enum_name}` exists in both configuration and the database"
47
+ log.debug " Enum `#{enum_name}` exists in both configuration and the database"
48
48
 
49
- log.info " Enum `#{enum_name}` values are different"
49
+ log.debug " Enum `#{enum_name}` values are different"
50
50
  original_enum = @database.loaded_schema(schema_name).enum(enum_name)
51
51
  updated_enum = @database.configured_schema(schema_name).enum(enum_name)
52
52
  @generator.update_enum original_enum, updated_enum
@@ -54,10 +54,10 @@ module DynamicMigrations
54
54
  if configuration_enum[:description][:matches] == false
55
55
  # if the description was removed
56
56
  if configuration_enum[:description].nil?
57
- log.info " Enum `#{enum_name}` description exists in database but not in the configuration"
57
+ log.debug " Enum `#{enum_name}` description exists in database but not in the configuration"
58
58
  @generator.remove_enum_comment updated_enum
59
59
  else
60
- log.info " Enum `#{enum_name}` description does not match"
60
+ log.debug " Enum `#{enum_name}` description does not match"
61
61
  @generator.set_enum_comment updated_enum
62
62
  end
63
63
  end
@@ -68,10 +68,10 @@ module DynamicMigrations
68
68
  enum = @database.configured_schema(schema_name).enum(enum_name)
69
69
  # if the description was removed
70
70
  if configuration_enum[:description].nil?
71
- log.info " Enum `#{enum_name}` description exists in database but not in the configuration"
71
+ log.debug " Enum `#{enum_name}` description exists in database but not in the configuration"
72
72
  @generator.remove_enum_comment enum
73
73
  else
74
- log.info " Enum `#{enum_name}` description does not match"
74
+ log.debug " Enum `#{enum_name}` description does not match"
75
75
  @generator.set_enum_comment enum
76
76
  end
77
77
  end