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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/lib/dynamic_migrations/postgres/generator/enum.rb +13 -9
- data/lib/dynamic_migrations/postgres/generator/fragment.rb +1 -1
- data/lib/dynamic_migrations/postgres/generator/function.rb +13 -13
- data/lib/dynamic_migrations/postgres/generator/migration.rb +45 -7
- data/lib/dynamic_migrations/postgres/generator/table_migration.rb +2 -0
- data/lib/dynamic_migrations/postgres/generator.rb +100 -46
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/extensions.rb +2 -2
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/enums.rb +10 -10
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +11 -11
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +11 -11
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +11 -11
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +11 -11
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +6 -6
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +11 -11
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +11 -11
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +11 -11
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +8 -8
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +3 -3
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +4 -4
- data/lib/dynamic_migrations/postgres/server/database/differences.rb +17 -17
- data/lib/dynamic_migrations/postgres/server/database/schema/enum.rb +7 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +61 -1
- data/lib/dynamic_migrations/version.rb +1 -1
- data/sig/dynamic_migrations/postgres/generator/enum.rbs +2 -0
- data/sig/dynamic_migrations/postgres/generator/function.rbs +1 -0
- data/sig/dynamic_migrations/postgres/generator/migration.rbs +1 -0
- data/sig/dynamic_migrations/postgres/generator/schema_migration.rbs +2 -0
- data/sig/dynamic_migrations/postgres/generator/table_migration.rbs +3 -0
- data/sig/dynamic_migrations/postgres/generator.rbs +3 -1
- data/sig/dynamic_migrations/postgres/server/database/schema/enum.rbs +3 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table.rbs +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 443be07abb74aef74bd5d94de87ab2be7f227f81e1c401cb7bf8ebc55bc1c6af
|
4
|
+
data.tar.gz: f93c398273cb1ae28cf6d5b257adeda625f3c732952f327692fb3ef076798f7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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:
|
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]
|
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:
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
-
#
|
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
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/extensions.rb
CHANGED
@@ -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.
|
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.
|
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
|
data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/enums.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
47
|
+
log.debug " Enum `#{enum_name}` exists in both configuration and the database"
|
48
48
|
|
49
|
-
log.
|
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.
|
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.
|
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.
|
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.
|
74
|
+
log.debug " Enum `#{enum_name}` description does not match"
|
75
75
|
@generator.set_enum_comment enum
|
76
76
|
end
|
77
77
|
end
|