pg_trunk 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +1 -0
- data/lib/pg_trunk/core/operation/attributes.rb +1 -1
- data/lib/pg_trunk/operations/rules/base.rb +1 -1
- data/lib/pg_trunk/operations/rules/create_rule.rb +1 -1
- data/lib/pg_trunk/operations/rules/drop_rule.rb +2 -2
- data/lib/pg_trunk/operations/rules/rename_rule.rb +1 -1
- data/lib/pg_trunk/operations/rules.rb +1 -1
- 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.rb +1 -0
- data/lib/pg_trunk/version.rb +1 -1
- 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 +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7e02c0f3a19350a502afda3e8697421912161b9bd800eb6302738cc623eb3eb
|
4
|
+
data.tar.gz: c0108a41cabe1fd1eebc0695e222ceafeb27c195b9c8fe2766e1f4ced4142c63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7af9d03c65275a700408841a032cf2f074349943a8d131e54d5dff4f467b9e4bb3fadce657d70469ec2cb1471b41578bef68fc91a92771429d3da84b648161f8
|
7
|
+
data.tar.gz: 9a47edbac23bffb1db4c74f0108f42c06c5e9d7aa28b5d213fed05f908a8e738114db1ad4c38557fde7db7e2d514cabf6646d65a8193c7e5bf664b4d20bc38ff
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,12 @@ The noteworthy changes for each PGTrunk version are included here.
|
|
4
4
|
The format is based on [Keep a Changelog] and this project adheres to [Semantic Versioning].
|
5
5
|
For a complete changelog, see the [commits] for each version via the version links.
|
6
6
|
|
7
|
+
## [0.2.0] (2022-01-26)
|
8
|
+
|
9
|
+
* Add support for sequences (nepalez)
|
10
|
+
* Fix inheritance of attribute aliases (nepalez)
|
11
|
+
* Fix documentation for rules (nepalez)
|
12
|
+
|
7
13
|
## [0.1.3] (2022-01-20)
|
8
14
|
|
9
15
|
* Add support for rules (nepalez)
|
data/README.md
CHANGED
@@ -73,6 +73,7 @@ As of today we support creation, modification and dropping the following objects
|
|
73
73
|
- composite types
|
74
74
|
- domains types
|
75
75
|
- rules
|
76
|
+
- sequences
|
76
77
|
|
77
78
|
For `tables` and `indexes` we reuse the ActiveRecord's native methods.
|
78
79
|
For `check constraints` and `foreign keys` we support both the native definitions inside the table
|
@@ -23,7 +23,7 @@ module PGTrunk::Operations::Rules
|
|
23
23
|
validates :kind, inclusion: { in: %i[instead also] }, allow_nil: true
|
24
24
|
validates :event, inclusion: { in: %i[insert update delete] }, allow_nil: true
|
25
25
|
|
26
|
-
# By default
|
26
|
+
# By default rules are sorted by tables and names.
|
27
27
|
def <=>(other)
|
28
28
|
return unless other.is_a?(self.class)
|
29
29
|
|
@@ -14,7 +14,7 @@
|
|
14
14
|
# # @option options [String] :where (nil) The condition (SQL) for the rule to be applied.
|
15
15
|
# # @option options [String] :command (nil) The SQL command to be added by the rule.
|
16
16
|
# # @yield [r] the block with the rule's definition
|
17
|
-
# # @yieldparam Object receiver of methods specifying the
|
17
|
+
# # @yieldparam Object receiver of methods specifying the rule
|
18
18
|
# # @return [void]
|
19
19
|
# #
|
20
20
|
# # @notice `SELECT` rules are not supported by the gem.
|
@@ -16,7 +16,7 @@
|
|
16
16
|
# # @option options [String] :where (nil) The condition (SQL) for the rule to be applied.
|
17
17
|
# # @option options [String] :command (nil) The SQL command to be added by the rule.
|
18
18
|
# # @yield [r] the block with the rule's definition
|
19
|
-
# # @yieldparam Object receiver of methods specifying the
|
19
|
+
# # @yieldparam Object receiver of methods specifying the rule
|
20
20
|
# # @return [void]
|
21
21
|
# #
|
22
22
|
# # The rule can be identified by the table and explicit name
|
@@ -59,7 +59,7 @@
|
|
59
59
|
# # ```
|
60
60
|
# #
|
61
61
|
# # With the `force: :cascade` option the operation would remove
|
62
|
-
# # all the objects that use the
|
62
|
+
# # all the objects that use the rule.
|
63
63
|
# #
|
64
64
|
# # ```ruby
|
65
65
|
# # drop_rule :users, force: :cascade do |r|
|
@@ -11,7 +11,7 @@
|
|
11
11
|
# # @yieldparam Object receiver of methods specifying the constraint
|
12
12
|
# # @return [void]
|
13
13
|
# #
|
14
|
-
# # A
|
14
|
+
# # A rule can be identified by the table and explicit name
|
15
15
|
# #
|
16
16
|
# # ```ruby
|
17
17
|
# # rename_rule :users, "_forbid_insertion", to: "_skip_insertion"
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
module PGTrunk::Operations::Sequences
|
4
|
+
# @abstract
|
5
|
+
# @private
|
6
|
+
# Base class for operations with sequences
|
7
|
+
class Base < PGTrunk::Operation
|
8
|
+
attribute :type, :string, aliases: :as
|
9
|
+
attribute :increment_by, :integer
|
10
|
+
attribute :min_value, :integer
|
11
|
+
attribute :max_value, :integer
|
12
|
+
attribute :start_with, :integer
|
13
|
+
attribute :cache, :integer
|
14
|
+
attribute :cycle, :boolean
|
15
|
+
attribute :table, :string
|
16
|
+
attribute :column, :string
|
17
|
+
|
18
|
+
def owned_by(table, column)
|
19
|
+
self.table = table
|
20
|
+
self.column = column
|
21
|
+
end
|
22
|
+
|
23
|
+
# Ensure correctness of present values
|
24
|
+
# The table must be defined because the name only
|
25
|
+
# is not enough to identify the constraint.
|
26
|
+
validates :name, presence: true
|
27
|
+
validates :cache, numericality: { greater_than_or_equal_to: 1 }, allow_nil: true
|
28
|
+
validate { errors.add :base, "Increment must not be zero" if increment_by&.zero? }
|
29
|
+
validate do
|
30
|
+
next unless table.present? ^ column.present?
|
31
|
+
|
32
|
+
errors.add :base, "Both table and column must be set"
|
33
|
+
end
|
34
|
+
validate do
|
35
|
+
next if min_value.blank? || max_value.blank? || min_value <= max_value
|
36
|
+
|
37
|
+
errors.add :base, "Min value must not exceed max value"
|
38
|
+
end
|
39
|
+
validate do
|
40
|
+
next if start_with.blank? || min_value.blank? || start_with >= min_value
|
41
|
+
|
42
|
+
errors.add :base, "start value cannot be less than min value"
|
43
|
+
end
|
44
|
+
validate do
|
45
|
+
next if start_with.blank? || max_value.blank? || start_with <= max_value
|
46
|
+
|
47
|
+
errors.add :base, "start value cannot be greater than max value"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Use comparison by name from pg_trunk operations base class (default)
|
51
|
+
# Support name as the only positional argument (default)
|
52
|
+
|
53
|
+
# Snippet to be used in all operations with rules
|
54
|
+
ruby_snippet do |s|
|
55
|
+
s.ruby_param(name.lean) if name.present?
|
56
|
+
s.ruby_param(as: type) if type.present? && from_type.blank?
|
57
|
+
s.ruby_param(to: new_name) if new_name.present?
|
58
|
+
s.ruby_param(if_exists: true) if if_exists
|
59
|
+
s.ruby_param(if_not_exists: true) if if_not_exists
|
60
|
+
s.ruby_param(force: :cascade) if force == :cascade
|
61
|
+
|
62
|
+
s.ruby_line(:type, type, from: from_type) if from_type.present?
|
63
|
+
s.ruby_line(:owned_by, table, column) if table.present? || column.present?
|
64
|
+
s.ruby_line(:increment_by, increment_by, from: from_increment_by) if increment_by&.!= 1
|
65
|
+
s.ruby_line(:min_value, min_value, from: from_min_value) if min_value.present?
|
66
|
+
s.ruby_line(:max_value, max_value, from: from_max_value) if max_value.present?
|
67
|
+
s.ruby_line(:start_with, start_with, from: from_start_with) if custom_start?
|
68
|
+
s.ruby_line(:cache, cache, from: from_cache) if cache&.!= 1
|
69
|
+
s.ruby_line(:cycle, cycle) unless cycle.nil?
|
70
|
+
s.ruby_line(:comment, comment, from: from_comment) if comment.present?
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def custom_start?
|
76
|
+
increment_by&.<(0) ? start_with&.!=(max_value) : start_with&.!=(min_value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -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
|
data/lib/pg_trunk/operations.rb
CHANGED
@@ -9,6 +9,7 @@ module PGTrunk
|
|
9
9
|
require_relative "operations/enums"
|
10
10
|
require_relative "operations/composite_types"
|
11
11
|
require_relative "operations/domains"
|
12
|
+
require_relative "operations/sequences"
|
12
13
|
require_relative "operations/tables"
|
13
14
|
require_relative "operations/views"
|
14
15
|
require_relative "operations/materialized_views"
|
data/lib/pg_trunk/version.rb
CHANGED
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe ActiveRecord::Migration, "#change_sequence" do
|
4
|
+
before { run_migration(old_snippet) }
|
5
|
+
|
6
|
+
let(:old_snippet) do
|
7
|
+
<<~RUBY
|
8
|
+
create_sequence "global_num", as: "integer" do |s|
|
9
|
+
s.increment_by 2
|
10
|
+
s.min_value 0
|
11
|
+
s.max_value 2000
|
12
|
+
s.start_with 1
|
13
|
+
s.cache 10
|
14
|
+
s.cycle true
|
15
|
+
s.comment "Sequence for global numbers (odds then evens)"
|
16
|
+
end
|
17
|
+
RUBY
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with reversible changes" do
|
21
|
+
let(:migration) do
|
22
|
+
<<~RUBY
|
23
|
+
change_sequence "global_num" do |s|
|
24
|
+
s.type "bigint", from: "integer"
|
25
|
+
s.increment_by 3, from: 2
|
26
|
+
s.min_value 1, from: 0
|
27
|
+
s.max_value 3000, from: 2000
|
28
|
+
s.start_with 2, from: 1
|
29
|
+
s.cache 20, from: 10
|
30
|
+
s.cycle false
|
31
|
+
s.comment "Global numbers", from: "Sequence for global numbers (odds then evens)"
|
32
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
let(:new_snippet) do
|
36
|
+
<<~RUBY
|
37
|
+
create_sequence "global_num" do |s|
|
38
|
+
s.increment_by 3
|
39
|
+
s.max_value 3000
|
40
|
+
s.start_with 2
|
41
|
+
s.cache 20
|
42
|
+
s.comment "Global numbers"
|
43
|
+
end
|
44
|
+
RUBY
|
45
|
+
end
|
46
|
+
|
47
|
+
its(:execution) { is_expected.to remove(old_snippet).from_schema }
|
48
|
+
its(:execution) { is_expected.to insert(new_snippet).into_schema }
|
49
|
+
its(:inversion) { is_expected.not_to change_schema }
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with irreversible changes" do
|
53
|
+
let(:migration) do
|
54
|
+
<<~RUBY
|
55
|
+
change_sequence "global_num" do |s|
|
56
|
+
s.type "bigint"
|
57
|
+
s.increment_by 3
|
58
|
+
s.min_value 1
|
59
|
+
s.max_value 3000
|
60
|
+
s.start_with 2
|
61
|
+
s.cache 20
|
62
|
+
s.cycle false
|
63
|
+
s.comment "Global numbers"
|
64
|
+
end
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
let(:new_snippet) do
|
68
|
+
<<~RUBY
|
69
|
+
create_sequence "global_num" do |s|
|
70
|
+
s.increment_by 3
|
71
|
+
s.max_value 3000
|
72
|
+
s.start_with 2
|
73
|
+
s.cache 20
|
74
|
+
s.comment "Global numbers"
|
75
|
+
end
|
76
|
+
RUBY
|
77
|
+
end
|
78
|
+
|
79
|
+
its(:execution) { is_expected.to remove(old_snippet).from_schema }
|
80
|
+
its(:execution) { is_expected.to insert(new_snippet).into_schema }
|
81
|
+
it { is_expected.to be_irreversible.because_of(/undefined values to revert/i) }
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when sequence was absent" do
|
85
|
+
let(:old_snippet) { "" }
|
86
|
+
|
87
|
+
context "without the `:if_exists` option" do
|
88
|
+
let(:migration) do
|
89
|
+
<<~RUBY
|
90
|
+
change_sequence "global_num" do |s|
|
91
|
+
s.comment "Global numbers"
|
92
|
+
end
|
93
|
+
RUBY
|
94
|
+
end
|
95
|
+
|
96
|
+
its(:execution) { is_expected.to raise_error(StandardError) }
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with the `if_exists: true` option" do
|
100
|
+
let(:migration) do
|
101
|
+
<<~RUBY
|
102
|
+
change_sequence "global_num", if_exists: true do |s|
|
103
|
+
s.comment "Global numbers"
|
104
|
+
end
|
105
|
+
RUBY
|
106
|
+
end
|
107
|
+
|
108
|
+
its(:execution) { is_expected.not_to change_schema }
|
109
|
+
it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "without changes" do
|
114
|
+
let(:migration) do
|
115
|
+
<<~RUBY
|
116
|
+
change_sequence "global_num"
|
117
|
+
RUBY
|
118
|
+
end
|
119
|
+
|
120
|
+
it { is_expected.to fail_validation.because(/changes can't be blank/i) }
|
121
|
+
end
|
122
|
+
|
123
|
+
context "without a name" do
|
124
|
+
let(:migration) do
|
125
|
+
<<~RUBY
|
126
|
+
change_sequence do |s|
|
127
|
+
s.comment "Global numbers"
|
128
|
+
end
|
129
|
+
RUBY
|
130
|
+
end
|
131
|
+
|
132
|
+
it { is_expected.to fail_validation.because(/name can't be blank/i) }
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe ActiveRecord::Migration, "#create_sequence" do
|
4
|
+
before_all do
|
5
|
+
run_migration <<~RUBY
|
6
|
+
create_schema :app
|
7
|
+
|
8
|
+
create_table :customers do |t|
|
9
|
+
t.bigint :global_num
|
10
|
+
end
|
11
|
+
RUBY
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with a minimal definition" do
|
15
|
+
let(:migration) do
|
16
|
+
<<~RUBY
|
17
|
+
create_sequence "app.global_num"
|
18
|
+
RUBY
|
19
|
+
end
|
20
|
+
|
21
|
+
its(:execution) { is_expected.to insert(migration).into_schema }
|
22
|
+
its(:inversion) { is_expected.not_to change_schema }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with a table-agnostic definition" do
|
26
|
+
let(:migration) do
|
27
|
+
<<~RUBY
|
28
|
+
create_sequence "app.global_num", as: "integer" do |s|
|
29
|
+
s.increment_by 2
|
30
|
+
s.min_value 0
|
31
|
+
s.max_value 2000
|
32
|
+
s.start_with 1
|
33
|
+
s.cache 10
|
34
|
+
s.cycle true
|
35
|
+
s.comment "Sequence for global numbers (odds then evens)"
|
36
|
+
end
|
37
|
+
RUBY
|
38
|
+
end
|
39
|
+
|
40
|
+
its(:execution) { is_expected.to insert(migration).into_schema }
|
41
|
+
its(:inversion) { is_expected.not_to change_schema }
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with a column-specific definition" do
|
45
|
+
let(:migration) do
|
46
|
+
<<~RUBY
|
47
|
+
create_sequence "global_num" do |s|
|
48
|
+
s.owned_by "customers", "global_num"
|
49
|
+
s.increment_by 2
|
50
|
+
s.min_value 0
|
51
|
+
s.max_value 2000
|
52
|
+
s.start_with 1
|
53
|
+
s.cache 10
|
54
|
+
s.cycle true
|
55
|
+
s.comment "Sequence for customers global_num"
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
end
|
59
|
+
|
60
|
+
its(:execution) { is_expected.to insert(migration).into_schema }
|
61
|
+
its(:inversion) { is_expected.not_to change_schema }
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when the sequence existed" do
|
65
|
+
before { run_migration(migration) }
|
66
|
+
|
67
|
+
context "without the `:if_not_exists` option" do
|
68
|
+
let(:migration) do
|
69
|
+
<<~RUBY
|
70
|
+
create_sequence "app.global_num"
|
71
|
+
RUBY
|
72
|
+
end
|
73
|
+
|
74
|
+
its(:execution) { is_expected.to raise_error(StandardError) }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with the `if_not_exists: true` option" do
|
78
|
+
let(:migration) do
|
79
|
+
<<~RUBY
|
80
|
+
create_sequence "app.global_num", if_not_exists: true
|
81
|
+
RUBY
|
82
|
+
end
|
83
|
+
let(:snippet) do
|
84
|
+
<<~RUBY
|
85
|
+
create_sequence "app.global_num"
|
86
|
+
RUBY
|
87
|
+
end
|
88
|
+
|
89
|
+
its(:execution) { is_expected.not_to change_schema }
|
90
|
+
it { is_expected.to be_irreversible.because_of(/if_not_exists: true/i) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with a zero increment" do
|
95
|
+
let(:migration) do
|
96
|
+
<<~RUBY
|
97
|
+
create_sequence "app.global_number", increment_by: 0
|
98
|
+
RUBY
|
99
|
+
end
|
100
|
+
|
101
|
+
it { is_expected.to fail_validation.because(/increment must not be zero/i) }
|
102
|
+
end
|
103
|
+
|
104
|
+
context "with invalid min..max range" do
|
105
|
+
let(:migration) do
|
106
|
+
<<~RUBY
|
107
|
+
create_sequence "app.global_number", min_value: 2, max_value: 1
|
108
|
+
RUBY
|
109
|
+
end
|
110
|
+
|
111
|
+
it { is_expected.to fail_validation.because(/min value must not exceed max value/i) }
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with start value out of min..max range" do
|
115
|
+
let(:migration) do
|
116
|
+
<<~RUBY
|
117
|
+
create_sequence "app.global_number",
|
118
|
+
min_value: 0,
|
119
|
+
max_value: 10,
|
120
|
+
start_with: -1
|
121
|
+
RUBY
|
122
|
+
end
|
123
|
+
|
124
|
+
it { is_expected.to fail_validation.because(/start value cannot be less than min value/i) }
|
125
|
+
end
|
126
|
+
|
127
|
+
context "with a zero cache" do
|
128
|
+
let(:migration) do
|
129
|
+
<<~RUBY
|
130
|
+
create_sequence "app.global_number", cache: 0
|
131
|
+
RUBY
|
132
|
+
end
|
133
|
+
|
134
|
+
it { is_expected.to fail_validation.because(/cache must be greater than or equal to 1/i) }
|
135
|
+
end
|
136
|
+
|
137
|
+
context "with a wrong type" do
|
138
|
+
let(:migration) do
|
139
|
+
<<~RUBY
|
140
|
+
create_sequence "app.global_number", as: "text"
|
141
|
+
RUBY
|
142
|
+
end
|
143
|
+
|
144
|
+
its(:execution) { is_expected.to raise_error(StandardError) }
|
145
|
+
end
|
146
|
+
|
147
|
+
context "without a name" do
|
148
|
+
let(:migration) do
|
149
|
+
<<~RUBY
|
150
|
+
create_sequence
|
151
|
+
RUBY
|
152
|
+
end
|
153
|
+
|
154
|
+
it { is_expected.to fail_validation.because(/name can't be blank/i) }
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe ActiveRecord::Migration, "#drop_sequence" do
|
4
|
+
before_all { run_migration("create_schema :app") }
|
5
|
+
before { run_migration(old_snippet) }
|
6
|
+
|
7
|
+
let(:old_snippet) do
|
8
|
+
<<~RUBY
|
9
|
+
create_sequence "app.global_num", as: "integer" do |s|
|
10
|
+
s.increment_by 2
|
11
|
+
s.min_value 0
|
12
|
+
s.max_value 2000
|
13
|
+
s.start_with 1
|
14
|
+
s.cache 10
|
15
|
+
s.cycle true
|
16
|
+
s.comment "Sequence for global numbers (odds then evens)"
|
17
|
+
end
|
18
|
+
RUBY
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with a full definition" do
|
22
|
+
let(:migration) do
|
23
|
+
<<~RUBY
|
24
|
+
drop_sequence "app.global_num", as: "integer" do |s|
|
25
|
+
s.increment_by 2
|
26
|
+
s.min_value 0
|
27
|
+
s.max_value 2000
|
28
|
+
s.start_with 1
|
29
|
+
s.cache 10
|
30
|
+
s.cycle true
|
31
|
+
s.comment "Sequence for global numbers (odds then evens)"
|
32
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
|
36
|
+
its(:execution) { is_expected.to remove(old_snippet).from_schema }
|
37
|
+
its(:inversion) { is_expected.not_to change_schema }
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with a minimal definition" do
|
41
|
+
let(:migration) do
|
42
|
+
<<~RUBY
|
43
|
+
drop_sequence "app.global_num"
|
44
|
+
RUBY
|
45
|
+
end
|
46
|
+
let(:new_snippet) do
|
47
|
+
<<~RUBY
|
48
|
+
create_sequence "app.global_num"
|
49
|
+
RUBY
|
50
|
+
end
|
51
|
+
|
52
|
+
its(:execution) { is_expected.to remove(old_snippet).from_schema }
|
53
|
+
its(:inversion) { is_expected.to remove(old_snippet).from_schema }
|
54
|
+
its(:inversion) { is_expected.to insert(new_snippet).into_schema }
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when the sequence was absent" do
|
58
|
+
before { run_migration(migration) }
|
59
|
+
|
60
|
+
context "without the `:if_exists` option" do
|
61
|
+
let(:migration) do
|
62
|
+
<<~RUBY
|
63
|
+
drop_sequence "app.global_num"
|
64
|
+
RUBY
|
65
|
+
end
|
66
|
+
|
67
|
+
its(:execution) { is_expected.to raise_error(StandardError) }
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with the `if_exists: true` option" do
|
71
|
+
let(:migration) do
|
72
|
+
<<~RUBY
|
73
|
+
drop_sequence "app.global_num", if_exists: true
|
74
|
+
RUBY
|
75
|
+
end
|
76
|
+
|
77
|
+
its(:execution) { is_expected.not_to change_schema }
|
78
|
+
it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "with the force: :cascade option" do
|
83
|
+
let(:migration) do
|
84
|
+
<<~RUBY
|
85
|
+
drop_sequence "app.global_num", force: :cascade
|
86
|
+
RUBY
|
87
|
+
end
|
88
|
+
|
89
|
+
its(:execution) { is_expected.to remove(old_snippet).from_schema }
|
90
|
+
it { is_expected.to be_irreversible.because_of(/force: :cascade/i) }
|
91
|
+
end
|
92
|
+
|
93
|
+
context "without a name" do
|
94
|
+
let(:migration) do
|
95
|
+
<<~RUBY
|
96
|
+
drop_sequence
|
97
|
+
RUBY
|
98
|
+
end
|
99
|
+
|
100
|
+
it { is_expected.to fail_validation.because(/name can't be blank/i) }
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe ActiveRecord::Migration, "#rename_sequence" do
|
4
|
+
before_all { run_migration "create_schema :seq" }
|
5
|
+
before { run_migration(old_snippet) }
|
6
|
+
|
7
|
+
let(:old_snippet) do
|
8
|
+
<<~RUBY
|
9
|
+
create_sequence "global_num" do |s|
|
10
|
+
s.increment_by 2
|
11
|
+
s.min_value 0
|
12
|
+
s.max_value 2000
|
13
|
+
s.start_with 1
|
14
|
+
s.cache 10
|
15
|
+
s.cycle true
|
16
|
+
s.comment "Sequence for global numbers (odds then evens)"
|
17
|
+
end
|
18
|
+
RUBY
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with a new name" do
|
22
|
+
let(:migration) do
|
23
|
+
<<~RUBY
|
24
|
+
rename_sequence "global_num", to: "seq.global_number"
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
let(:new_snippet) do
|
28
|
+
<<~RUBY
|
29
|
+
create_sequence "seq.global_number" do |s|
|
30
|
+
s.increment_by 2
|
31
|
+
s.min_value 0
|
32
|
+
s.max_value 2000
|
33
|
+
s.start_with 1
|
34
|
+
s.cache 10
|
35
|
+
s.cycle true
|
36
|
+
s.comment "Sequence for global numbers (odds then evens)"
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
end
|
40
|
+
|
41
|
+
its(:execution) { is_expected.to remove(old_snippet).from_schema }
|
42
|
+
its(:execution) { is_expected.to insert(new_snippet).into_schema }
|
43
|
+
its(:inversion) { is_expected.not_to change_schema }
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when sequence was absent" do
|
47
|
+
let(:old_snippet) { "" }
|
48
|
+
|
49
|
+
context "without the `:if_exists` option" do
|
50
|
+
let(:migration) do
|
51
|
+
<<~RUBY
|
52
|
+
rename_sequence "global_num", to: "global_number"
|
53
|
+
RUBY
|
54
|
+
end
|
55
|
+
|
56
|
+
its(:execution) { is_expected.to raise_error(StandardError) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with the `if_exists: true` option" do
|
60
|
+
let(:migration) do
|
61
|
+
<<~RUBY
|
62
|
+
rename_sequence "global_num", to: "global_number", if_exists: true
|
63
|
+
RUBY
|
64
|
+
end
|
65
|
+
|
66
|
+
its(:execution) { is_expected.not_to change_schema }
|
67
|
+
it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "with the same name" do
|
72
|
+
let(:migration) do
|
73
|
+
<<~RUBY
|
74
|
+
rename_sequence "global_num", to: "global_num"
|
75
|
+
RUBY
|
76
|
+
end
|
77
|
+
|
78
|
+
it { is_expected.to fail_validation.because(/new name must be different/i) }
|
79
|
+
end
|
80
|
+
|
81
|
+
context "without new name" do
|
82
|
+
let(:migration) do
|
83
|
+
<<~RUBY
|
84
|
+
rename_sequence "global_num"
|
85
|
+
RUBY
|
86
|
+
end
|
87
|
+
|
88
|
+
it { is_expected.to fail_validation.because(/new name can't be blank/i) }
|
89
|
+
end
|
90
|
+
|
91
|
+
context "without current name" do
|
92
|
+
let(:migration) do
|
93
|
+
<<~RUBY
|
94
|
+
rename_sequence to: "seq.global_number"
|
95
|
+
RUBY
|
96
|
+
end
|
97
|
+
|
98
|
+
it { is_expected.to fail_validation.because(/name can't be blank/i) }
|
99
|
+
end
|
100
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_trunk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kozin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -174,6 +174,12 @@ files:
|
|
174
174
|
- lib/pg_trunk/operations/rules/create_rule.rb
|
175
175
|
- lib/pg_trunk/operations/rules/drop_rule.rb
|
176
176
|
- lib/pg_trunk/operations/rules/rename_rule.rb
|
177
|
+
- lib/pg_trunk/operations/sequences.rb
|
178
|
+
- lib/pg_trunk/operations/sequences/base.rb
|
179
|
+
- lib/pg_trunk/operations/sequences/change_sequence.rb
|
180
|
+
- lib/pg_trunk/operations/sequences/create_sequence.rb
|
181
|
+
- lib/pg_trunk/operations/sequences/drop_sequence.rb
|
182
|
+
- lib/pg_trunk/operations/sequences/rename_sequence.rb
|
177
183
|
- lib/pg_trunk/operations/statistics.rb
|
178
184
|
- lib/pg_trunk/operations/statistics/base.rb
|
179
185
|
- lib/pg_trunk/operations/statistics/create_statistics.rb
|
@@ -246,6 +252,10 @@ files:
|
|
246
252
|
- spec/operations/rules/create_rule_spec.rb
|
247
253
|
- spec/operations/rules/drop_rule_spec.rb
|
248
254
|
- spec/operations/rules/rename_rule_spec.rb
|
255
|
+
- spec/operations/sequences/change_sequence_spec.rb
|
256
|
+
- spec/operations/sequences/create_sequence_spec.rb
|
257
|
+
- spec/operations/sequences/drop_sequence_spec.rb
|
258
|
+
- spec/operations/sequences/rename_sequence_spec.rb
|
249
259
|
- spec/operations/statistics/create_statistics_spec.rb
|
250
260
|
- spec/operations/statistics/drop_statistics_spec.rb
|
251
261
|
- spec/operations/statistics/rename_statistics_spec.rb
|