dynamic_migrations 3.8.5 → 3.8.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -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/trigger.rb +7 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +8 -1
- 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,27 @@
|
|
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
|
+
|
18
|
+
## [3.8.6](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.5...v3.8.6) (2023-10-08)
|
19
|
+
|
20
|
+
|
21
|
+
### Bug Fixes
|
22
|
+
|
23
|
+
* replacing enum casts with the temporary enums when normalizing check clause and action conditions ([0cb67fc](https://github.com/craigulliott/dynamic_migrations/commit/0cb67fcff4d7833ec1dd60453526b1bc3dbe45ee))
|
24
|
+
|
3
25
|
## [3.8.5](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.4...v3.8.5) (2023-10-08)
|
4
26
|
|
5
27
|
|
@@ -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
|