dynamic_migrations 3.8.6 → 3.8.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/lib/dynamic_migrations/active_record/migrators/validation.rb +2 -20
- data/lib/dynamic_migrations/postgres/generator/enum.rb +13 -9
- data/lib/dynamic_migrations/postgres/generator/fragment.rb +10 -3
- 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/validation.rb +1 -3
- data/lib/dynamic_migrations/postgres/generator/validation_template_base.rb +1 -7
- 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 +25 -20
- data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +2 -2
- data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +1 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/enum.rb +9 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +2 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +6 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +0 -6
- data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +6 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/index.rb +7 -3
- data/lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb +6 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +10 -8
- data/lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb +2 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb +6 -2
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +12 -19
- data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +62 -2
- data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +1 -3
- data/lib/dynamic_migrations/version.rb +1 -1
- data/sig/dynamic_migrations/active_record/migrators/validation.rbs +1 -1
- data/sig/dynamic_migrations/postgres/generator/enum.rbs +2 -0
- data/sig/dynamic_migrations/postgres/generator/fragment.rbs +3 -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/validation_template_base.rbs +0 -1
- 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/column.rbs +3 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rbs +3 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/index.rbs +3 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/primary_key.rbs +3 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/trigger.rbs +3 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rbs +3 -0
- data/sig/dynamic_migrations/postgres/server/database/schema/table/validation.rbs +4 -3
- data/sig/dynamic_migrations/postgres/server/database/schema/table.rbs +3 -0
- metadata +2 -3
- data/lib/dynamic_migrations/name_helper.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d822c8e95efe29d0f03f07162f27909ba4222ab4be98c421d64bd167fe923443
|
4
|
+
data.tar.gz: 88a20111dae2a34872d0759f47a0c8c29eab87d354ebb7a55d33152c9d06c6e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2210081126891f62ebadb6dd9f9f53cafd5cdd7ae196558b00e94ffdf91b64b1686d65c95ec07357ee8043ba1e8c698812ed07503d8b46287ab5496e26beb46d
|
7
|
+
data.tar.gz: eb23900019d373320e0f2510eadd2714c1c2c4324af0b0a4f68886754204deb7ebc0de11f833bf43b0a53b38f80448b70370ccd57ca55005c97715c397f4b579
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,32 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.8.8](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.7...v3.8.8) (2023-10-11)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* adding a specific error type for invalid names ([12df7bd](https://github.com/craigulliott/dynamic_migrations/commit/12df7bddcb947d3ab5b1b00eebefdda0a3ce15ac))
|
9
|
+
* fixed bug where check_clause and action_condition were being modified, also freezing strings to prevent similar bugs in the future ([dc1191b](https://github.com/craigulliott/dynamic_migrations/commit/dc1191b7e219b73da8469742259686721265a2a7))
|
10
|
+
* removed arbitrary column name sorting, as we want migrations to create columns in the order they were added ([e9a515e](https://github.com/craigulliott/dynamic_migrations/commit/e9a515eb9dbf16ef5a5e77ca0084a6514690530b))
|
11
|
+
* removing deferrable and initially deferred from check constraints because postgres doesn't support them ([945aef2](https://github.com/craigulliott/dynamic_migrations/commit/945aef2419fd5f55a0551929244f3f9b0a29a905))
|
12
|
+
* should not have assumed enums in the same schema use their short name, enums are always referenced by their full name (including schema) ([a9863ba](https://github.com/craigulliott/dynamic_migrations/commit/a9863ba49727e5016c8eae74f5f2a6e7ab8c1042))
|
13
|
+
* we were not resolving the correct deferred and initially_deferred values when loading existing database structure ([02c539a](https://github.com/craigulliott/dynamic_migrations/commit/02c539a8ea153cde6d06f6be6d8fdb0aaf851f40))
|
14
|
+
|
15
|
+
## [3.8.7](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.6...v3.8.7) (2023-10-09)
|
16
|
+
|
17
|
+
|
18
|
+
### Bug Fixes
|
19
|
+
|
20
|
+
* 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))
|
21
|
+
* added enum value length validation ([d82ff57](https://github.com/craigulliott/dynamic_migrations/commit/d82ff57f5b51c8636e87b40b8d6b016d11e6c72a))
|
22
|
+
* also drastic performance improvement of migration circular dependency resolution ([2915b8f](https://github.com/craigulliott/dynamic_migrations/commit/2915b8f28c9202bd18f221cfafa2c4352a80fd2c))
|
23
|
+
* 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))
|
24
|
+
* 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))
|
25
|
+
* 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))
|
26
|
+
* reducing some log messages from info level to debug level ([cdf1200](https://github.com/craigulliott/dynamic_migrations/commit/cdf12000186a9e373d5085f83d2fb197c7796c6c))
|
27
|
+
* table migrations now allow enum related fragments ([b894bf1](https://github.com/craigulliott/dynamic_migrations/commit/b894bf14743c51b505693818407ba6bfb3f31571))
|
28
|
+
* updating log level for some log entries ([456a90e](https://github.com/craigulliott/dynamic_migrations/commit/456a90e7fe6da78667a84928b75217f89c344f35))
|
29
|
+
|
3
30
|
## [3.8.6](https://github.com/craigulliott/dynamic_migrations/compare/v3.8.5...v3.8.6) (2023-10-08)
|
4
31
|
|
5
32
|
|
@@ -3,7 +3,7 @@ module DynamicMigrations
|
|
3
3
|
module Migrators
|
4
4
|
module Validation
|
5
5
|
# this exists because because the standard rails migration does not support deffered constraints
|
6
|
-
def add_validation table_name, name:,
|
6
|
+
def add_validation table_name, name:, comment: nil, &block
|
7
7
|
unless block
|
8
8
|
raise MissingFunctionBlockError, "create_function requires a block"
|
9
9
|
end
|
@@ -12,28 +12,10 @@ module DynamicMigrations
|
|
12
12
|
sql = block.call.strip
|
13
13
|
end
|
14
14
|
|
15
|
-
if initially_deferred == true && deferrable == false
|
16
|
-
raise DeferrableOptionsError, "A constraint can only be initially deferred if it is also deferrable"
|
17
|
-
end
|
18
|
-
|
19
|
-
# allow it to be deferred, and defer it by default
|
20
|
-
deferrable_sql = if initially_deferred
|
21
|
-
"DEFERRABLE INITIALLY DEFERRED"
|
22
|
-
|
23
|
-
# allow it to be deferred, but do not deferr by default
|
24
|
-
elsif deferrable
|
25
|
-
"DEFERRABLE INITIALLY IMMEDIATE"
|
26
|
-
|
27
|
-
# it can not be deferred (this is the default)
|
28
|
-
else
|
29
|
-
"NOT DEFERRABLE"
|
30
|
-
end
|
31
|
-
|
32
15
|
execute <<~SQL
|
33
16
|
ALTER TABLE #{table_name}
|
34
17
|
ADD CONSTRAINT #{name}
|
35
|
-
CHECK (#{sql})
|
36
|
-
#{deferrable_sql};
|
18
|
+
CHECK (#{sql});
|
37
19
|
SQL
|
38
20
|
|
39
21
|
if comment.is_a? String
|
@@ -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
|
@@ -5,6 +5,9 @@ module DynamicMigrations
|
|
5
5
|
class InvalidNameError < StandardError
|
6
6
|
end
|
7
7
|
|
8
|
+
class ContentRequiredError < StandardError
|
9
|
+
end
|
10
|
+
|
8
11
|
attr_reader :schema_name
|
9
12
|
attr_reader :table_name
|
10
13
|
attr_reader :migration_method
|
@@ -15,7 +18,7 @@ module DynamicMigrations
|
|
15
18
|
attr_reader :dependency_enum_name
|
16
19
|
|
17
20
|
def initialize schema_name, table_name, migration_method, object_name, code_comment, content
|
18
|
-
valid_name_regex = /\A[a-z][a-z0-9]
|
21
|
+
valid_name_regex = /\A[a-z][a-z0-9]*(_[a-z0-9]+)*\z/
|
19
22
|
|
20
23
|
unless schema_name.nil? || (schema_name.to_s.match valid_name_regex)
|
21
24
|
raise InvalidNameError, "Invalid schema name `#{schema_name}`, must only be lowercase letters, numbers and underscores"
|
@@ -33,8 +36,12 @@ module DynamicMigrations
|
|
33
36
|
@object_name = object_name
|
34
37
|
|
35
38
|
@migration_method = migration_method
|
36
|
-
@code_comment = code_comment
|
37
|
-
|
39
|
+
@code_comment = code_comment&.freeze
|
40
|
+
|
41
|
+
if content.nil?
|
42
|
+
raise ContentRequiredError, "Content is required for a fragment"
|
43
|
+
end
|
44
|
+
@content = content.freeze
|
38
45
|
end
|
39
46
|
|
40
47
|
# Returns a string representation of the fragment for use in the final
|
@@ -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"
|
@@ -10,17 +10,11 @@ module DynamicMigrations
|
|
10
10
|
|
11
11
|
def initialize validation, code_comment
|
12
12
|
@validation = validation
|
13
|
-
@code_comment = code_comment
|
13
|
+
@code_comment = code_comment&.freeze
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
-
def assert_not_deferred!
|
19
|
-
if @validation.initially_deferred || @validation.deferrable
|
20
|
-
raise TemplateError, "#{self.class.name} validation template requires constraints to be are not deferrable"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
18
|
def assert_column_count! count = 1
|
25
19
|
if @validation.columns.count != count
|
26
20
|
raise TemplateError, "#{self.class.name} validation template requires a validation with only #{count} column"
|
@@ -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
|