pg_trunk 0.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 +7 -0
- data/.github/workflows/ci.yml +87 -0
- data/.gitignore +9 -0
- data/.rspec +4 -0
- data/.rubocop.yml +92 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +31 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +141 -0
- data/Rakefile +16 -0
- data/bin/console +8 -0
- data/bin/rake +19 -0
- data/bin/rspec +19 -0
- data/bin/setup +8 -0
- data/bin/yard +19 -0
- data/lib/pg_trunk/core/adapters/postgres.rb +80 -0
- data/lib/pg_trunk/core/dependencies_resolver.rb +101 -0
- data/lib/pg_trunk/core/generators.rb +140 -0
- data/lib/pg_trunk/core/operation/attributes.rb +78 -0
- data/lib/pg_trunk/core/operation/callbacks.rb +40 -0
- data/lib/pg_trunk/core/operation/generators.rb +51 -0
- data/lib/pg_trunk/core/operation/inversion.rb +70 -0
- data/lib/pg_trunk/core/operation/registration.rb +55 -0
- data/lib/pg_trunk/core/operation/ruby_builder.rb +112 -0
- data/lib/pg_trunk/core/operation/ruby_helpers.rb +99 -0
- data/lib/pg_trunk/core/operation/sql_helpers.rb +44 -0
- data/lib/pg_trunk/core/operation/validations.rb +21 -0
- data/lib/pg_trunk/core/operation.rb +78 -0
- data/lib/pg_trunk/core/qualified_name.rb +165 -0
- data/lib/pg_trunk/core/railtie/command_recorder.rb +30 -0
- data/lib/pg_trunk/core/railtie/custom_types.rb +37 -0
- data/lib/pg_trunk/core/railtie/migration.rb +50 -0
- data/lib/pg_trunk/core/railtie/migrator.rb +22 -0
- data/lib/pg_trunk/core/railtie/schema_dumper.rb +75 -0
- data/lib/pg_trunk/core/railtie/schema_migration.rb +22 -0
- data/lib/pg_trunk/core/railtie/statements.rb +21 -0
- data/lib/pg_trunk/core/railtie.rb +35 -0
- data/lib/pg_trunk/core/registry.rb +159 -0
- data/lib/pg_trunk/core/serializers/array_of_hashes_serializer.rb +28 -0
- data/lib/pg_trunk/core/serializers/array_of_strings_serializer.rb +29 -0
- data/lib/pg_trunk/core/serializers/array_of_symbols_serializer.rb +28 -0
- data/lib/pg_trunk/core/serializers/array_serializer.rb +22 -0
- data/lib/pg_trunk/core/serializers/lowercase_string_serializer.rb +21 -0
- data/lib/pg_trunk/core/serializers/multiline_text_serializer.rb +21 -0
- data/lib/pg_trunk/core/serializers/qualified_name_serializer.rb +27 -0
- data/lib/pg_trunk/core/serializers/symbol_serializer.rb +22 -0
- data/lib/pg_trunk/core/serializers.rb +16 -0
- data/lib/pg_trunk/core/validators/all_items_valid_validator.rb +15 -0
- data/lib/pg_trunk/core/validators/difference_validator.rb +19 -0
- data/lib/pg_trunk/core/validators.rb +10 -0
- data/lib/pg_trunk/core.rb +21 -0
- data/lib/pg_trunk/generators.rb +7 -0
- data/lib/pg_trunk/operations/check_constraints/add_check_constraint.rb +109 -0
- data/lib/pg_trunk/operations/check_constraints/base.rb +69 -0
- data/lib/pg_trunk/operations/check_constraints/drop_check_constraint.rb +60 -0
- data/lib/pg_trunk/operations/check_constraints/rename_check_constraint.rb +54 -0
- data/lib/pg_trunk/operations/check_constraints/validate_check_constraint.rb +39 -0
- data/lib/pg_trunk/operations/check_constraints.rb +14 -0
- data/lib/pg_trunk/operations/composite_types/base.rb +61 -0
- data/lib/pg_trunk/operations/composite_types/change_composite_type.rb +136 -0
- data/lib/pg_trunk/operations/composite_types/column.rb +118 -0
- data/lib/pg_trunk/operations/composite_types/create_composite_type.rb +99 -0
- data/lib/pg_trunk/operations/composite_types/drop_composite_type.rb +67 -0
- data/lib/pg_trunk/operations/composite_types/rename_composite_type.rb +44 -0
- data/lib/pg_trunk/operations/composite_types.rb +15 -0
- data/lib/pg_trunk/operations/domains/base.rb +46 -0
- data/lib/pg_trunk/operations/domains/change_domain.rb +140 -0
- data/lib/pg_trunk/operations/domains/constraint.rb +93 -0
- data/lib/pg_trunk/operations/domains/create_domain.rb +124 -0
- data/lib/pg_trunk/operations/domains/drop_domain.rb +65 -0
- data/lib/pg_trunk/operations/domains/rename_domain.rb +44 -0
- data/lib/pg_trunk/operations/domains.rb +15 -0
- data/lib/pg_trunk/operations/enums/base.rb +47 -0
- data/lib/pg_trunk/operations/enums/change.rb +55 -0
- data/lib/pg_trunk/operations/enums/change_enum.rb +119 -0
- data/lib/pg_trunk/operations/enums/create_enum.rb +83 -0
- data/lib/pg_trunk/operations/enums/drop_enum.rb +63 -0
- data/lib/pg_trunk/operations/enums/rename_enum.rb +44 -0
- data/lib/pg_trunk/operations/enums.rb +15 -0
- data/lib/pg_trunk/operations/foreign_keys/add_foreign_key.rb +174 -0
- data/lib/pg_trunk/operations/foreign_keys/base.rb +155 -0
- data/lib/pg_trunk/operations/foreign_keys/drop_foreign_key.rb +76 -0
- data/lib/pg_trunk/operations/foreign_keys/rename_foreign_key.rb +63 -0
- data/lib/pg_trunk/operations/foreign_keys.rb +16 -0
- data/lib/pg_trunk/operations/functions/base.rb +54 -0
- data/lib/pg_trunk/operations/functions/change_function.rb +108 -0
- data/lib/pg_trunk/operations/functions/create_function.rb +198 -0
- data/lib/pg_trunk/operations/functions/drop_function.rb +88 -0
- data/lib/pg_trunk/operations/functions/rename_function.rb +57 -0
- data/lib/pg_trunk/operations/functions.rb +14 -0
- data/lib/pg_trunk/operations/indexes/add_index.rb +68 -0
- data/lib/pg_trunk/operations/indexes.rb +10 -0
- data/lib/pg_trunk/operations/materialized_views/base.rb +79 -0
- data/lib/pg_trunk/operations/materialized_views/change_materialized_view.rb +139 -0
- data/lib/pg_trunk/operations/materialized_views/column.rb +94 -0
- data/lib/pg_trunk/operations/materialized_views/create_materialized_view.rb +170 -0
- data/lib/pg_trunk/operations/materialized_views/drop_materialized_view.rb +70 -0
- data/lib/pg_trunk/operations/materialized_views/refresh_materialized_view.rb +48 -0
- data/lib/pg_trunk/operations/materialized_views/rename_materialized_view.rb +61 -0
- data/lib/pg_trunk/operations/materialized_views.rb +17 -0
- data/lib/pg_trunk/operations/procedures/base.rb +42 -0
- data/lib/pg_trunk/operations/procedures/change_procedure.rb +107 -0
- data/lib/pg_trunk/operations/procedures/create_procedure.rb +146 -0
- data/lib/pg_trunk/operations/procedures/drop_procedure.rb +66 -0
- data/lib/pg_trunk/operations/procedures/rename_procedure.rb +57 -0
- data/lib/pg_trunk/operations/procedures.rb +14 -0
- data/lib/pg_trunk/operations/statistics/base.rb +94 -0
- data/lib/pg_trunk/operations/statistics/create_statistics.rb +181 -0
- data/lib/pg_trunk/operations/statistics/drop_statistics.rb +75 -0
- data/lib/pg_trunk/operations/statistics/rename_statistics.rb +48 -0
- data/lib/pg_trunk/operations/statistics.rb +13 -0
- data/lib/pg_trunk/operations/tables/create_table.rb +75 -0
- data/lib/pg_trunk/operations/tables.rb +10 -0
- data/lib/pg_trunk/operations/triggers/base.rb +119 -0
- data/lib/pg_trunk/operations/triggers/change_trigger.rb +82 -0
- data/lib/pg_trunk/operations/triggers/create_trigger.rb +208 -0
- data/lib/pg_trunk/operations/triggers/drop_trigger.rb +66 -0
- data/lib/pg_trunk/operations/triggers/rename_trigger.rb +71 -0
- data/lib/pg_trunk/operations/triggers.rb +14 -0
- data/lib/pg_trunk/operations/views/base.rb +38 -0
- data/lib/pg_trunk/operations/views/change_view.rb +90 -0
- data/lib/pg_trunk/operations/views/create_view.rb +115 -0
- data/lib/pg_trunk/operations/views/drop_view.rb +69 -0
- data/lib/pg_trunk/operations/views/rename_view.rb +58 -0
- data/lib/pg_trunk/operations/views.rb +14 -0
- data/lib/pg_trunk/operations.rb +23 -0
- data/lib/pg_trunk/version.rb +6 -0
- data/lib/pg_trunk.rb +27 -0
- data/pg_trunk.gemspec +34 -0
- data/spec/dummy/.gitignore +16 -0
- data/spec/dummy/Rakefile +15 -0
- data/spec/dummy/bin/bundle +6 -0
- data/spec/dummy/bin/rails +6 -0
- data/spec/dummy/bin/rake +6 -0
- data/spec/dummy/config/application.rb +18 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/database.yml +14 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/db/materialized_views/admin_users_v01.sql +1 -0
- data/spec/dummy/db/migrate/.keep +0 -0
- data/spec/dummy/db/schema.rb +18 -0
- data/spec/dummy/db/views/admin_users_v01.sql +1 -0
- data/spec/dummy/db/views/admin_users_v02.sql +1 -0
- data/spec/operations/check_constraints/add_check_constraint_spec.rb +85 -0
- data/spec/operations/check_constraints/drop_check_constraint_spec.rb +111 -0
- data/spec/operations/check_constraints/rename_check_constraint_spec.rb +90 -0
- data/spec/operations/composite_types/change_composite_type_spec.rb +257 -0
- data/spec/operations/composite_types/create_composite_type_spec.rb +55 -0
- data/spec/operations/composite_types/drop_composite_type_spec.rb +109 -0
- data/spec/operations/composite_types/rename_composite_type_spec.rb +74 -0
- data/spec/operations/dependency_resolver_spec.rb +177 -0
- data/spec/operations/domains/change_domain_spec.rb +287 -0
- data/spec/operations/domains/create_domain_spec.rb +69 -0
- data/spec/operations/domains/drop_domain_spec.rb +119 -0
- data/spec/operations/domains/rename_domain_spec.rb +70 -0
- data/spec/operations/enums/change_enum_spec.rb +157 -0
- data/spec/operations/enums/create_enum_spec.rb +40 -0
- data/spec/operations/enums/drop_enum_spec.rb +120 -0
- data/spec/operations/enums/rename_enum_spec.rb +72 -0
- data/spec/operations/foreign_keys/add_foreign_key_spec.rb +208 -0
- data/spec/operations/foreign_keys/drop_foreign_key_spec.rb +167 -0
- data/spec/operations/foreign_keys/rename_foreign_key_spec.rb +101 -0
- data/spec/operations/functions/change_function_spec.rb +166 -0
- data/spec/operations/functions/create_function_spec.rb +192 -0
- data/spec/operations/functions/drop_function_spec.rb +182 -0
- data/spec/operations/functions/rename_function_spec.rb +101 -0
- data/spec/operations/indexes/add_index_spec.rb +94 -0
- data/spec/operations/materialized_views/change_materialized_view_spec.rb +190 -0
- data/spec/operations/materialized_views/create_materialized_view_spec.rb +144 -0
- data/spec/operations/materialized_views/drop_materialized_view_spec.rb +145 -0
- data/spec/operations/materialized_views/refresh_materialized_view_spec.rb +79 -0
- data/spec/operations/materialized_views/rename_materialized_view_spec.rb +88 -0
- data/spec/operations/procedures/change_procedure_spec.rb +175 -0
- data/spec/operations/procedures/create_procedure_spec.rb +151 -0
- data/spec/operations/procedures/drop_procedure_spec.rb +159 -0
- data/spec/operations/procedures/rename_procedure_spec.rb +107 -0
- data/spec/operations/statistics/create_statistics_spec.rb +230 -0
- data/spec/operations/statistics/drop_statistics_spec.rb +106 -0
- data/spec/operations/statistics/rename_statistics_spec.rb +129 -0
- data/spec/operations/tables/create_table_spec.rb +53 -0
- data/spec/operations/tables/rename_table_spec.rb +37 -0
- data/spec/operations/triggers/change_trigger_spec.rb +195 -0
- data/spec/operations/triggers/create_trigger_spec.rb +104 -0
- data/spec/operations/triggers/drop_trigger_spec.rb +124 -0
- data/spec/operations/triggers/rename_trigger_spec.rb +160 -0
- data/spec/operations/views/change_view_spec.rb +144 -0
- data/spec/operations/views/create_view_spec.rb +134 -0
- data/spec/operations/views/drop_view_spec.rb +146 -0
- data/spec/operations/views/rename_view_spec.rb +85 -0
- data/spec/pg_trunk/dependencies_resolver_spec.rb +43 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/migrations_helper.rb +376 -0
- metadata +348 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#drop_check_constraint(table, expression = nil, **options, &block)
|
|
4
|
+
# Remove a check constraint from the table
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] table (nil) The qualified name of the table
|
|
7
|
+
# @param [#to_s] expression (nil) The SQL expression
|
|
8
|
+
# @option [Boolean] :if_exists (false) Suppress the error when the constraint is absent
|
|
9
|
+
# @option [#to_s] :name (nil) The optional name of the constraint
|
|
10
|
+
# @option [Boolean] :inherit (true) If the constraint should be inherited by subtables
|
|
11
|
+
# @option [#to_s] :comment (nil) The comment describing the constraint
|
|
12
|
+
# @yield [Proc] the block with the constraint's definition
|
|
13
|
+
# @yieldparam The receiver of methods specifying the constraint
|
|
14
|
+
#
|
|
15
|
+
# Definition for the `drop_check_constraint` operation
|
|
16
|
+
#
|
|
17
|
+
# The constraint can be identified by the table and explicit name
|
|
18
|
+
#
|
|
19
|
+
# drop_check_constraint :users, name: "phone_is_long_enough"
|
|
20
|
+
#
|
|
21
|
+
# Alternatively the name can be got from the expression.
|
|
22
|
+
# Be careful! the expression must have exactly the same form
|
|
23
|
+
# as stored in the database:
|
|
24
|
+
#
|
|
25
|
+
# drop_check_constraint :users, "length((phone::text) > 10)"
|
|
26
|
+
#
|
|
27
|
+
# To made operation reversible the expression must be provided:
|
|
28
|
+
#
|
|
29
|
+
# drop_check_constraint "users" do |c|
|
|
30
|
+
# c.expression "length((phone::text) > 10)"
|
|
31
|
+
# c.inherit false
|
|
32
|
+
# c.comment "The phone is 10+ chars long"
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# The operation can be called with `if_exists` option.
|
|
36
|
+
#
|
|
37
|
+
# drop_check_constraint :users,
|
|
38
|
+
# name: "phone_is_long_enough",
|
|
39
|
+
# if_exists: true
|
|
40
|
+
#
|
|
41
|
+
# In this case the operation is always irreversible due to
|
|
42
|
+
# uncertainty of the previous state of the database.
|
|
43
|
+
|
|
44
|
+
module PGTrunk::Operations::CheckConstraints
|
|
45
|
+
# @private
|
|
46
|
+
class DropCheckConstraint < Base
|
|
47
|
+
validates :new_name, absence: true
|
|
48
|
+
|
|
49
|
+
def to_sql(_version)
|
|
50
|
+
sql = "ALTER TABLE #{table.to_sql} DROP CONSTRAINT"
|
|
51
|
+
sql << " IF EXISTS" if if_exists
|
|
52
|
+
sql << " #{name.name.inspect};"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def invert
|
|
56
|
+
irreversible!("if_exists: true") if if_exists
|
|
57
|
+
AddCheckConstraint.new(**to_h)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#rename_check_constraint(table, expression = nil, **options, &block)
|
|
4
|
+
# Rename a check constraint
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] table (nil) The qualified name of the table
|
|
7
|
+
# @param [#to_s] expression (nil) The SQL expression
|
|
8
|
+
# @option [#to_s] :name (nil) The current name of the constraint
|
|
9
|
+
# @option [#to_s] :to (nil) The new name for the constraint
|
|
10
|
+
# @yield [Proc] the block with the constraint's definition
|
|
11
|
+
# @yieldparam The receiver of methods specifying the constraint
|
|
12
|
+
#
|
|
13
|
+
# A constraint can be identified by the table and explicit name
|
|
14
|
+
#
|
|
15
|
+
# rename_check_constraint :users,
|
|
16
|
+
# name: "phone_is_long_enough",
|
|
17
|
+
# to: "phones.long_enough"
|
|
18
|
+
#
|
|
19
|
+
# Alternatively the name can be got from the expression.
|
|
20
|
+
# Be careful! the expression must have exactly the same form
|
|
21
|
+
# as stored in the database:
|
|
22
|
+
#
|
|
23
|
+
# rename_check_constraint :users, "length((phone::text) > 10)",
|
|
24
|
+
# to: "long_enough"
|
|
25
|
+
#
|
|
26
|
+
# The name can be reset to auto-generated when
|
|
27
|
+
# the `:to` option is missed or blank:
|
|
28
|
+
#
|
|
29
|
+
# rename_check_constraint :users, "phone_is_long_enough"
|
|
30
|
+
#
|
|
31
|
+
# The operation is always reversible.
|
|
32
|
+
|
|
33
|
+
module PGTrunk::Operations::CheckConstraints
|
|
34
|
+
# @private
|
|
35
|
+
class RenameCheckConstraint < Base
|
|
36
|
+
# Reset the name to default when `to:` option is missed or set to `nil`
|
|
37
|
+
after_initialize { self.new_name = generated_name if new_name.blank? }
|
|
38
|
+
|
|
39
|
+
validates :new_name, presence: true
|
|
40
|
+
validates :if_exists, :force, :comment, absence: true
|
|
41
|
+
|
|
42
|
+
def to_sql(_version)
|
|
43
|
+
<<~SQL.squish
|
|
44
|
+
ALTER TABLE #{table.to_sql}
|
|
45
|
+
RENAME CONSTRAINT #{name.name.inspect}
|
|
46
|
+
TO #{new_name.name.inspect};
|
|
47
|
+
SQL
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def invert
|
|
51
|
+
self.class.new(**to_h, name: new_name, to: name)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#validate_check_constraint(table, expression = nil, **options, &block)
|
|
4
|
+
# Validate an invalid check constraint
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] table (nil) The qualified name of the table
|
|
7
|
+
# @param [#to_s] expression (nil) The SQL expression
|
|
8
|
+
# @option [#to_s] :name (nil) The optional name of the constraint
|
|
9
|
+
# @yield [Proc] the block with the constraint's definition
|
|
10
|
+
# @yieldparam The receiver of methods specifying the constraint
|
|
11
|
+
#
|
|
12
|
+
# The invalid constraint can be identified by table and explicit name:
|
|
13
|
+
#
|
|
14
|
+
# validate_check_constraint :users, name: "phone_is_long_enough"
|
|
15
|
+
#
|
|
16
|
+
# Alternatively it can be specified by expression. In this case
|
|
17
|
+
# you must ensure the expression has the same form as it is stored
|
|
18
|
+
# in the database (after parsing the source).
|
|
19
|
+
#
|
|
20
|
+
# validate_check_constraint :users, "length((phone::text) > 10)"
|
|
21
|
+
#
|
|
22
|
+
# Notice that it is invertible but the inverted operation does nothing.
|
|
23
|
+
|
|
24
|
+
module PGTrunk::Operations::CheckConstraints
|
|
25
|
+
# @private
|
|
26
|
+
class ValidateCheckConstraint < Base
|
|
27
|
+
validates :if_exists, :force, :new_name, :comment, :new_name, :inherit,
|
|
28
|
+
absence: true
|
|
29
|
+
|
|
30
|
+
def to_sql(_version)
|
|
31
|
+
<<~SQL.squish
|
|
32
|
+
ALTER TABLE #{table.to_sql} VALIDATE CONSTRAINT #{name.name.inspect};
|
|
33
|
+
SQL
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The operation is invertible but the inversion does nothing
|
|
37
|
+
def invert; end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# nodoc
|
|
4
|
+
module PGTrunk::Operations
|
|
5
|
+
# @private
|
|
6
|
+
# Definitions for check constraints
|
|
7
|
+
module CheckConstraints
|
|
8
|
+
require_relative "check_constraints/base"
|
|
9
|
+
require_relative "check_constraints/add_check_constraint"
|
|
10
|
+
require_relative "check_constraints/drop_check_constraint"
|
|
11
|
+
require_relative "check_constraints/rename_check_constraint"
|
|
12
|
+
require_relative "check_constraints/validate_check_constraint"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module PGTrunk::Operations::CompositeTypes
|
|
4
|
+
# @abstract
|
|
5
|
+
# @private
|
|
6
|
+
# Base class for operations with composite types
|
|
7
|
+
class Base < PGTrunk::Operation
|
|
8
|
+
# All columns that can be used by type-related commands
|
|
9
|
+
attribute :columns, :pg_trunk_array_of_hashes, default: []
|
|
10
|
+
|
|
11
|
+
# Populate columns from a block
|
|
12
|
+
def column(name, type, collation: nil)
|
|
13
|
+
columns << Column.new(name: name, type: type, collation: collation)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Wrap column definitions to value objects
|
|
17
|
+
after_initialize { columns.map! { |a| Column.build(a) } }
|
|
18
|
+
|
|
19
|
+
validates :if_not_exists, absence: true
|
|
20
|
+
validates :name, presence: true
|
|
21
|
+
validates :columns, "PGTrunk/all_items_valid": true, allow_nil: true
|
|
22
|
+
|
|
23
|
+
# Use comparison by name from pg_trunk operations base class (default)
|
|
24
|
+
# Support name as the only positional argument (default)
|
|
25
|
+
|
|
26
|
+
ruby_snippet do |s|
|
|
27
|
+
s.ruby_param(name.lean) if name.present?
|
|
28
|
+
s.ruby_param(to: new_name.lean) if new_name.present?
|
|
29
|
+
s.ruby_param(if_exists: true) if if_exists
|
|
30
|
+
s.ruby_param(force: :cascade) if force == :cascade
|
|
31
|
+
|
|
32
|
+
columns.reject(&:change).each do |c|
|
|
33
|
+
s.ruby_line(
|
|
34
|
+
:column, c.name, c.type&.lean, collation: c.collation&.lean,
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
columns.select { |c| c.change == :add }.each do |c|
|
|
38
|
+
s.ruby_line(
|
|
39
|
+
:add_column, c.name, c.type&.lean, collation: c.collation&.lean,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
columns.select { |c| c.change == :rename }.each do |c|
|
|
43
|
+
s.ruby_line(:rename_column, c.name, to: c.new_name)
|
|
44
|
+
end
|
|
45
|
+
columns.select { |c| c.change == :alter }.each do |c|
|
|
46
|
+
s.ruby_line(
|
|
47
|
+
:change_column, c.name, c.type&.lean,
|
|
48
|
+
collation: c.collation&.lean,
|
|
49
|
+
from_type: c.from_type&.lean,
|
|
50
|
+
from_collation: c.from_collation&.lean,
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
columns.select { |c| c.change == :drop }.each do |c|
|
|
54
|
+
s.ruby_line(
|
|
55
|
+
:drop_column, c.name, *c.type&.lean, collation: c.collation&.lean,
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
s.ruby_line(:comment, comment, from: from_comment) if comment
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#change_composite_type(name, **options, &block)
|
|
4
|
+
# Modify a composite type
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] name (nil) The qualified name of the type
|
|
7
|
+
# @option [Symbol] :force (:restrict) How to process dependent objects (`:cascade` or `:restrict`)
|
|
8
|
+
# @option [#to_s] :comment (nil) The comment describing the constraint
|
|
9
|
+
# @yield [Proc] the block with the type's definition
|
|
10
|
+
# @yieldparam The receiver of methods specifying the type
|
|
11
|
+
#
|
|
12
|
+
# The operation can be used to add, drop, rename or change columns.
|
|
13
|
+
# The comment can be changed as well.
|
|
14
|
+
#
|
|
15
|
+
# Providing a type "paint.colored_point":
|
|
16
|
+
#
|
|
17
|
+
# create_composite_type "paint.colored_point" do |t|
|
|
18
|
+
# t.column "color", "text", collation: "en_US"
|
|
19
|
+
# t.column "x", "integer"
|
|
20
|
+
# t.column "z", "integer"
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# After the following change:
|
|
24
|
+
#
|
|
25
|
+
# change_composite_type "paint.colored_point" do |t|
|
|
26
|
+
# t.change_column "color", "text", collation: "ru_RU", from_collation: "en_US"
|
|
27
|
+
# t.change_column "x", "bigint", from_type: "integer"
|
|
28
|
+
# t.drop_column "z", "integer"
|
|
29
|
+
# t.add_column "Y", "bigint"
|
|
30
|
+
# t.rename_column "x", to: "X"
|
|
31
|
+
# t.comment "2D point with a color", from: "2D point"
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# The definition became:
|
|
35
|
+
#
|
|
36
|
+
# create_composite_type "paint.colored_point" do |t|
|
|
37
|
+
# t.column "color", "text", collation: "ru_RU"
|
|
38
|
+
# t.column "X", "bigint"
|
|
39
|
+
# t.column "Y", "integer"
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# Notice, that all renames will be done AFTER other changes,
|
|
43
|
+
# so in `change_column` you should use the old names.
|
|
44
|
+
#
|
|
45
|
+
# In several cases the operation is not invertible:
|
|
46
|
+
#
|
|
47
|
+
# - when a column was dropped
|
|
48
|
+
# - when `force: :cascade` option is used (to update
|
|
49
|
+
# objects that use the type)
|
|
50
|
+
# - when `if_exists: true` is added to the `drop_column` clause
|
|
51
|
+
# - when a previous state of the column type, collation or comment
|
|
52
|
+
# is not specified.
|
|
53
|
+
|
|
54
|
+
module PGTrunk::Operations::CompositeTypes
|
|
55
|
+
# @private
|
|
56
|
+
class ChangeCompositeType < Base
|
|
57
|
+
# Methods to populate `columns` from the block
|
|
58
|
+
def add_column(name, type, collation: nil)
|
|
59
|
+
columns << Column.new(
|
|
60
|
+
name: name, type: type, collation: collation, change: :add, force: force,
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def drop_column(name, type = nil, **opts)
|
|
65
|
+
opts = opts.slice(:if_exists, :collation)
|
|
66
|
+
columns << Column.new(
|
|
67
|
+
name: name, type: type, force: force, **opts, change: :drop,
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def change_column(name, type, **opts)
|
|
72
|
+
opts = opts.slice(:collation, :from_type, :from_collation)
|
|
73
|
+
columns << Column.new(
|
|
74
|
+
name: name, type: type, force: force, change: :alter, **opts,
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def rename_column(name, to:)
|
|
79
|
+
columns << Column.new(
|
|
80
|
+
name: name, new_name: to, force: force, change: :rename,
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
validates :if_exists, :new_name, absence: true
|
|
85
|
+
validate { errors.add :base, "There are no changes" if change.blank? }
|
|
86
|
+
|
|
87
|
+
def to_sql(_version)
|
|
88
|
+
[*change_columns, *rename_columns, *change_comment].join(" ")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def invert
|
|
92
|
+
keys = inversion.select { |_, v| v.nil? }.keys.join(", ").presence
|
|
93
|
+
errors = columns.map(&:inversion_error).compact
|
|
94
|
+
errors << "Can't invert #{keys}" if keys.present?
|
|
95
|
+
errors << "Can't invert dropped columns" if columns.any? { |c| c.change == :drop }
|
|
96
|
+
raise IrreversibleMigration.new(self, nil, *errors) if errors.any?
|
|
97
|
+
|
|
98
|
+
self.class.new(**to_h, **inversion)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def change_columns
|
|
104
|
+
list = columns.select { |c| c.change&.!= :rename }
|
|
105
|
+
return if list.blank?
|
|
106
|
+
|
|
107
|
+
"ALTER TYPE #{name.to_sql} #{list.map(&:to_sql).join(', ')};"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def rename_columns
|
|
111
|
+
columns.select { |c| c.change == :rename }.map do |c|
|
|
112
|
+
"ALTER TYPE #{name.to_sql} #{c.to_sql};"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def change_comment
|
|
117
|
+
<<~SQL.squish if comment
|
|
118
|
+
COMMENT ON TYPE #{name.to_sql} IS $comment$#{comment}$comment$;
|
|
119
|
+
SQL
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def change
|
|
123
|
+
@change ||= {
|
|
124
|
+
comment: comment,
|
|
125
|
+
columns: columns.select(&:change).map(&:to_h).presence,
|
|
126
|
+
}.compact
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def inversion
|
|
130
|
+
@inversion ||= {
|
|
131
|
+
comment: from_comment,
|
|
132
|
+
columns: columns.reverse.map(&:invert).presence,
|
|
133
|
+
}.slice(*change.keys)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module PGTrunk::Operations::CompositeTypes
|
|
4
|
+
# @private
|
|
5
|
+
# Definition for an column of a composite type
|
|
6
|
+
class Column
|
|
7
|
+
include ActiveModel::Model
|
|
8
|
+
include ActiveModel::Attributes
|
|
9
|
+
include ActiveModel::Validations
|
|
10
|
+
|
|
11
|
+
def self.build(data)
|
|
12
|
+
data.is_a?(self) ? data : new(**data)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attribute :change, :pg_trunk_symbol
|
|
16
|
+
attribute :collation, :pg_trunk_qualified_name
|
|
17
|
+
attribute :force, :pg_trunk_symbol
|
|
18
|
+
attribute :from_collation, :pg_trunk_qualified_name
|
|
19
|
+
attribute :from_type, :pg_trunk_qualified_name
|
|
20
|
+
attribute :if_exists, :boolean
|
|
21
|
+
attribute :name, :string
|
|
22
|
+
attribute :new_name, :string
|
|
23
|
+
attribute :type, :pg_trunk_qualified_name
|
|
24
|
+
|
|
25
|
+
validates :name, presence: true
|
|
26
|
+
validates :new_name, "PGTrunk/difference": { from: :name }, allow_nil: true
|
|
27
|
+
validates :change, inclusion: { in: %i[add alter drop rename] }, allow_nil: true
|
|
28
|
+
validates :force, inclusion: { in: %i[force restrict] }, allow_nil: true
|
|
29
|
+
|
|
30
|
+
def to_h
|
|
31
|
+
@to_h ||= attributes.compact.symbolize_keys
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
INVERTED = {
|
|
35
|
+
add: :drop, drop: :add, rename: :rename, alter: :alter,
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
def invert
|
|
39
|
+
@invert ||= {}.tap do |i|
|
|
40
|
+
i[:change] = INVERTED[change]
|
|
41
|
+
i[:name] = new_name.presence || name
|
|
42
|
+
i[:new_name] = name if new_name.present?
|
|
43
|
+
i[:type] = change == :add ? type : from_type
|
|
44
|
+
i[:collation] = change == :add ? collation : from_collation
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_sql
|
|
49
|
+
case change
|
|
50
|
+
when :add then add_sql
|
|
51
|
+
when :alter then alter_sql
|
|
52
|
+
when :drop then drop_sql
|
|
53
|
+
when :rename then rename_sql
|
|
54
|
+
else sql
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def inversion_error
|
|
59
|
+
return <<~MSG.squish if if_exists
|
|
60
|
+
with `if_exists: true` option cannot be inverted
|
|
61
|
+
due to uncertainty of the previous state of the database.
|
|
62
|
+
MSG
|
|
63
|
+
|
|
64
|
+
return <<~MSG.squish if force == :cascade
|
|
65
|
+
with `force: :cascade` option cannot be inverted
|
|
66
|
+
due to uncertainty of the previous state of the database.
|
|
67
|
+
MSG
|
|
68
|
+
|
|
69
|
+
return <<~MSG.squish if change == :drop && type.blank?
|
|
70
|
+
undefined type of the dropped column #{name}
|
|
71
|
+
MSG
|
|
72
|
+
|
|
73
|
+
return <<~MSG.squish if change == :alter && type && !from_type
|
|
74
|
+
undefined a previous state of the type for column #{name}
|
|
75
|
+
MSG
|
|
76
|
+
|
|
77
|
+
return <<~MSG.squish if change == :alter && collation && !from_collation
|
|
78
|
+
undefined a previous state of the collation for column #{name}
|
|
79
|
+
MSG
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def rename_sql
|
|
85
|
+
"RENAME ATTRIBUTE #{name.inspect} TO #{new_name.inspect}".tap do |sql|
|
|
86
|
+
sql << " CASCADE" if force == :cascade
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def drop_sql
|
|
91
|
+
"DROP ATTRIBUTE".tap do |sql|
|
|
92
|
+
sql << " IF EXISTS" if if_exists
|
|
93
|
+
sql << " #{name.inspect}"
|
|
94
|
+
sql << " CASCADE" if force == :cascade
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add_sql
|
|
99
|
+
"ADD ATTRIBUTE #{name.inspect} #{type.lean}".tap do |sql|
|
|
100
|
+
sql << " COLLATE #{collation.to_sql}" if collation.present?
|
|
101
|
+
sql << " CASCADE" if force == :cascade
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def alter_sql
|
|
106
|
+
"ALTER ATTRIBUTE #{name.inspect} SET DATA TYPE #{type.lean}".tap do |sql|
|
|
107
|
+
sql << " COLLATE #{collation.to_sql}" if collation.present?
|
|
108
|
+
sql << " CASCADE" if force == :cascade
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def sql
|
|
113
|
+
"#{name.inspect} #{type.lean}".tap do |sql|
|
|
114
|
+
sql << " COLLATE #{collation.to_sql}" if collation
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#create_composite_type(name, **options, &block)
|
|
4
|
+
# Create a composite type
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] name (nil) The qualified name of the type
|
|
7
|
+
# @option [#to_s] :comment (nil) The comment describing the constraint
|
|
8
|
+
# @yield [Proc] the block with the type's definition
|
|
9
|
+
# @yieldparam The receiver of methods specifying the type
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# create_composite_type "paint.colored_point" do |d|
|
|
13
|
+
# d.column "x", "integer"
|
|
14
|
+
# d.column "y", "integer"
|
|
15
|
+
# d.column "color", "text", collation: "en_US"
|
|
16
|
+
# d.comment <<~COMMENT
|
|
17
|
+
# 2D point with color
|
|
18
|
+
# COMMENT
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# It is always reversible.
|
|
22
|
+
|
|
23
|
+
module PGTrunk::Operations::CompositeTypes
|
|
24
|
+
# @private
|
|
25
|
+
class CreateCompositeType < Base
|
|
26
|
+
validates :force, :if_exists, :new_name, absence: true
|
|
27
|
+
|
|
28
|
+
from_sql do |_version|
|
|
29
|
+
<<~SQL
|
|
30
|
+
SELECT
|
|
31
|
+
t.oid,
|
|
32
|
+
(t.typnamespace::regnamespace || '.' || t.typname) AS name,
|
|
33
|
+
(
|
|
34
|
+
SELECT
|
|
35
|
+
json_agg(
|
|
36
|
+
json_build_object(
|
|
37
|
+
'name', a.attname,
|
|
38
|
+
'type', format_type(a.atttypid, a.atttypmod),
|
|
39
|
+
'collation', (
|
|
40
|
+
CASE
|
|
41
|
+
WHEN c.collnamespace != 'pg_catalog'::regnamespace
|
|
42
|
+
THEN c.collnamespace::regnamespace || '.' || c.collname
|
|
43
|
+
WHEN c.collname != 'default'
|
|
44
|
+
THEN c.collname
|
|
45
|
+
END
|
|
46
|
+
)
|
|
47
|
+
) ORDER BY a.attnum
|
|
48
|
+
)
|
|
49
|
+
FROM pg_attribute a
|
|
50
|
+
LEFT JOIN pg_collation c ON c.oid = a.attcollation
|
|
51
|
+
WHERE a.attrelid = t.typrelid
|
|
52
|
+
AND EXISTS (SELECT FROM pg_type WHERE a.atttypid = pg_type.oid)
|
|
53
|
+
) AS columns,
|
|
54
|
+
d.description AS comment
|
|
55
|
+
FROM pg_type t
|
|
56
|
+
JOIN pg_trunk e ON e.oid = t.oid
|
|
57
|
+
AND e.classid = 'pg_type'::regclass
|
|
58
|
+
LEFT JOIN pg_description d ON d.objoid = t.oid
|
|
59
|
+
AND d.classoid = 'pg_type'::regclass
|
|
60
|
+
WHERE t.typtype = 'c';
|
|
61
|
+
SQL
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_sql(_version)
|
|
65
|
+
[create_type, *create_comment, register_type].join(" ")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def invert
|
|
69
|
+
DropCompositeType.new(**to_h)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def create_type
|
|
75
|
+
<<~SQL.squish
|
|
76
|
+
CREATE TYPE #{name.to_sql}
|
|
77
|
+
AS (#{columns.reject(&:change).map(&:to_sql).join(',')});
|
|
78
|
+
SQL
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create_comment
|
|
82
|
+
return if comment.blank?
|
|
83
|
+
|
|
84
|
+
"COMMENT ON TYPE #{name.to_sql} IS $comment$#{comment}$comment$;"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def register_type
|
|
88
|
+
<<~SQL.squish
|
|
89
|
+
INSERT INTO pg_trunk (oid, classid)
|
|
90
|
+
SELECT oid, 'pg_type'::regclass
|
|
91
|
+
FROM pg_type
|
|
92
|
+
WHERE typname = #{name.quoted}
|
|
93
|
+
AND typnamespace = #{name.namespace}
|
|
94
|
+
AND typtype = 'c'
|
|
95
|
+
ON CONFLICT DO NOTHING;
|
|
96
|
+
SQL
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#drop_composite_type(name, **options, &block)
|
|
4
|
+
# Drop a composite type
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] name (nil) The qualified name of the type
|
|
7
|
+
# @option [Boolean] :if_exists (false) Suppress the error when the type is absent
|
|
8
|
+
# @option [Symbol] :force (:restrict) How to process dependent objects (`:cascade` or `:restrict`)
|
|
9
|
+
# @option [#to_s] :comment (nil) The comment describing the constraint
|
|
10
|
+
# @yield [Proc] the block with the type's definition
|
|
11
|
+
# @yieldparam The receiver of methods specifying the type
|
|
12
|
+
#
|
|
13
|
+
# The operation drops a composite_type type identified by its
|
|
14
|
+
# qualified name (it can include a schema).
|
|
15
|
+
#
|
|
16
|
+
# For inversion use the same options as in the
|
|
17
|
+
# `create_composite_type` operation.
|
|
18
|
+
#
|
|
19
|
+
# drop_composite_type "paint.colored_point" do |d|
|
|
20
|
+
# d.column "x", "integer"
|
|
21
|
+
# d.column "y", "integer"
|
|
22
|
+
# d.column "color", "text", collation: "en_US"
|
|
23
|
+
# d.comment <<~COMMENT
|
|
24
|
+
# 2D point with color
|
|
25
|
+
# COMMENT
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# Notice, that the composite type creation can use no attributes.
|
|
29
|
+
# That's why dropping it is always reversible; though the reversion
|
|
30
|
+
# would provide a type without columns:
|
|
31
|
+
#
|
|
32
|
+
# drop_composite_type "paint.colored_point"
|
|
33
|
+
#
|
|
34
|
+
# With the `force: :cascade` option the operation would remove
|
|
35
|
+
# all objects using the type.
|
|
36
|
+
#
|
|
37
|
+
# drop_composite_type "paint.colored_point", force: :cascade
|
|
38
|
+
#
|
|
39
|
+
# With the `if_exists: true` option the operation won't fail
|
|
40
|
+
# even when the view was absent in the database.
|
|
41
|
+
#
|
|
42
|
+
# drop_composite_type "paint.colored_point", if_exists: true
|
|
43
|
+
#
|
|
44
|
+
# Both options make a migration irreversible due to uncertainty
|
|
45
|
+
# of the previous state of the database.
|
|
46
|
+
|
|
47
|
+
module PGTrunk::Operations::CompositeTypes
|
|
48
|
+
# @private
|
|
49
|
+
class DropCompositeType < Base
|
|
50
|
+
# Forbid these columns
|
|
51
|
+
validates :new_name, absence: true
|
|
52
|
+
|
|
53
|
+
def to_sql(_version)
|
|
54
|
+
sql = "DROP TYPE"
|
|
55
|
+
sql << " IF EXISTS" if if_exists
|
|
56
|
+
sql << " #{name.to_sql}"
|
|
57
|
+
sql << " CASCADE" if force == :cascade
|
|
58
|
+
sql << ";"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def invert
|
|
62
|
+
irreversible!("if_exists: true") if if_exists
|
|
63
|
+
irreversible!("force: :cascade") if force == :cascade
|
|
64
|
+
CreateCompositeType.new(**to_h.except(:force, :if_exists))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#rename_composite_type(name, to:)
|
|
4
|
+
# Change the name and/or schema of a composite type
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] name (nil) The qualified name of the type
|
|
7
|
+
# @option [#to_s] :to (nil) The new qualified name for the type
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
#
|
|
11
|
+
# rename_composite_type "point", to: "paint.colored_point"
|
|
12
|
+
#
|
|
13
|
+
# The operation is always reversible.
|
|
14
|
+
|
|
15
|
+
module PGTrunk::Operations::CompositeTypes
|
|
16
|
+
# @private
|
|
17
|
+
class RenameCompositeType < Base
|
|
18
|
+
validates :new_name, presence: true
|
|
19
|
+
validates :force, :if_exists, :columns, absence: true
|
|
20
|
+
|
|
21
|
+
def to_sql(_version)
|
|
22
|
+
[*change_schema, *change_name].join("; ")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def invert
|
|
26
|
+
self.class.new(**to_h, name: new_name, to: name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def change_schema
|
|
32
|
+
return if name.schema == new_name.schema
|
|
33
|
+
|
|
34
|
+
"ALTER TYPE #{name.to_sql} SET SCHEMA #{new_name.schema.inspect};"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def change_name
|
|
38
|
+
return if new_name.name == name.name
|
|
39
|
+
|
|
40
|
+
moved = name.merge(schema: new_name.schema)
|
|
41
|
+
"ALTER TYPE #{moved.to_sql} RENAME TO #{new_name.name.inspect};"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|