pg_trunk 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +4 -15
  3. data/CHANGELOG.md +21 -0
  4. data/README.md +3 -1
  5. data/lib/pg_trunk/core/operation/attributes.rb +1 -1
  6. data/lib/pg_trunk/core/railtie/custom_types.rb +5 -6
  7. data/lib/pg_trunk/operations/check_constraints/add_check_constraint.rb +42 -33
  8. data/lib/pg_trunk/operations/check_constraints/drop_check_constraint.rb +51 -40
  9. data/lib/pg_trunk/operations/check_constraints/rename_check_constraint.rb +39 -30
  10. data/lib/pg_trunk/operations/check_constraints/validate_check_constraint.rb +28 -21
  11. data/lib/pg_trunk/operations/composite_types/change_composite_type.rb +59 -50
  12. data/lib/pg_trunk/operations/composite_types/create_composite_type.rb +23 -19
  13. data/lib/pg_trunk/operations/composite_types/drop_composite_type.rb +48 -43
  14. data/lib/pg_trunk/operations/composite_types/rename_composite_type.rb +15 -12
  15. data/lib/pg_trunk/operations/domains/change_domain.rb +53 -47
  16. data/lib/pg_trunk/operations/domains/create_domain.rb +28 -25
  17. data/lib/pg_trunk/operations/domains/drop_domain.rb +50 -41
  18. data/lib/pg_trunk/operations/domains/rename_domain.rb +17 -12
  19. data/lib/pg_trunk/operations/enums/change_enum.rb +37 -32
  20. data/lib/pg_trunk/operations/enums/create_enum.rb +23 -20
  21. data/lib/pg_trunk/operations/enums/drop_enum.rb +50 -39
  22. data/lib/pg_trunk/operations/enums/rename_enum.rb +17 -12
  23. data/lib/pg_trunk/operations/foreign_keys/add_foreign_key.rb +58 -49
  24. data/lib/pg_trunk/operations/foreign_keys/drop_foreign_key.rb +57 -48
  25. data/lib/pg_trunk/operations/foreign_keys/rename_foreign_key.rb +38 -29
  26. data/lib/pg_trunk/operations/functions/change_function.rb +53 -47
  27. data/lib/pg_trunk/operations/functions/create_function.rb +75 -64
  28. data/lib/pg_trunk/operations/functions/drop_function.rb +78 -65
  29. data/lib/pg_trunk/operations/functions/rename_function.rb +29 -22
  30. data/lib/pg_trunk/operations/materialized_views/change_materialized_view.rb +65 -55
  31. data/lib/pg_trunk/operations/materialized_views/create_materialized_view.rb +82 -71
  32. data/lib/pg_trunk/operations/materialized_views/drop_materialized_view.rb +59 -46
  33. data/lib/pg_trunk/operations/materialized_views/refresh_materialized_view.rb +29 -24
  34. data/lib/pg_trunk/operations/materialized_views/rename_materialized_view.rb +29 -22
  35. data/lib/pg_trunk/operations/procedures/change_procedure.rb +53 -46
  36. data/lib/pg_trunk/operations/procedures/create_procedure.rb +63 -52
  37. data/lib/pg_trunk/operations/procedures/drop_procedure.rb +56 -45
  38. data/lib/pg_trunk/operations/procedures/rename_procedure.rb +29 -22
  39. data/lib/pg_trunk/operations/rules/base.rb +77 -0
  40. data/lib/pg_trunk/operations/rules/create_rule.rb +155 -0
  41. data/lib/pg_trunk/operations/rules/drop_rule.rb +94 -0
  42. data/lib/pg_trunk/operations/rules/rename_rule.rb +62 -0
  43. data/lib/pg_trunk/operations/rules.rb +13 -0
  44. data/lib/pg_trunk/operations/sequences/base.rb +79 -0
  45. data/lib/pg_trunk/operations/sequences/change_sequence.rb +142 -0
  46. data/lib/pg_trunk/operations/sequences/create_sequence.rb +180 -0
  47. data/lib/pg_trunk/operations/sequences/drop_sequence.rb +82 -0
  48. data/lib/pg_trunk/operations/sequences/rename_sequence.rb +64 -0
  49. data/lib/pg_trunk/operations/sequences.rb +14 -0
  50. data/lib/pg_trunk/operations/statistics/create_statistics.rb +67 -56
  51. data/lib/pg_trunk/operations/statistics/drop_statistics.rb +64 -53
  52. data/lib/pg_trunk/operations/statistics/rename_statistics.rb +18 -13
  53. data/lib/pg_trunk/operations/triggers/change_trigger.rb +23 -18
  54. data/lib/pg_trunk/operations/triggers/create_trigger.rb +63 -54
  55. data/lib/pg_trunk/operations/triggers/drop_trigger.rb +55 -46
  56. data/lib/pg_trunk/operations/triggers/rename_trigger.rb +51 -48
  57. data/lib/pg_trunk/operations/views/change_view.rb +47 -38
  58. data/lib/pg_trunk/operations/views/create_view.rb +56 -45
  59. data/lib/pg_trunk/operations/views/drop_view.rb +59 -46
  60. data/lib/pg_trunk/operations/views/rename_view.rb +27 -20
  61. data/lib/pg_trunk/operations.rb +2 -0
  62. data/lib/pg_trunk/version.rb +1 -1
  63. data/pg_trunk.gemspec +0 -1
  64. data/spec/operations/rules/create_rule_spec.rb +119 -0
  65. data/spec/operations/rules/drop_rule_spec.rb +117 -0
  66. data/spec/operations/rules/rename_rule_spec.rb +148 -0
  67. data/spec/operations/sequences/change_sequence_spec.rb +134 -0
  68. data/spec/operations/sequences/create_sequence_spec.rb +156 -0
  69. data/spec/operations/sequences/drop_sequence_spec.rb +102 -0
  70. data/spec/operations/sequences/rename_sequence_spec.rb +100 -0
  71. 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
- # @!method ActiveRecord::Migration#create_statistics(name, **options, &block)
4
- # Create a custom statistics
5
- #
6
- # @param [#to_s] name (nil) The qualified name of the statistics
7
- # @option [Boolean] :if_not_exists (false)
8
- # Suppress the error when the statistics is already exist
9
- # @option [#to_s] table (nil)
10
- # The qualified name of the table whose statistics will be collected
11
- # @option [Array<Symbol>] kinds ([:dependencies, :mcv, :ndistinct])
12
- # The kinds of statistics to be collected (all by default).
13
- # Supported values in the array: :dependencies, :mcv, :ndistinct
14
- # @option [#to_s] :comment The description of the statistics
15
- # @yield [Proc] the block with the statistics' definition
16
- # @yieldparam The receiver of methods specifying the statistics
17
- #
18
- # The statistics can be created with explicit name:
19
- #
20
- # create_statistics "users_stats" do |s|
21
- # s.table "users"
22
- # s.columns "family", "name"
23
- # s.kinds :dependencies, :mcv, :ndistinct
24
- # s.comment "Statistics for users' names and families"
25
- # SQL
26
- #
27
- # The name can be generated as well:
28
- #
29
- # create_statistics do |s|
30
- # s.table "users"
31
- # s.columns "family", "name"
32
- # s.kinds :dependencies, :mcv, :ndistinct
33
- # s.comment "Statistics for users' names and families"
34
- # SQL
35
- #
36
- # Since v14 PostgreSQL have supported expressions in addition to columns:
37
- #
38
- # create_statistics "users_stats" do |s|
39
- # s.table "users"
40
- # s.columns "family"
41
- # s.expression "length(name)"
42
- # s.kinds :dependencies, :mcv, :ndistinct
43
- # s.comment "Statistics for users' name lengths and families"
44
- # SQL
45
- #
46
- # as well as statistics for the sole expression (kinds must be blank)
47
- # by columns of some table.
48
- #
49
- # create_statistics "users_stats" do |s|
50
- # s.table "users"
51
- # s.expression "length(name || ' ' || family)"
52
- # s.comment "Statistics for full name lengths"
53
- # SQL
54
- #
55
- # Use `if_not_exists: true` to suppress error in case the statistics
56
- # has already been created. This option, though, makes the migration
57
- # irreversible due to uncertainty of the previous state of the database.
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