dynamic_migrations 2.2.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -4
- data/lib/dynamic_migrations/active_record/migrators/column.rb +21 -0
- data/lib/dynamic_migrations/active_record/migrators/foreign_key_constraint.rb +112 -0
- data/lib/dynamic_migrations/active_record/migrators/function.rb +108 -0
- data/lib/dynamic_migrations/active_record/migrators/index.rb +27 -0
- data/lib/dynamic_migrations/active_record/migrators/schema.rb +21 -0
- data/lib/dynamic_migrations/active_record/migrators/table.rb +21 -0
- data/lib/dynamic_migrations/active_record/migrators/trigger.rb +109 -0
- data/lib/dynamic_migrations/active_record/migrators/unique_constraint.rb +63 -0
- data/lib/dynamic_migrations/active_record/migrators/validation.rb +67 -0
- data/lib/dynamic_migrations/active_record/migrators.rb +64 -0
- data/lib/dynamic_migrations/name_helper.rb +13 -0
- data/lib/dynamic_migrations/postgres/generator/column.rb +92 -0
- data/lib/dynamic_migrations/postgres/generator/foreign_key_constraint.rb +84 -0
- data/lib/dynamic_migrations/postgres/generator/fragment.rb +30 -0
- data/lib/dynamic_migrations/postgres/generator/function.rb +77 -0
- data/lib/dynamic_migrations/postgres/generator/index.rb +101 -0
- data/lib/dynamic_migrations/postgres/generator/primary_key.rb +55 -0
- data/lib/dynamic_migrations/postgres/generator/schema.rb +19 -0
- data/lib/dynamic_migrations/postgres/generator/schema_migrations/section.rb +37 -0
- data/lib/dynamic_migrations/postgres/generator/schema_migrations.rb +92 -0
- data/lib/dynamic_migrations/postgres/generator/table.rb +122 -0
- data/lib/dynamic_migrations/postgres/generator/trigger.rb +101 -0
- data/lib/dynamic_migrations/postgres/generator/unique_constraint.rb +79 -0
- data/lib/dynamic_migrations/postgres/generator/validation.rb +87 -0
- data/lib/dynamic_migrations/postgres/generator.rb +359 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +68 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +72 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +49 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +73 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +80 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +48 -0
- data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +59 -0
- data/lib/dynamic_migrations/postgres/server/database/differences.rb +81 -6
- data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +35 -9
- data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +49 -8
- data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +69 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/functions.rb +63 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +4 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +1 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +40 -5
- data/lib/dynamic_migrations/postgres/server/database/schema/table/index.rb +23 -9
- data/lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb +21 -6
- data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +151 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb +66 -0
- data/lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb +19 -9
- data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +20 -1
- data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +15 -5
- data/lib/dynamic_migrations/postgres/server/database/schema/tables.rb +63 -0
- data/lib/dynamic_migrations/postgres/server/database/schema.rb +3 -49
- data/lib/dynamic_migrations/postgres/server/database/source.rb +21 -0
- data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +6 -6
- data/lib/dynamic_migrations/postgres/server/database/triggers_and_functions_loader.rb +131 -0
- data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +10 -4
- data/lib/dynamic_migrations/postgres/server/database.rb +2 -1
- data/lib/dynamic_migrations/postgres/server.rb +6 -0
- data/lib/dynamic_migrations/postgres.rb +1 -1
- data/lib/dynamic_migrations/version.rb +1 -1
- data/lib/dynamic_migrations.rb +47 -3
- metadata +44 -2
@@ -0,0 +1,64 @@
|
|
1
|
+
# include this module at the top of your migration to get access to the
|
2
|
+
# custom migration methods. For example:
|
3
|
+
#
|
4
|
+
# class CreateFooBars < ActiveRecord::Migration[7.0]
|
5
|
+
# include DynamicMigrations::ActiveRecord::Migrators
|
6
|
+
#
|
7
|
+
# def change
|
8
|
+
# ...
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
module DynamicMigrations
|
13
|
+
module ActiveRecord
|
14
|
+
module Migrators
|
15
|
+
class SchemaNameNotSetError < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
class DeferrableOptionsError < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
class MissingFunctionBlockError < StandardError
|
22
|
+
end
|
23
|
+
|
24
|
+
include Schema
|
25
|
+
include Validation
|
26
|
+
include ForeignKeyConstraint
|
27
|
+
include Table
|
28
|
+
include Index
|
29
|
+
include Column
|
30
|
+
include Function
|
31
|
+
include UniqueConstraint
|
32
|
+
include Trigger
|
33
|
+
|
34
|
+
# The schema name should be set before the migrations for
|
35
|
+
# each schema's migrations are run. This is done by:
|
36
|
+
# DynamicMigrations::ActiveRecord::Migrators.set_schema_name :schema_name
|
37
|
+
def self.schema_name
|
38
|
+
@current_schema
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.set_schema_name schema_name
|
42
|
+
@current_schema = schema_name.to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.clear_schema_name
|
46
|
+
@current_schema = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def quote string
|
50
|
+
connection.quote string
|
51
|
+
end
|
52
|
+
|
53
|
+
# this method is made available on the final migration class
|
54
|
+
def schema_name
|
55
|
+
sn = Migrators.schema_name
|
56
|
+
if sn.nil?
|
57
|
+
raise SchemaNameNotSetError
|
58
|
+
end
|
59
|
+
# return the schema name
|
60
|
+
sn
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DynamicMigrations
|
2
|
+
module NameHelper
|
3
|
+
# shortens a table name like 'invoice_subscription_prepayments' to 'inv_sub_pre'
|
4
|
+
warn "no unit tests"
|
5
|
+
def abbreviate_table_name table_name
|
6
|
+
table_name_without_schema = table_name.to_s.split(".").last
|
7
|
+
if table_name_without_schema.nil?
|
8
|
+
raise "no table name provided"
|
9
|
+
end
|
10
|
+
table_name_without_schema.split("_").map { |v| v[0..2] }.join("_")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -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
|