dynamic_migrations 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/lib/dynamic_migrations/active_record/migrators/column.rb +21 -0
  4. data/lib/dynamic_migrations/active_record/migrators/foreign_key_constraint.rb +112 -0
  5. data/lib/dynamic_migrations/active_record/migrators/function.rb +108 -0
  6. data/lib/dynamic_migrations/active_record/migrators/index.rb +27 -0
  7. data/lib/dynamic_migrations/active_record/migrators/schema.rb +21 -0
  8. data/lib/dynamic_migrations/active_record/migrators/table.rb +21 -0
  9. data/lib/dynamic_migrations/active_record/migrators/trigger.rb +109 -0
  10. data/lib/dynamic_migrations/active_record/migrators/unique_constraint.rb +63 -0
  11. data/lib/dynamic_migrations/active_record/migrators/validation.rb +67 -0
  12. data/lib/dynamic_migrations/active_record/migrators.rb +64 -0
  13. data/lib/dynamic_migrations/name_helper.rb +13 -0
  14. data/lib/dynamic_migrations/postgres/generator/column.rb +92 -0
  15. data/lib/dynamic_migrations/postgres/generator/foreign_key_constraint.rb +84 -0
  16. data/lib/dynamic_migrations/postgres/generator/fragment.rb +30 -0
  17. data/lib/dynamic_migrations/postgres/generator/function.rb +77 -0
  18. data/lib/dynamic_migrations/postgres/generator/index.rb +101 -0
  19. data/lib/dynamic_migrations/postgres/generator/primary_key.rb +55 -0
  20. data/lib/dynamic_migrations/postgres/generator/schema.rb +19 -0
  21. data/lib/dynamic_migrations/postgres/generator/schema_migrations/section.rb +37 -0
  22. data/lib/dynamic_migrations/postgres/generator/schema_migrations.rb +92 -0
  23. data/lib/dynamic_migrations/postgres/generator/table.rb +122 -0
  24. data/lib/dynamic_migrations/postgres/generator/trigger.rb +101 -0
  25. data/lib/dynamic_migrations/postgres/generator/unique_constraint.rb +79 -0
  26. data/lib/dynamic_migrations/postgres/generator/validation.rb +87 -0
  27. data/lib/dynamic_migrations/postgres/generator.rb +359 -0
  28. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +68 -0
  29. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +72 -0
  30. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +73 -0
  31. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +73 -0
  32. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +49 -0
  33. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +73 -0
  34. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +73 -0
  35. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +73 -0
  36. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +80 -0
  37. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +48 -0
  38. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +59 -0
  39. data/lib/dynamic_migrations/postgres/server/database/differences.rb +76 -16
  40. data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +35 -9
  41. data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +50 -26
  42. data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +69 -0
  43. data/lib/dynamic_migrations/postgres/server/database/schema/functions.rb +63 -0
  44. data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +6 -44
  45. data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +1 -1
  46. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +40 -5
  47. data/lib/dynamic_migrations/postgres/server/database/schema/table/index.rb +23 -9
  48. data/lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb +21 -6
  49. data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +151 -0
  50. data/lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb +66 -0
  51. data/lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb +19 -9
  52. data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +20 -1
  53. data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +15 -5
  54. data/lib/dynamic_migrations/postgres/server/database/schema/tables.rb +63 -0
  55. data/lib/dynamic_migrations/postgres/server/database/schema.rb +3 -49
  56. data/lib/dynamic_migrations/postgres/server/database/source.rb +21 -0
  57. data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +22 -112
  58. data/lib/dynamic_migrations/postgres/server/database/triggers_and_functions_loader.rb +131 -0
  59. data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +10 -4
  60. data/lib/dynamic_migrations/postgres/server/database.rb +2 -1
  61. data/lib/dynamic_migrations/postgres/server.rb +6 -0
  62. data/lib/dynamic_migrations/postgres.rb +1 -1
  63. data/lib/dynamic_migrations/version.rb +1 -1
  64. data/lib/dynamic_migrations.rb +47 -4
  65. metadata +44 -3
  66. data/lib/dynamic_migrations/postgres/data_types.rb +0 -320
@@ -0,0 +1,92 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Column
5
+ class NoColumnCommentError < StandardError
6
+ end
7
+
8
+ def add_column column, code_comment = nil
9
+ if column.description.nil?
10
+ raise NoColumnCommentError, "Refusing to generate add_column migration, no description was provided for `#{column.table.schema.name}`.`#{column.table.name}` column `#{column.name}`"
11
+ end
12
+
13
+ options = {}
14
+ options[:null] = column.null
15
+
16
+ unless column.default.nil?
17
+ options[:default] = "\"#{column.default}\""
18
+ end
19
+
20
+ if column.array?
21
+ options[:array] = true
22
+ end
23
+
24
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
25
+
26
+ data_type = column.data_type
27
+ if column.array?
28
+ data_type = "\"#{data_type}\""
29
+ end
30
+
31
+ add_migration column.table.schema.name, column.table.name, :add_column, column.name, code_comment, <<~RUBY
32
+ add_column :#{column.table.name}, :#{column.name}, :#{data_type}, #{options_syntax}, comment: <<~COMMENT
33
+ #{indent column.description}
34
+ COMMENT
35
+ RUBY
36
+ end
37
+
38
+ def change_column column, code_comment = nil
39
+ options = {}
40
+ options[:null] = column.null
41
+
42
+ unless column.default.nil?
43
+ options[:default] = "\"#{column.default}\""
44
+ end
45
+
46
+ if column.array?
47
+ options[:array] = true
48
+ end
49
+
50
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
51
+
52
+ data_type = column.data_type
53
+ if column.array?
54
+ data_type = ":\"#{data_type}\""
55
+ end
56
+
57
+ add_migration column.table.schema.name, column.table.name, :change_column, column.name, code_comment, <<~RUBY
58
+ change_column :#{column.table.name}, :#{column.name}, :#{data_type}, #{options_syntax}
59
+ RUBY
60
+ end
61
+
62
+ def remove_column column, code_comment = nil
63
+ add_migration column.table.schema.name, column.table.name, :remove_column, column.name, code_comment, <<~RUBY
64
+ remove_column :#{column.table.name}, :#{column.name}
65
+ RUBY
66
+ end
67
+
68
+ # add a comment to a column
69
+ def set_column_comment column, code_comment = nil
70
+ description = column.description
71
+
72
+ if description.nil?
73
+ raise MissingDescriptionError
74
+ end
75
+
76
+ add_migration column.table.schema.name, column.table.name, :set_column_comment, column.name, code_comment, <<~RUBY
77
+ set_column_comment :#{column.table.name}, :#{column.name}, <<~COMMENT
78
+ #{indent description}
79
+ COMMENT
80
+ RUBY
81
+ end
82
+
83
+ # remove the comment from a column
84
+ def remove_column_comment column, code_comment = nil
85
+ add_migration column.table.schema.name, column.table.name, :remove_column_comment, column.name, code_comment, <<~RUBY
86
+ remove_column_comment :#{column.table.name}, :#{column.name}
87
+ RUBY
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,84 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module ForeignKeyConstraint
5
+ def add_foreign_key_constraint foreign_key_constraint, code_comment = nil
6
+ # the migration accepts either a single column name or an array of column names
7
+ # we use the appropriate syntax just to make the migration prettier and easier
8
+ # to understand
9
+ p_c_names = foreign_key_constraint.column_names
10
+ column_names = (p_c_names.count == 1) ? ":#{p_c_names.first}" : "[:#{p_c_names.join(", :")}]"
11
+
12
+ f_c_names = foreign_key_constraint.foreign_column_names
13
+ foreign_column_names = (f_c_names.count == 1) ? ":#{f_c_names.first}" : "[:#{f_c_names.join(", :")}]"
14
+
15
+ options = {
16
+ name: ":#{foreign_key_constraint.name}",
17
+ initially_deferred: foreign_key_constraint.initially_deferred,
18
+ deferrable: foreign_key_constraint.deferrable,
19
+ on_delete: ":#{foreign_key_constraint.on_delete}",
20
+ on_update: ":#{foreign_key_constraint.on_update}"
21
+ }
22
+ unless foreign_key_constraint.description.nil?
23
+ options[:comment] = <<~RUBY
24
+ <<~COMMENT
25
+ #{indent foreign_key_constraint.description}
26
+ COMMENT
27
+ RUBY
28
+ end
29
+
30
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
31
+
32
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :add_foreign_key, foreign_key_constraint.name, code_comment, <<~RUBY
33
+ add_foreign_key :#{foreign_key_constraint.table.name}, #{column_names}, :#{foreign_key_constraint.foreign_table.name}, #{foreign_column_names}, #{options_syntax}
34
+ RUBY
35
+ end
36
+
37
+ def remove_foreign_key_constraint foreign_key_constraint, code_comment = nil
38
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :remove_foreign_key, foreign_key_constraint.name, code_comment, <<~RUBY
39
+ remove_foreign_key :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}
40
+ RUBY
41
+ end
42
+
43
+ def recreate_foreign_key_constraint original_foreign_key_constraint, updated_foreign_key_constraint
44
+ # remove the original foreign_key_constraint
45
+ removal_fragment = remove_foreign_key_constraint original_foreign_key_constraint, <<~CODE_COMMENT
46
+ Removing original foreign key constraint because it has changed (it is recreated below)
47
+ Changes:
48
+ #{indent original_foreign_key_constraint.differences_descriptions(updated_foreign_key_constraint).join("\n")}
49
+ CODE_COMMENT
50
+
51
+ # recrete the foreign_key_constraint with the new options
52
+ recreation_fragment = add_foreign_key_constraint updated_foreign_key_constraint, <<~CODE_COMMENT
53
+ Recreating this foreign key constraint
54
+ CODE_COMMENT
55
+
56
+ # return the new fragments (the main reason to return them here is for the specs)
57
+ [removal_fragment, recreation_fragment]
58
+ end
59
+
60
+ # add a comment to a foreign_key_constraint
61
+ def set_foreign_key_constraint_comment foreign_key_constraint, code_comment = nil
62
+ description = foreign_key_constraint.description
63
+
64
+ if description.nil?
65
+ raise MissingDescriptionError
66
+ end
67
+
68
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :set_foreign_key_constraint_comment, foreign_key_constraint.name, code_comment, <<~RUBY
69
+ set_foreign_key_comment :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}, <<~COMMENT
70
+ #{indent description}
71
+ COMMENT
72
+ RUBY
73
+ end
74
+
75
+ # remove the comment from a foreign_key_constraint
76
+ def remove_foreign_key_constraint_comment foreign_key_constraint, code_comment = nil
77
+ add_migration foreign_key_constraint.table.schema.name, foreign_key_constraint.table.name, :remove_foreign_key_constraint_comment, foreign_key_constraint.name, code_comment, <<~RUBY
78
+ remove_foreign_key_comment :#{foreign_key_constraint.table.name}, :#{foreign_key_constraint.name}
79
+ RUBY
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,30 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ class Fragment
5
+ attr_reader :object_name
6
+ attr_reader :code_comment
7
+
8
+ def initialize object_name, code_comment, content
9
+ @object_name = object_name
10
+ @code_comment = code_comment
11
+ @content = content
12
+ end
13
+
14
+ def to_s
15
+ strings = []
16
+ comment = @code_comment
17
+ unless comment.nil?
18
+ strings << "# " + comment.split("\n").join("\n# ")
19
+ end
20
+ strings << @content
21
+ strings.join("\n").strip
22
+ end
23
+
24
+ def has_code_comment?
25
+ !@code_comment.nil?
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,77 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Function
5
+ def create_function function, code_comment = nil
6
+ options = {}
7
+
8
+ if function.description.nil?
9
+ comment_sql = ""
10
+ else
11
+ comment_sql = <<~RUBY
12
+ #{function.name}_comment = <<~COMMENT
13
+ #{indent function.description || ""}
14
+ COMMENT
15
+ RUBY
16
+ options[:comment] = "#{function.name}_comment"
17
+ end
18
+
19
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
20
+ optional_options_syntax = (options_syntax == "") ? "" : ", #{options_syntax}"
21
+
22
+ fn_sql = function.definition.strip
23
+ # ensure that the function ends with a semicolon
24
+ unless fn_sql.end_with? ";"
25
+ fn_sql << ";"
26
+ end
27
+
28
+ add_migration function.schema.name, function.triggers.first&.table&.name, :create_function, function.name, code_comment, (comment_sql + <<~RUBY)
29
+ create_function :#{function.name}#{optional_options_syntax} do
30
+ <<~SQL
31
+ #{indent fn_sql}
32
+ SQL
33
+ end
34
+ RUBY
35
+ end
36
+
37
+ def update_function function, code_comment = nil
38
+ fn_sql = function.definition.strip
39
+ # ensure that the function ends with a semicolon
40
+ unless fn_sql.end_with? ";"
41
+ fn_sql << ";"
42
+ end
43
+
44
+ add_migration function.schema.name, function.triggers.first&.table&.name, :update_function, function.name, code_comment, <<~RUBY
45
+ update_function :#{function.name} do
46
+ <<~SQL
47
+ #{indent fn_sql}
48
+ SQL
49
+ end
50
+ RUBY
51
+ end
52
+
53
+ def drop_function function, code_comment = nil
54
+ add_migration function.schema.name, function.triggers.first&.table&.name, :drop_function, function.name, code_comment, <<~RUBY
55
+ drop_function :#{function.name}
56
+ RUBY
57
+ end
58
+
59
+ # add a comment to a function
60
+ def set_function_comment function, code_comment = nil
61
+ add_migration function.schema.name, function.triggers.first&.table&.name, :set_function_comment, function.name, code_comment, <<~RUBY
62
+ set_function_comment :#{function.name}, <<~COMMENT
63
+ #{indent function.description || ""}
64
+ COMMENT
65
+ RUBY
66
+ end
67
+
68
+ # remove the comment from a function
69
+ def remove_function_comment function, code_comment = nil
70
+ add_migration function.schema.name, function.triggers.first&.table&.name, :remove_function_comment, function.name, code_comment, <<~RUBY
71
+ remove_function_comment :#{function.name}
72
+ RUBY
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,101 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Index
5
+ def add_index index, code_comment = nil
6
+ # the migration accepts either a single column name or an array of column names
7
+ # we use the appropriate syntax just to make the migration prettier and easier
8
+ # to understand
9
+ column_names = (index.column_names.count == 1) ? ":#{index.column_names.first}" : "[:#{index.column_names.join(", :")}]"
10
+
11
+ options = {
12
+ name: ":#{index.name}",
13
+ unique: index.unique,
14
+ using: ":#{index.type}",
15
+ # todo: support custom sorting, it requires refactoring the index class because the ordering is actually on a column by column basis, not the index itself
16
+ sort: ":#{index.order}"
17
+ }
18
+
19
+ # :first is the default when :desc is specified, :last is the default when :asc is specified
20
+ if (index.order == :desc && index.nulls_position == :last) || (index.order == :asc && index.nulls_position == :first)
21
+ # todo: support nulls_position, it requires writing our own migrator because rails does not provide this option
22
+ raise "custom nulls_position is not currently supported"
23
+ end
24
+
25
+ unless index.where.nil?
26
+ options[:where] = "\"#{index.where}\""
27
+ end
28
+
29
+ unless index.description.nil?
30
+ options[:comment] = <<~RUBY
31
+ <<~COMMENT
32
+ #{indent index.description}
33
+ COMMENT
34
+ RUBY
35
+ end
36
+
37
+ where_sql = ""
38
+ unless index.where.nil?
39
+ options[:where] = "#{index.name}_where_sql"
40
+ where_sql = <<~RUBY
41
+ #{index.name}_where_sql = <<~SQL
42
+ #{indent index.where}
43
+ SQL
44
+ RUBY
45
+ end
46
+
47
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
48
+
49
+ add_migration index.table.schema.name, index.table.name, :add_index, index.name, code_comment, (where_sql + <<~RUBY)
50
+ add_index :#{index.table.name}, #{column_names}, #{options_syntax}
51
+ RUBY
52
+ end
53
+
54
+ def remove_index index, code_comment = nil
55
+ add_migration index.table.schema.name, index.table.name, :remove_index, index.name, code_comment, <<~RUBY
56
+ remove_index :#{index.table.name}, :#{index.name}
57
+ RUBY
58
+ end
59
+
60
+ def recreate_index original_index, updated_index
61
+ # remove the original index
62
+ removal_fragment = remove_index original_index, <<~CODE_COMMENT
63
+ Removing original index because it has changed (it is recreated below)
64
+ Changes:
65
+ #{indent original_index.differences_descriptions(updated_index).join("\n")}
66
+ CODE_COMMENT
67
+
68
+ # recrete the index with the new options
69
+ recreation_fragment = add_index updated_index, <<~CODE_COMMENT
70
+ Recreating this index
71
+ CODE_COMMENT
72
+
73
+ # return the new fragments (the main reason to return them here is for the specs)
74
+ [removal_fragment, recreation_fragment]
75
+ end
76
+
77
+ # add a comment to a index
78
+ def set_index_comment index, code_comment = nil
79
+ description = index.description
80
+
81
+ if description.nil?
82
+ raise MissingDescriptionError
83
+ end
84
+
85
+ add_migration index.table.schema.name, index.table.name, :set_index_comment, index.name, code_comment, <<~RUBY
86
+ set_index_comment :#{index.table.name}, :#{index.name}, <<~COMMENT
87
+ #{indent description}
88
+ COMMENT
89
+ RUBY
90
+ end
91
+
92
+ # remove the comment from a index
93
+ def remove_index_comment index, code_comment = nil
94
+ add_migration index.table.schema.name, index.table.name, :remove_index_comment, index.name, code_comment, <<~RUBY
95
+ remove_index_comment :#{index.table.name}, :#{index.name}
96
+ RUBY
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,55 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module PrimaryKey
5
+ def add_primary_key primary_key, code_comment = nil
6
+ # the migration accepts either a single column name or an array of column names
7
+ # we use the appropriate syntax just to make the migration prettier and easier
8
+ # to understand
9
+ column_names = (primary_key.column_names.count == 1) ? ":#{primary_key.column_names.first}" : "[:#{primary_key.column_names.join(", :")}]"
10
+
11
+ options = {
12
+ name: ":#{primary_key.name}"
13
+ }
14
+
15
+ unless primary_key.description.nil?
16
+ options[:comment] = <<~RUBY
17
+ <<~COMMENT
18
+ #{indent primary_key.description}
19
+ COMMENT
20
+ RUBY
21
+ end
22
+
23
+ options_syntax = options.map { |k, v| "#{k}: #{v}" }.join(", ")
24
+
25
+ add_migration primary_key.table.schema.name, primary_key.table.name, :add_primary_key, primary_key.name, code_comment, <<~RUBY
26
+ add_primary_key :#{primary_key.table.name}, #{column_names}, #{options_syntax}
27
+ RUBY
28
+ end
29
+
30
+ def remove_primary_key primary_key, code_comment = nil
31
+ add_migration primary_key.table.schema.name, primary_key.table.name, :remove_primary_key, primary_key.name, code_comment, <<~RUBY
32
+ remove_primary_key :#{primary_key.table.name}, :#{primary_key.name}
33
+ RUBY
34
+ end
35
+
36
+ def recreate_primary_key original_primary_key, updated_primary_key
37
+ # remove the original primary_key
38
+ removal_fragment = remove_primary_key original_primary_key, <<~CODE_COMMENT
39
+ Removing original primary key because it has changed (it is recreated below)
40
+ Changes:
41
+ #{indent original_primary_key.differences_descriptions(updated_primary_key).join("\n")}
42
+ CODE_COMMENT
43
+
44
+ # recrete the primary_key with the new options
45
+ recreation_fragment = add_primary_key updated_primary_key, <<~CODE_COMMENT
46
+ Recreating this primary key
47
+ CODE_COMMENT
48
+
49
+ # return the new fragments (the main reason to return them here is for the specs)
50
+ [removal_fragment, recreation_fragment]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ module Schema
5
+ def create_schema schema, code_comment = nil
6
+ add_migration schema.name, nil, :create_schema, schema.name, code_comment, <<~RUBY
7
+ create_schema :#{schema.name}
8
+ RUBY
9
+ end
10
+
11
+ def drop_schema schema, code_comment = nil
12
+ add_migration schema.name, nil, :drop_schema, schema.name, code_comment, <<~RUBY
13
+ drop_schema :#{schema.name}
14
+ RUBY
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ class SchemaMigrations
5
+ class Section
6
+ attr_reader :schema_name
7
+ attr_reader :table_name
8
+ attr_reader :content_type
9
+ attr_reader :fragment
10
+
11
+ def initialize schema_name, table_name, content_type, fragment
12
+ @schema_name = schema_name
13
+ @table_name = table_name
14
+ @content_type = content_type
15
+ @fragment = fragment
16
+ end
17
+
18
+ def object_name
19
+ @fragment.object_name
20
+ end
21
+
22
+ def to_s
23
+ @fragment.to_s
24
+ end
25
+
26
+ def is_comment?
27
+ content_type? :comment
28
+ end
29
+
30
+ def content_type? content_type
31
+ @content_type == content_type
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ module DynamicMigrations
2
+ module Postgres
3
+ class Generator
4
+ class SchemaMigrations
5
+ class SectionNotFoundError < StandardError
6
+ end
7
+
8
+ attr_reader :current_migration_sections
9
+
10
+ def initialize
11
+ @migrations = []
12
+ @current_migration_sections = []
13
+ end
14
+
15
+ def add_fragment schema_name, table_name, content_type, fragment
16
+ @current_migration_sections << Section.new(schema_name, table_name, content_type, fragment)
17
+ end
18
+
19
+ def finalize
20
+ if @current_migration_sections.any?
21
+
22
+ contents = []
23
+ @current_migration_sections.each do |section|
24
+ contents << section.to_s
25
+ # add an empty line between sections (unless this is a comment section)
26
+ unless section.is_comment?
27
+ contents << ""
28
+ end
29
+ end
30
+
31
+ @migrations << {
32
+ name: generate_current_migration_name,
33
+ content: contents.join("\n").strip
34
+ }
35
+
36
+ @current_migration_sections = []
37
+ end
38
+ end
39
+
40
+ def to_a
41
+ @migrations
42
+ end
43
+
44
+ private
45
+
46
+ def current_migration_has_content_type? content_type
47
+ @current_migration_sections.map(&:content_type).include? content_type
48
+ end
49
+
50
+ def current_migration_section_of_content_type content_type
51
+ section = @current_migration_sections.find(&:content_type)
52
+ if section.nil?
53
+ raise SectionNotFoundError, "No section of type #{content_type} found"
54
+ end
55
+ section
56
+ end
57
+
58
+ # return true if the current migration only has the provided content types and comments
59
+ def current_migration_only_content_types? content_types
60
+ (@current_migration_sections.map(&:content_type) - content_types - [:comment]).empty?
61
+ end
62
+
63
+ def generate_current_migration_name
64
+ if current_migration_has_content_type? :create_schema
65
+ "create_#{current_migration_section_of_content_type(:create_schema).schema_name}_schema".to_sym
66
+
67
+ elsif current_migration_has_content_type? :drop_schema
68
+ "drop_#{current_migration_section_of_content_type(:drop_schema).schema_name}_schema".to_sym
69
+
70
+ elsif current_migration_has_content_type? :create_table
71
+ "create_#{current_migration_section_of_content_type(:create_table).table_name}".to_sym
72
+
73
+ elsif current_migration_has_content_type? :drop_table
74
+ "drop_#{current_migration_section_of_content_type(:drop_table).table_name}".to_sym
75
+
76
+ elsif current_migration_only_content_types? [:create_function]
77
+ "create_function_#{@current_migration_sections.find { |s| s.content_type == :create_function }&.object_name}".to_sym
78
+
79
+ elsif current_migration_only_content_types? [:create_function, :update_function, :drop_function, :set_function_comment, :remove_function_comment]
80
+ :schema_functions
81
+
82
+ elsif @current_migration_sections.first&.table_name
83
+ "changes_for_#{@current_migration_sections.first&.table_name}".to_sym
84
+
85
+ else
86
+ :changes
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end