pg_trunk 0.1.0 → 0.2.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/.github/workflows/ci.yml +4 -15
- data/CHANGELOG.md +21 -0
- data/README.md +3 -1
- data/lib/pg_trunk/core/operation/attributes.rb +1 -1
- data/lib/pg_trunk/core/railtie/custom_types.rb +5 -6
- data/lib/pg_trunk/operations/check_constraints/add_check_constraint.rb +42 -33
- data/lib/pg_trunk/operations/check_constraints/drop_check_constraint.rb +51 -40
- data/lib/pg_trunk/operations/check_constraints/rename_check_constraint.rb +39 -30
- data/lib/pg_trunk/operations/check_constraints/validate_check_constraint.rb +28 -21
- data/lib/pg_trunk/operations/composite_types/change_composite_type.rb +59 -50
- data/lib/pg_trunk/operations/composite_types/create_composite_type.rb +23 -19
- data/lib/pg_trunk/operations/composite_types/drop_composite_type.rb +48 -43
- data/lib/pg_trunk/operations/composite_types/rename_composite_type.rb +15 -12
- data/lib/pg_trunk/operations/domains/change_domain.rb +53 -47
- data/lib/pg_trunk/operations/domains/create_domain.rb +28 -25
- data/lib/pg_trunk/operations/domains/drop_domain.rb +50 -41
- data/lib/pg_trunk/operations/domains/rename_domain.rb +17 -12
- data/lib/pg_trunk/operations/enums/change_enum.rb +37 -32
- data/lib/pg_trunk/operations/enums/create_enum.rb +23 -20
- data/lib/pg_trunk/operations/enums/drop_enum.rb +50 -39
- data/lib/pg_trunk/operations/enums/rename_enum.rb +17 -12
- data/lib/pg_trunk/operations/foreign_keys/add_foreign_key.rb +58 -49
- data/lib/pg_trunk/operations/foreign_keys/drop_foreign_key.rb +57 -48
- data/lib/pg_trunk/operations/foreign_keys/rename_foreign_key.rb +38 -29
- data/lib/pg_trunk/operations/functions/change_function.rb +53 -47
- data/lib/pg_trunk/operations/functions/create_function.rb +75 -64
- data/lib/pg_trunk/operations/functions/drop_function.rb +78 -65
- data/lib/pg_trunk/operations/functions/rename_function.rb +29 -22
- data/lib/pg_trunk/operations/materialized_views/change_materialized_view.rb +65 -55
- data/lib/pg_trunk/operations/materialized_views/create_materialized_view.rb +82 -71
- data/lib/pg_trunk/operations/materialized_views/drop_materialized_view.rb +59 -46
- data/lib/pg_trunk/operations/materialized_views/refresh_materialized_view.rb +29 -24
- data/lib/pg_trunk/operations/materialized_views/rename_materialized_view.rb +29 -22
- data/lib/pg_trunk/operations/procedures/change_procedure.rb +53 -46
- data/lib/pg_trunk/operations/procedures/create_procedure.rb +63 -52
- data/lib/pg_trunk/operations/procedures/drop_procedure.rb +56 -45
- data/lib/pg_trunk/operations/procedures/rename_procedure.rb +29 -22
- data/lib/pg_trunk/operations/rules/base.rb +77 -0
- data/lib/pg_trunk/operations/rules/create_rule.rb +155 -0
- data/lib/pg_trunk/operations/rules/drop_rule.rb +94 -0
- data/lib/pg_trunk/operations/rules/rename_rule.rb +62 -0
- data/lib/pg_trunk/operations/rules.rb +13 -0
- data/lib/pg_trunk/operations/sequences/base.rb +79 -0
- data/lib/pg_trunk/operations/sequences/change_sequence.rb +142 -0
- data/lib/pg_trunk/operations/sequences/create_sequence.rb +180 -0
- data/lib/pg_trunk/operations/sequences/drop_sequence.rb +82 -0
- data/lib/pg_trunk/operations/sequences/rename_sequence.rb +64 -0
- data/lib/pg_trunk/operations/sequences.rb +14 -0
- data/lib/pg_trunk/operations/statistics/create_statistics.rb +67 -56
- data/lib/pg_trunk/operations/statistics/drop_statistics.rb +64 -53
- data/lib/pg_trunk/operations/statistics/rename_statistics.rb +18 -13
- data/lib/pg_trunk/operations/triggers/change_trigger.rb +23 -18
- data/lib/pg_trunk/operations/triggers/create_trigger.rb +63 -54
- data/lib/pg_trunk/operations/triggers/drop_trigger.rb +55 -46
- data/lib/pg_trunk/operations/triggers/rename_trigger.rb +51 -48
- data/lib/pg_trunk/operations/views/change_view.rb +47 -38
- data/lib/pg_trunk/operations/views/create_view.rb +56 -45
- data/lib/pg_trunk/operations/views/drop_view.rb +59 -46
- data/lib/pg_trunk/operations/views/rename_view.rb +27 -20
- data/lib/pg_trunk/operations.rb +2 -0
- data/lib/pg_trunk/version.rb +1 -1
- data/pg_trunk.gemspec +0 -1
- data/spec/operations/rules/create_rule_spec.rb +119 -0
- data/spec/operations/rules/drop_rule_spec.rb +117 -0
- data/spec/operations/rules/rename_rule_spec.rb +148 -0
- data/spec/operations/sequences/change_sequence_spec.rb +134 -0
- data/spec/operations/sequences/create_sequence_spec.rb +156 -0
- data/spec/operations/sequences/drop_sequence_spec.rb +102 -0
- data/spec/operations/sequences/rename_sequence_spec.rb +100 -0
- metadata +22 -68
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
# @!parse
|
4
|
+
# class ActiveRecord::Migration
|
5
|
+
# # Modify a sequence
|
6
|
+
# #
|
7
|
+
# # @param [#to_s] name (nil) The qualified name of the sequence.
|
8
|
+
# # @option options [Boolean] :if_exists (false) Suppress the error when the sequence is absent.
|
9
|
+
# # @yield [s] the block with the sequence's definition.
|
10
|
+
# # @yieldparam Object receiver of methods specifying the sequence.
|
11
|
+
# # @return [void]
|
12
|
+
# #
|
13
|
+
# # The operation enables to alter a sequence without recreating it.
|
14
|
+
# # PostgreSQL allows any setting to be modified. The comment can be
|
15
|
+
# # changed as well.
|
16
|
+
# #
|
17
|
+
# # ```ruby
|
18
|
+
# # change_sequence "my_schema.global_id" do |s|
|
19
|
+
# # s.owned_by "", "", from: %w[users gid]
|
20
|
+
# # s.type "smallint", from: "integer"
|
21
|
+
# # s.iterate_by 1, from: 2
|
22
|
+
# # s.min_value 1, from: 0
|
23
|
+
# # s.max_value 2000, from: 1999
|
24
|
+
# # s.start_with 2, from: 1
|
25
|
+
# # s.cache 1, from: 10
|
26
|
+
# # s.cycle false
|
27
|
+
# # s.comment "Identifier", from: "Global identifier"
|
28
|
+
# # end
|
29
|
+
# # ```
|
30
|
+
# #
|
31
|
+
# # As in the snippet above, to make the change invertible,
|
32
|
+
# # you have to define from option for every changed attribute,
|
33
|
+
# # except for the boolean `cycle`.
|
34
|
+
# #
|
35
|
+
# # With the `if_exists: true` option, the operation won't raise
|
36
|
+
# # when the sequence is absent.
|
37
|
+
# #
|
38
|
+
# # ```ruby
|
39
|
+
# # change_sequence "my_schema.global_id", if_exists: true do |s|
|
40
|
+
# # s.type "smallint"
|
41
|
+
# # s.iterate_by 1
|
42
|
+
# # s.min_value 1
|
43
|
+
# # s.max_value 2000
|
44
|
+
# # s.start_with 2
|
45
|
+
# # s.cache 1
|
46
|
+
# # s.cycle false
|
47
|
+
# # s.comment "Identifier"
|
48
|
+
# # end
|
49
|
+
# # ```
|
50
|
+
# #
|
51
|
+
# # This option makes a migration irreversible due to uncertainty
|
52
|
+
# # of the previous state of the database. That's why in the last
|
53
|
+
# # example no `from:` option was added (they are useless).
|
54
|
+
# def change_sequence(name, **options, &block); end
|
55
|
+
# end
|
56
|
+
module PGTrunk::Operations::Sequences
|
57
|
+
# @private
|
58
|
+
class ChangeSequence < Base
|
59
|
+
# Operation-specific validations
|
60
|
+
validate { errors.add :base, "Changes can't be blank" if changes.blank? }
|
61
|
+
validates :force, :if_not_exists, :new_name, absence: true
|
62
|
+
|
63
|
+
def owned_by(table, column, from: nil)
|
64
|
+
self.table = table
|
65
|
+
self.column = column
|
66
|
+
self.from_table, self.from_column = Array(from)
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_sql(_version)
|
70
|
+
[*alter_sequence, *update_comment].join(" ")
|
71
|
+
end
|
72
|
+
|
73
|
+
def invert
|
74
|
+
irreversible!("if_exists: true") if if_exists
|
75
|
+
undefined = inversion.select { |_, v| v.nil? }.keys.join(", ").presence
|
76
|
+
raise IrreversibleMigration.new(self, nil, <<~MSG.squish) if undefined
|
77
|
+
Undefined values to revert #{undefined}.
|
78
|
+
MSG
|
79
|
+
|
80
|
+
self.class.new(name: name, **inversion) if inversion.any?
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
INF = (2**63) - 1
|
86
|
+
|
87
|
+
def changes
|
88
|
+
@changes ||= attributes.symbolize_keys.except(:name, :if_exists).compact
|
89
|
+
end
|
90
|
+
|
91
|
+
def inversion
|
92
|
+
@inversion ||= changes.each_with_object({}) do |(key, val), obj|
|
93
|
+
obj[key] = send(:"from_#{key}")
|
94
|
+
obj[key] = !val if [true, false].include?(val)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def alter_sequence
|
99
|
+
return if changes.except(:comment).blank?
|
100
|
+
|
101
|
+
sql = "ALTER SEQUENCE"
|
102
|
+
sql << " IF EXISTS" if if_exists
|
103
|
+
sql << " #{name.to_sql}"
|
104
|
+
sql << " AS #{type}" if type.present?
|
105
|
+
sql << " INCREMENT BY #{increment_by}" if increment_by.present?
|
106
|
+
sql << " MINVALUE #{min_value}" if min_value&.>(-INF)
|
107
|
+
sql << " NO MINVALUE" if min_value&.<=(-INF)
|
108
|
+
sql << " MAXVALUE #{max_value}" if max_value&.<(INF)
|
109
|
+
sql << " NO MAXVALUE" if max_value&.>=(INF)
|
110
|
+
sql << " START WITH #{start_with}" if start_with.present?
|
111
|
+
sql << " CACHE #{cache}" if cache.present?
|
112
|
+
sql << " OWNED BY #{table}.#{column}" if table.present? && column.present?
|
113
|
+
sql << " OWNED BY NONE" if table == "" || column == ""
|
114
|
+
sql << " CYCLE" if cycle
|
115
|
+
sql << " NO CYCLE" if cycle == false
|
116
|
+
sql << ";"
|
117
|
+
end
|
118
|
+
|
119
|
+
def update_comment
|
120
|
+
return unless comment
|
121
|
+
return <<~SQL.squish unless if_exists
|
122
|
+
COMMENT ON SEQUENCE #{name.to_sql} IS $comment$#{comment}$comment$;
|
123
|
+
SQL
|
124
|
+
|
125
|
+
# change the comment conditionally
|
126
|
+
<<~SQL.squish
|
127
|
+
DO $$
|
128
|
+
BEGIN
|
129
|
+
IF EXISTS (
|
130
|
+
SELECT FROM pg_sequence s JOIN pg_class c ON c.oid = s.seqrelid
|
131
|
+
WHERE c.relname = #{name.quoted}
|
132
|
+
AND c.relnamespace = #{name.namespace}
|
133
|
+
) THEN
|
134
|
+
COMMENT ON SEQUENCE #{name.to_sql}
|
135
|
+
IS $comment$#{comment}$comment$;
|
136
|
+
END IF;
|
137
|
+
END
|
138
|
+
$$;
|
139
|
+
SQL
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
# @!parse
|
4
|
+
# class ActiveRecord::Migration
|
5
|
+
# # Create a sequence
|
6
|
+
# #
|
7
|
+
# # @param [#to_s] name (nil) The qualified name of the sequence
|
8
|
+
# # @option options [#to_s] :as ("bigint") The type of the sequence's value
|
9
|
+
# # Supported values: "bigint" (or "int8", default), "integer" (or "int4"), "smallint" ("int2").
|
10
|
+
# # @option options [Boolean] :if_not_exists (false)
|
11
|
+
# # Suppress the error when the sequence already existed.
|
12
|
+
# # @option options [Integer] :increment_by (1) Non-zero step of the sequence (either positive or negative).
|
13
|
+
# # @option options [Integer] :min_value (nil) Minimum value of the sequence.
|
14
|
+
# # @option options [Integer] :max_value (nil) Maximum value of the sequence.
|
15
|
+
# # @option options [Integer] :start_with (nil) The first value of the sequence.
|
16
|
+
# # @option options [Integer] :cache (1) The number of values to be generated and cached.
|
17
|
+
# # @option options [Boolean] :cycle (false) If the sequence should be reset to start
|
18
|
+
# # after its value reaches min/max value.
|
19
|
+
# # @option options [#to_s] :comment (nil) The comment describing the sequence.
|
20
|
+
# # @yield [s] the block with the sequence's definition
|
21
|
+
# # @yieldparam Object receiver of methods specifying the sequence
|
22
|
+
# # @return [void]
|
23
|
+
# #
|
24
|
+
# # The sequence can be created by its qualified name only
|
25
|
+
# #
|
26
|
+
# # ```ruby
|
27
|
+
# # create_sequence "my_schema.global_id"
|
28
|
+
# # ```
|
29
|
+
# #
|
30
|
+
# # we also support all PostgreSQL settings for the sequence:
|
31
|
+
# #
|
32
|
+
# # ```ruby
|
33
|
+
# # create_sequence "my_schema.global_id", as: "integer" do |s|
|
34
|
+
# # s.iterate_by 2
|
35
|
+
# # s.min_value 0
|
36
|
+
# # s.max_value 1999
|
37
|
+
# # s.start_with 1
|
38
|
+
# # s.cache 10
|
39
|
+
# # s.cycle true
|
40
|
+
# # s.comment "Global identifier"
|
41
|
+
# # end
|
42
|
+
# # ```
|
43
|
+
# #
|
44
|
+
# # Using a block method `s.owned_by` you can bind the sequence to
|
45
|
+
# # some table's column. This means the sequence is dependent from
|
46
|
+
# # the column and will be dropped along with it. Notice that the
|
47
|
+
# # name of the table is NOT qualified because the table MUST belong
|
48
|
+
# # to the same schema as the sequence itself.
|
49
|
+
# #
|
50
|
+
# # ```ruby
|
51
|
+
# # create_table "users" do |t|
|
52
|
+
# # t.bigint :gid
|
53
|
+
# # end
|
54
|
+
# #
|
55
|
+
# # create_sequence "my_schema.global_id" do |s|
|
56
|
+
# # s.owned_by "users", "gid"
|
57
|
+
# # end
|
58
|
+
# # ```
|
59
|
+
# #
|
60
|
+
# # With the `if_not_exists: true` option the operation wouldn't raise
|
61
|
+
# # an exception in case the sequence has been already created.
|
62
|
+
# #
|
63
|
+
# # ```ruby
|
64
|
+
# # create_sequence "my_schema.global_id", if_not_exists: true
|
65
|
+
# # ```
|
66
|
+
# #
|
67
|
+
# # This option makes the migration irreversible due to uncertainty
|
68
|
+
# # of the previous state of the database.
|
69
|
+
# def create_sequence(name, **options, &block); end
|
70
|
+
# end
|
71
|
+
module PGTrunk::Operations::Sequences
|
72
|
+
# @private
|
73
|
+
class CreateSequence < Base
|
74
|
+
validates :if_exists, :force, :new_name, absence: true
|
75
|
+
|
76
|
+
from_sql do |_server_version|
|
77
|
+
<<~SQL
|
78
|
+
SELECT
|
79
|
+
c.oid,
|
80
|
+
(c.relnamespace::regnamespace || '.' || c.relname) AS name,
|
81
|
+
p.refobjid::regclass AS table,
|
82
|
+
a.attname AS column,
|
83
|
+
(
|
84
|
+
CASE WHEN s.seqtypid != 'int8'::regtype THEN format_type(s.seqtypid, 0) END
|
85
|
+
) AS type,
|
86
|
+
( CASE WHEN s.seqincrement != 1 THEN s.seqincrement END ) AS increment_by,
|
87
|
+
(
|
88
|
+
CASE
|
89
|
+
WHEN s.seqincrement > 0 THEN
|
90
|
+
CASE WHEN s.seqmin != 1 THEN s.seqmin END
|
91
|
+
ELSE
|
92
|
+
CASE
|
93
|
+
WHEN s.seqtypid = 'int2'::regtype AND s.seqmin = -32768 THEN NULL
|
94
|
+
WHEN s.seqtypid = 'int4'::regtype AND s.seqmin = -2147483648 THEN NULL
|
95
|
+
WHEN s.seqtypid = 'int8'::regtype AND s.seqmin = -9223372036854775808 THEN NULL
|
96
|
+
ELSE s.seqmin
|
97
|
+
END
|
98
|
+
END
|
99
|
+
) AS min_value,
|
100
|
+
(
|
101
|
+
CASE
|
102
|
+
WHEN s.seqincrement < 0 THEN
|
103
|
+
CASE WHEN s.seqmax != -1 THEN s.seqmax END
|
104
|
+
ELSE
|
105
|
+
CASE
|
106
|
+
WHEN s.seqtypid = 'int2'::regtype AND s.seqmax = 32767 THEN NULL
|
107
|
+
WHEN s.seqtypid = 'int4'::regtype AND s.seqmax = 2147483647 THEN NULL
|
108
|
+
WHEN s.seqtypid = 'int8'::regtype AND s.seqmax = 9223372036854775807 THEN NULL
|
109
|
+
ELSE s.seqmax
|
110
|
+
END
|
111
|
+
END
|
112
|
+
) AS max_value,
|
113
|
+
(
|
114
|
+
CASE
|
115
|
+
WHEN s.seqincrement > 0 AND s.seqstart = s.seqmin THEN NULL
|
116
|
+
WHEN s.seqincrement < 0 AND s.seqstart = s.seqmax THEN NULL
|
117
|
+
ELSE s.seqstart
|
118
|
+
END
|
119
|
+
) AS start_with,
|
120
|
+
( CASE WHEN s.seqcache != 1 THEN s.seqcache END ) AS cache,
|
121
|
+
( CASE WHEN s.seqcycle THEN true END ) AS cycle,
|
122
|
+
d.description AS comment
|
123
|
+
FROM pg_sequence s
|
124
|
+
JOIN pg_class c ON c.oid = s.seqrelid
|
125
|
+
JOIN pg_trunk t ON t.oid = c.oid
|
126
|
+
AND t.classid = 'pg_sequence'::regclass
|
127
|
+
LEFT JOIN pg_depend p ON p.objid = c.oid
|
128
|
+
AND p.classid = 'pg_class'::regclass
|
129
|
+
AND p.refclassid = 'pg_class'::regclass
|
130
|
+
AND p.objsubid = 0 AND p.refobjsubid !=0
|
131
|
+
LEFT JOIN pg_attribute a ON a.attrelid = p.refobjid
|
132
|
+
AND a.attnum = p.refobjsubid
|
133
|
+
LEFT JOIN pg_description d ON d.objoid = c.oid;
|
134
|
+
SQL
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_sql(_server_version)
|
138
|
+
[create_sequence, *comment_sequence, register_sequence].join(" ")
|
139
|
+
end
|
140
|
+
|
141
|
+
def invert
|
142
|
+
irreversible!("if_not_exists: true") if if_not_exists
|
143
|
+
DropSequence.new(**to_h.except(:if_not_exists))
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def create_sequence
|
149
|
+
sql = "CREATE SEQUENCE"
|
150
|
+
sql << " IF NOT EXISTS" if if_not_exists
|
151
|
+
sql << " #{name.to_sql}"
|
152
|
+
sql << " AS #{type}" if type.present?
|
153
|
+
sql << " INCREMENT BY #{increment_by}" if increment_by.present?
|
154
|
+
sql << " MINVALUE #{min_value}" if min_value.present?
|
155
|
+
sql << " MAXVALUE #{max_value}" if max_value.present?
|
156
|
+
sql << " START WITH #{start_with}" if start_with.present?
|
157
|
+
sql << " CACHE #{cache}" if cache.present?
|
158
|
+
sql << " OWNED BY #{table}.#{column}" if table.present? && column.present?
|
159
|
+
sql << " CYCLE" if cycle
|
160
|
+
sql << ";"
|
161
|
+
end
|
162
|
+
|
163
|
+
def comment_sequence
|
164
|
+
<<~SQL.squish if comment.present?
|
165
|
+
COMMENT ON SEQUENCE #{name.to_sql} IS $comment$#{comment}$comment$;
|
166
|
+
SQL
|
167
|
+
end
|
168
|
+
|
169
|
+
def register_sequence
|
170
|
+
<<~SQL.squish
|
171
|
+
INSERT INTO pg_trunk (oid, classid)
|
172
|
+
SELECT c.oid, 'pg_sequence'::regclass
|
173
|
+
FROM pg_sequence s JOIN pg_class c ON c.oid = s.seqrelid
|
174
|
+
WHERE c.relname = #{name.quoted}
|
175
|
+
AND c.relnamespace = #{name.namespace}
|
176
|
+
ON CONFLICT DO NOTHING;
|
177
|
+
SQL
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
# @!parse
|
4
|
+
# class ActiveRecord::Migration
|
5
|
+
# # Drop a sequence
|
6
|
+
# #
|
7
|
+
# # @param [#to_s] name (nil) The qualified name of the sequence
|
8
|
+
# # @option options [#to_s] :as ("bigint") The type of the sequence's value
|
9
|
+
# # Supported values: "bigint" (or "int8", default), "integer" (or "int4"), "smallint" ("int2").
|
10
|
+
# # @option options [Boolean] :if_exists (false) Suppress the error when the sequence is absent.
|
11
|
+
# # @option options [Symbol] :force (:restrict) Define how to process dependent objects
|
12
|
+
# # Supported values: :restrict (default), :cascade.
|
13
|
+
# # @option options [Integer] :increment_by (1) Non-zero step of the sequence (either positive or negative).
|
14
|
+
# # @option options [Integer] :min_value (nil) Minimum value of the sequence.
|
15
|
+
# # @option options [Integer] :max_value (nil) Maximum value of the sequence.
|
16
|
+
# # @option options [Integer] :start_with (nil) The first value of the sequence.
|
17
|
+
# # @option options [Integer] :cache (1) The number of values to be generated and cached.
|
18
|
+
# # @option options [Boolean] :cycle (false) If the sequence should be reset to start
|
19
|
+
# # after its value reaches min/max value.
|
20
|
+
# # @option options [#to_s] :comment (nil) The comment describing the sequence.
|
21
|
+
# # @yield [s] the block with the sequence's definition
|
22
|
+
# # @yieldparam Object receiver of methods specifying the sequence
|
23
|
+
# # @return [void]
|
24
|
+
# #
|
25
|
+
# # The sequence can be dropped by its qualified name only
|
26
|
+
# #
|
27
|
+
# # ```ruby
|
28
|
+
# # drop_sequence "global_number"
|
29
|
+
# # ```
|
30
|
+
# #
|
31
|
+
# # For inversion provide options for the `create_sequence` operation as well:
|
32
|
+
# #
|
33
|
+
# # ```ruby
|
34
|
+
# # drop_sequence "global_id", as: "int2" do |s|
|
35
|
+
# # s.iterate_by 2
|
36
|
+
# # s.min_value 0
|
37
|
+
# # s.max_value 1999
|
38
|
+
# # s.start_with 1
|
39
|
+
# # s.cache 10
|
40
|
+
# # s.cycle true
|
41
|
+
# # s.comment "Global identifier"
|
42
|
+
# # end
|
43
|
+
# # ```
|
44
|
+
# #
|
45
|
+
# # The operation can be called with `if_exists` option to suppress
|
46
|
+
# # the exception in case when the sequence is absent:
|
47
|
+
# #
|
48
|
+
# # ```ruby
|
49
|
+
# # drop_sequence "global_number", if_exists: true
|
50
|
+
# # ```
|
51
|
+
# #
|
52
|
+
# # With the `force: :cascade` option the operation would remove
|
53
|
+
# # all the objects that use the sequence.
|
54
|
+
# #
|
55
|
+
# # ```ruby
|
56
|
+
# # drop_sequence "global_number", force: :cascade
|
57
|
+
# # ```
|
58
|
+
# #
|
59
|
+
# # In both cases the operation becomes irreversible due to
|
60
|
+
# # uncertainty of the previous state of the database.
|
61
|
+
# def drop_sequence(name, **options, &block); end
|
62
|
+
# end
|
63
|
+
module PGTrunk::Operations::Sequences
|
64
|
+
# @private
|
65
|
+
class DropSequence < Base
|
66
|
+
validates :if_not_exists, :new_name, absence: true
|
67
|
+
|
68
|
+
def to_sql(_version)
|
69
|
+
sql = "DROP SEQUENCE"
|
70
|
+
sql << " IF EXISTS" if if_exists
|
71
|
+
sql << " #{name.to_sql}"
|
72
|
+
sql << " CASCADE" if force == :cascade
|
73
|
+
sql << ";"
|
74
|
+
end
|
75
|
+
|
76
|
+
def invert
|
77
|
+
irreversible!("if_exists: true") if if_exists
|
78
|
+
irreversible!("force: :cascade") if force == :cascade
|
79
|
+
CreateSequence.new(**to_h.except(:if_exists, :force))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
# @!parse
|
4
|
+
# class ActiveRecord::Migration
|
5
|
+
# # Rename a sequence
|
6
|
+
# #
|
7
|
+
# # @param [#to_s] name (nil) The current qualified name of the sequence
|
8
|
+
# # @option options [#to_s] :to (nil) The new qualified name for the sequence
|
9
|
+
# # @option options [Boolean] :if_exists (false) Suppress the error when the sequence is absent.
|
10
|
+
# # @return [void]
|
11
|
+
# #
|
12
|
+
# # The operation allows to change both name and schema
|
13
|
+
# #
|
14
|
+
# # ```ruby
|
15
|
+
# # rename_sequence "global_num", to: "sequences.global_number"
|
16
|
+
# # ```
|
17
|
+
# #
|
18
|
+
# # With the `if_exists: true` option the operation wouldn't raise
|
19
|
+
# # an exception in case the sequence hasn't been created yet.
|
20
|
+
# #
|
21
|
+
# # ```ruby
|
22
|
+
# # create_sequence "my_schema.global_id", if_exists: true
|
23
|
+
# # ```
|
24
|
+
# #
|
25
|
+
# # This option makes the migration irreversible due to uncertainty
|
26
|
+
# # of the previous state of the database.
|
27
|
+
# def rename_sequence(name, **options, &block); end
|
28
|
+
# end
|
29
|
+
module PGTrunk::Operations::Sequences
|
30
|
+
# @private
|
31
|
+
class RenameSequence < Base
|
32
|
+
validates :new_name, presence: true
|
33
|
+
validates :if_not_exists, :force, :type, :increment_by, :min_value,
|
34
|
+
:max_value, :start_with, :cache, :cycle, :comment, absence: true
|
35
|
+
|
36
|
+
def to_sql(_version)
|
37
|
+
[*change_schema, *change_name].join(" ")
|
38
|
+
end
|
39
|
+
|
40
|
+
def invert
|
41
|
+
irreversible!("if_exists: true") if if_exists
|
42
|
+
self.class.new(**to_h, name: new_name, to: name)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def change_schema
|
48
|
+
return if name.schema == new_name.schema
|
49
|
+
|
50
|
+
sql = "ALTER SEQUENCE"
|
51
|
+
sql << " IF EXISTS" if if_exists
|
52
|
+
sql << " #{name.to_sql} SET SCHEMA #{new_name.schema.inspect};"
|
53
|
+
end
|
54
|
+
|
55
|
+
def change_name
|
56
|
+
return if new_name.name == name.name
|
57
|
+
|
58
|
+
moved = name.merge(schema: new_name.schema)
|
59
|
+
sql = "ALTER SEQUENCE"
|
60
|
+
sql << " IF EXISTS" if if_exists
|
61
|
+
sql << " #{moved.to_sql} RENAME TO #{new_name.name.inspect};"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# nodoc
|
4
|
+
module PGTrunk::Operations
|
5
|
+
# @private
|
6
|
+
# Namespace for operations with sequences
|
7
|
+
module Sequences
|
8
|
+
require_relative "sequences/base"
|
9
|
+
require_relative "sequences/change_sequence"
|
10
|
+
require_relative "sequences/create_sequence"
|
11
|
+
require_relative "sequences/drop_sequence"
|
12
|
+
require_relative "sequences/rename_sequence"
|
13
|
+
end
|
14
|
+
end
|
@@ -1,61 +1,72 @@
|
|
1
1
|
# frozen_string_literal: false
|
2
2
|
|
3
|
-
# @!
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# @
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# @
|
16
|
-
# @
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# s.
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
# s.
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
|
3
|
+
# @!parse
|
4
|
+
# class ActiveRecord::Migration
|
5
|
+
# # Create a custom statistics
|
6
|
+
# #
|
7
|
+
# # @param [#to_s] name (nil) The qualified name of the statistics
|
8
|
+
# # @option options [Boolean] :if_not_exists (false)
|
9
|
+
# # Suppress the error when the statistics is already exist
|
10
|
+
# # @option options [#to_s] table (nil)
|
11
|
+
# # The qualified name of the table whose statistics will be collected
|
12
|
+
# # @option options [Array<Symbol>] kinds ([:dependencies, :mcv, :ndistinct])
|
13
|
+
# # The kinds of statistics to be collected (all by default).
|
14
|
+
# # Supported values in the array: :dependencies, :mcv, :ndistinct
|
15
|
+
# # @option options [#to_s] :comment The description of the statistics
|
16
|
+
# # @yield [s] the block with the statistics' definition
|
17
|
+
# # @yieldparam Object receiver of methods specifying the statistics
|
18
|
+
# # @return [void]
|
19
|
+
# #
|
20
|
+
# # The statistics can be created with explicit name:
|
21
|
+
# #
|
22
|
+
# # ```ruby
|
23
|
+
# # create_statistics "users_stats" do |s|
|
24
|
+
# # s.table "users"
|
25
|
+
# # s.columns "family", "name"
|
26
|
+
# # s.kinds :dependencies, :mcv, :ndistinct
|
27
|
+
# # s.comment "Statistics for users' names and families"
|
28
|
+
# # SQL
|
29
|
+
# # ```
|
30
|
+
# #
|
31
|
+
# # The name can be generated as well:
|
32
|
+
# #
|
33
|
+
# # ```ruby
|
34
|
+
# # create_statistics do |s|
|
35
|
+
# # s.table "users"
|
36
|
+
# # s.columns "family", "name"
|
37
|
+
# # s.kinds :dependencies, :mcv, :ndistinct
|
38
|
+
# # s.comment "Statistics for users' names and families"
|
39
|
+
# # SQL
|
40
|
+
# # ```
|
41
|
+
# #
|
42
|
+
# # Since v14 PostgreSQL have supported expressions in addition to columns:
|
43
|
+
# #
|
44
|
+
# # ```ruby
|
45
|
+
# # create_statistics "users_stats" do |s|
|
46
|
+
# # s.table "users"
|
47
|
+
# # s.columns "family"
|
48
|
+
# # s.expression "length(name)"
|
49
|
+
# # s.kinds :dependencies, :mcv, :ndistinct
|
50
|
+
# # s.comment "Statistics for users' name lengths and families"
|
51
|
+
# # SQL
|
52
|
+
# # ```
|
53
|
+
# #
|
54
|
+
# # as well as statistics for the sole expression (kinds must be blank)
|
55
|
+
# # by columns of some table.
|
56
|
+
# #
|
57
|
+
# # ```ruby
|
58
|
+
# # create_statistics "users_stats" do |s|
|
59
|
+
# # s.table "users"
|
60
|
+
# # s.expression "length(name || ' ' || family)"
|
61
|
+
# # s.comment "Statistics for full name lengths"
|
62
|
+
# # SQL
|
63
|
+
# # ```
|
64
|
+
# #
|
65
|
+
# # Use `if_not_exists: true` to suppress error in case the statistics
|
66
|
+
# # has already been created. This option, though, makes the migration
|
67
|
+
# # irreversible due to uncertainty of the previous state of the database.
|
68
|
+
# def create_statistics(name, **options, &block); end
|
69
|
+
# end
|
59
70
|
module PGTrunk::Operations::Statistics
|
60
71
|
# SQL snippet to fetch statistics in v10-13
|
61
72
|
SQL_V10 = <<~SQL.freeze
|