pg_trunk 0.1.3 → 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/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
|