pg_trunk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +87 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +4 -0
  5. data/.rubocop.yml +92 -0
  6. data/.yardopts +4 -0
  7. data/CHANGELOG.md +31 -0
  8. data/CONTRIBUTING.md +17 -0
  9. data/Gemfile +22 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +141 -0
  12. data/Rakefile +16 -0
  13. data/bin/console +8 -0
  14. data/bin/rake +19 -0
  15. data/bin/rspec +19 -0
  16. data/bin/setup +8 -0
  17. data/bin/yard +19 -0
  18. data/lib/pg_trunk/core/adapters/postgres.rb +80 -0
  19. data/lib/pg_trunk/core/dependencies_resolver.rb +101 -0
  20. data/lib/pg_trunk/core/generators.rb +140 -0
  21. data/lib/pg_trunk/core/operation/attributes.rb +78 -0
  22. data/lib/pg_trunk/core/operation/callbacks.rb +40 -0
  23. data/lib/pg_trunk/core/operation/generators.rb +51 -0
  24. data/lib/pg_trunk/core/operation/inversion.rb +70 -0
  25. data/lib/pg_trunk/core/operation/registration.rb +55 -0
  26. data/lib/pg_trunk/core/operation/ruby_builder.rb +112 -0
  27. data/lib/pg_trunk/core/operation/ruby_helpers.rb +99 -0
  28. data/lib/pg_trunk/core/operation/sql_helpers.rb +44 -0
  29. data/lib/pg_trunk/core/operation/validations.rb +21 -0
  30. data/lib/pg_trunk/core/operation.rb +78 -0
  31. data/lib/pg_trunk/core/qualified_name.rb +165 -0
  32. data/lib/pg_trunk/core/railtie/command_recorder.rb +30 -0
  33. data/lib/pg_trunk/core/railtie/custom_types.rb +37 -0
  34. data/lib/pg_trunk/core/railtie/migration.rb +50 -0
  35. data/lib/pg_trunk/core/railtie/migrator.rb +22 -0
  36. data/lib/pg_trunk/core/railtie/schema_dumper.rb +75 -0
  37. data/lib/pg_trunk/core/railtie/schema_migration.rb +22 -0
  38. data/lib/pg_trunk/core/railtie/statements.rb +21 -0
  39. data/lib/pg_trunk/core/railtie.rb +35 -0
  40. data/lib/pg_trunk/core/registry.rb +159 -0
  41. data/lib/pg_trunk/core/serializers/array_of_hashes_serializer.rb +28 -0
  42. data/lib/pg_trunk/core/serializers/array_of_strings_serializer.rb +29 -0
  43. data/lib/pg_trunk/core/serializers/array_of_symbols_serializer.rb +28 -0
  44. data/lib/pg_trunk/core/serializers/array_serializer.rb +22 -0
  45. data/lib/pg_trunk/core/serializers/lowercase_string_serializer.rb +21 -0
  46. data/lib/pg_trunk/core/serializers/multiline_text_serializer.rb +21 -0
  47. data/lib/pg_trunk/core/serializers/qualified_name_serializer.rb +27 -0
  48. data/lib/pg_trunk/core/serializers/symbol_serializer.rb +22 -0
  49. data/lib/pg_trunk/core/serializers.rb +16 -0
  50. data/lib/pg_trunk/core/validators/all_items_valid_validator.rb +15 -0
  51. data/lib/pg_trunk/core/validators/difference_validator.rb +19 -0
  52. data/lib/pg_trunk/core/validators.rb +10 -0
  53. data/lib/pg_trunk/core.rb +21 -0
  54. data/lib/pg_trunk/generators.rb +7 -0
  55. data/lib/pg_trunk/operations/check_constraints/add_check_constraint.rb +109 -0
  56. data/lib/pg_trunk/operations/check_constraints/base.rb +69 -0
  57. data/lib/pg_trunk/operations/check_constraints/drop_check_constraint.rb +60 -0
  58. data/lib/pg_trunk/operations/check_constraints/rename_check_constraint.rb +54 -0
  59. data/lib/pg_trunk/operations/check_constraints/validate_check_constraint.rb +39 -0
  60. data/lib/pg_trunk/operations/check_constraints.rb +14 -0
  61. data/lib/pg_trunk/operations/composite_types/base.rb +61 -0
  62. data/lib/pg_trunk/operations/composite_types/change_composite_type.rb +136 -0
  63. data/lib/pg_trunk/operations/composite_types/column.rb +118 -0
  64. data/lib/pg_trunk/operations/composite_types/create_composite_type.rb +99 -0
  65. data/lib/pg_trunk/operations/composite_types/drop_composite_type.rb +67 -0
  66. data/lib/pg_trunk/operations/composite_types/rename_composite_type.rb +44 -0
  67. data/lib/pg_trunk/operations/composite_types.rb +15 -0
  68. data/lib/pg_trunk/operations/domains/base.rb +46 -0
  69. data/lib/pg_trunk/operations/domains/change_domain.rb +140 -0
  70. data/lib/pg_trunk/operations/domains/constraint.rb +93 -0
  71. data/lib/pg_trunk/operations/domains/create_domain.rb +124 -0
  72. data/lib/pg_trunk/operations/domains/drop_domain.rb +65 -0
  73. data/lib/pg_trunk/operations/domains/rename_domain.rb +44 -0
  74. data/lib/pg_trunk/operations/domains.rb +15 -0
  75. data/lib/pg_trunk/operations/enums/base.rb +47 -0
  76. data/lib/pg_trunk/operations/enums/change.rb +55 -0
  77. data/lib/pg_trunk/operations/enums/change_enum.rb +119 -0
  78. data/lib/pg_trunk/operations/enums/create_enum.rb +83 -0
  79. data/lib/pg_trunk/operations/enums/drop_enum.rb +63 -0
  80. data/lib/pg_trunk/operations/enums/rename_enum.rb +44 -0
  81. data/lib/pg_trunk/operations/enums.rb +15 -0
  82. data/lib/pg_trunk/operations/foreign_keys/add_foreign_key.rb +174 -0
  83. data/lib/pg_trunk/operations/foreign_keys/base.rb +155 -0
  84. data/lib/pg_trunk/operations/foreign_keys/drop_foreign_key.rb +76 -0
  85. data/lib/pg_trunk/operations/foreign_keys/rename_foreign_key.rb +63 -0
  86. data/lib/pg_trunk/operations/foreign_keys.rb +16 -0
  87. data/lib/pg_trunk/operations/functions/base.rb +54 -0
  88. data/lib/pg_trunk/operations/functions/change_function.rb +108 -0
  89. data/lib/pg_trunk/operations/functions/create_function.rb +198 -0
  90. data/lib/pg_trunk/operations/functions/drop_function.rb +88 -0
  91. data/lib/pg_trunk/operations/functions/rename_function.rb +57 -0
  92. data/lib/pg_trunk/operations/functions.rb +14 -0
  93. data/lib/pg_trunk/operations/indexes/add_index.rb +68 -0
  94. data/lib/pg_trunk/operations/indexes.rb +10 -0
  95. data/lib/pg_trunk/operations/materialized_views/base.rb +79 -0
  96. data/lib/pg_trunk/operations/materialized_views/change_materialized_view.rb +139 -0
  97. data/lib/pg_trunk/operations/materialized_views/column.rb +94 -0
  98. data/lib/pg_trunk/operations/materialized_views/create_materialized_view.rb +170 -0
  99. data/lib/pg_trunk/operations/materialized_views/drop_materialized_view.rb +70 -0
  100. data/lib/pg_trunk/operations/materialized_views/refresh_materialized_view.rb +48 -0
  101. data/lib/pg_trunk/operations/materialized_views/rename_materialized_view.rb +61 -0
  102. data/lib/pg_trunk/operations/materialized_views.rb +17 -0
  103. data/lib/pg_trunk/operations/procedures/base.rb +42 -0
  104. data/lib/pg_trunk/operations/procedures/change_procedure.rb +107 -0
  105. data/lib/pg_trunk/operations/procedures/create_procedure.rb +146 -0
  106. data/lib/pg_trunk/operations/procedures/drop_procedure.rb +66 -0
  107. data/lib/pg_trunk/operations/procedures/rename_procedure.rb +57 -0
  108. data/lib/pg_trunk/operations/procedures.rb +14 -0
  109. data/lib/pg_trunk/operations/statistics/base.rb +94 -0
  110. data/lib/pg_trunk/operations/statistics/create_statistics.rb +181 -0
  111. data/lib/pg_trunk/operations/statistics/drop_statistics.rb +75 -0
  112. data/lib/pg_trunk/operations/statistics/rename_statistics.rb +48 -0
  113. data/lib/pg_trunk/operations/statistics.rb +13 -0
  114. data/lib/pg_trunk/operations/tables/create_table.rb +75 -0
  115. data/lib/pg_trunk/operations/tables.rb +10 -0
  116. data/lib/pg_trunk/operations/triggers/base.rb +119 -0
  117. data/lib/pg_trunk/operations/triggers/change_trigger.rb +82 -0
  118. data/lib/pg_trunk/operations/triggers/create_trigger.rb +208 -0
  119. data/lib/pg_trunk/operations/triggers/drop_trigger.rb +66 -0
  120. data/lib/pg_trunk/operations/triggers/rename_trigger.rb +71 -0
  121. data/lib/pg_trunk/operations/triggers.rb +14 -0
  122. data/lib/pg_trunk/operations/views/base.rb +38 -0
  123. data/lib/pg_trunk/operations/views/change_view.rb +90 -0
  124. data/lib/pg_trunk/operations/views/create_view.rb +115 -0
  125. data/lib/pg_trunk/operations/views/drop_view.rb +69 -0
  126. data/lib/pg_trunk/operations/views/rename_view.rb +58 -0
  127. data/lib/pg_trunk/operations/views.rb +14 -0
  128. data/lib/pg_trunk/operations.rb +23 -0
  129. data/lib/pg_trunk/version.rb +6 -0
  130. data/lib/pg_trunk.rb +27 -0
  131. data/pg_trunk.gemspec +34 -0
  132. data/spec/dummy/.gitignore +16 -0
  133. data/spec/dummy/Rakefile +15 -0
  134. data/spec/dummy/bin/bundle +6 -0
  135. data/spec/dummy/bin/rails +6 -0
  136. data/spec/dummy/bin/rake +6 -0
  137. data/spec/dummy/config/application.rb +18 -0
  138. data/spec/dummy/config/boot.rb +7 -0
  139. data/spec/dummy/config/database.yml +14 -0
  140. data/spec/dummy/config/environment.rb +7 -0
  141. data/spec/dummy/config.ru +6 -0
  142. data/spec/dummy/db/materialized_views/admin_users_v01.sql +1 -0
  143. data/spec/dummy/db/migrate/.keep +0 -0
  144. data/spec/dummy/db/schema.rb +18 -0
  145. data/spec/dummy/db/views/admin_users_v01.sql +1 -0
  146. data/spec/dummy/db/views/admin_users_v02.sql +1 -0
  147. data/spec/operations/check_constraints/add_check_constraint_spec.rb +85 -0
  148. data/spec/operations/check_constraints/drop_check_constraint_spec.rb +111 -0
  149. data/spec/operations/check_constraints/rename_check_constraint_spec.rb +90 -0
  150. data/spec/operations/composite_types/change_composite_type_spec.rb +257 -0
  151. data/spec/operations/composite_types/create_composite_type_spec.rb +55 -0
  152. data/spec/operations/composite_types/drop_composite_type_spec.rb +109 -0
  153. data/spec/operations/composite_types/rename_composite_type_spec.rb +74 -0
  154. data/spec/operations/dependency_resolver_spec.rb +177 -0
  155. data/spec/operations/domains/change_domain_spec.rb +287 -0
  156. data/spec/operations/domains/create_domain_spec.rb +69 -0
  157. data/spec/operations/domains/drop_domain_spec.rb +119 -0
  158. data/spec/operations/domains/rename_domain_spec.rb +70 -0
  159. data/spec/operations/enums/change_enum_spec.rb +157 -0
  160. data/spec/operations/enums/create_enum_spec.rb +40 -0
  161. data/spec/operations/enums/drop_enum_spec.rb +120 -0
  162. data/spec/operations/enums/rename_enum_spec.rb +72 -0
  163. data/spec/operations/foreign_keys/add_foreign_key_spec.rb +208 -0
  164. data/spec/operations/foreign_keys/drop_foreign_key_spec.rb +167 -0
  165. data/spec/operations/foreign_keys/rename_foreign_key_spec.rb +101 -0
  166. data/spec/operations/functions/change_function_spec.rb +166 -0
  167. data/spec/operations/functions/create_function_spec.rb +192 -0
  168. data/spec/operations/functions/drop_function_spec.rb +182 -0
  169. data/spec/operations/functions/rename_function_spec.rb +101 -0
  170. data/spec/operations/indexes/add_index_spec.rb +94 -0
  171. data/spec/operations/materialized_views/change_materialized_view_spec.rb +190 -0
  172. data/spec/operations/materialized_views/create_materialized_view_spec.rb +144 -0
  173. data/spec/operations/materialized_views/drop_materialized_view_spec.rb +145 -0
  174. data/spec/operations/materialized_views/refresh_materialized_view_spec.rb +79 -0
  175. data/spec/operations/materialized_views/rename_materialized_view_spec.rb +88 -0
  176. data/spec/operations/procedures/change_procedure_spec.rb +175 -0
  177. data/spec/operations/procedures/create_procedure_spec.rb +151 -0
  178. data/spec/operations/procedures/drop_procedure_spec.rb +159 -0
  179. data/spec/operations/procedures/rename_procedure_spec.rb +107 -0
  180. data/spec/operations/statistics/create_statistics_spec.rb +230 -0
  181. data/spec/operations/statistics/drop_statistics_spec.rb +106 -0
  182. data/spec/operations/statistics/rename_statistics_spec.rb +129 -0
  183. data/spec/operations/tables/create_table_spec.rb +53 -0
  184. data/spec/operations/tables/rename_table_spec.rb +37 -0
  185. data/spec/operations/triggers/change_trigger_spec.rb +195 -0
  186. data/spec/operations/triggers/create_trigger_spec.rb +104 -0
  187. data/spec/operations/triggers/drop_trigger_spec.rb +124 -0
  188. data/spec/operations/triggers/rename_trigger_spec.rb +160 -0
  189. data/spec/operations/views/change_view_spec.rb +144 -0
  190. data/spec/operations/views/create_view_spec.rb +134 -0
  191. data/spec/operations/views/drop_view_spec.rb +146 -0
  192. data/spec/operations/views/rename_view_spec.rb +85 -0
  193. data/spec/pg_trunk/dependencies_resolver_spec.rb +43 -0
  194. data/spec/spec_helper.rb +28 -0
  195. data/spec/support/migrations_helper.rb +376 -0
  196. metadata +348 -0
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#rename_materialized_view" do
4
+ before_all do
5
+ run_migration <<~RUBY
6
+ create_table :users do |t|
7
+ t.string :name
8
+ t.boolean :admin
9
+ end
10
+ RUBY
11
+ end
12
+ before { run_migration(old_snippet) }
13
+
14
+ let(:old_snippet) do
15
+ <<~RUBY
16
+ create_materialized_view "admins" do |v|
17
+ v.sql_definition <<~Q.chomp
18
+ SELECT users.id, users.name
19
+ FROM users
20
+ WHERE users.admin
21
+ Q
22
+ end
23
+ RUBY
24
+ end
25
+ let(:old_query) { "SELECT * FROM admins;" }
26
+
27
+ context "with a new name" do
28
+ let(:migration) do
29
+ <<~RUBY
30
+ rename_materialized_view :admins, to: :admin_users
31
+ RUBY
32
+ end
33
+ let(:new_snippet) do
34
+ <<~RUBY
35
+ create_materialized_view "admin_users" do |v|
36
+ v.sql_definition <<~Q.chomp
37
+ SELECT users.id, users.name
38
+ FROM users
39
+ WHERE users.admin
40
+ Q
41
+ end
42
+ RUBY
43
+ end
44
+ let(:new_query) { "SELECT * FROM admin_users;" }
45
+
46
+ its(:execution) { is_expected.to enable_sql_request(new_query) }
47
+ its(:execution) { is_expected.to disable_sql_request(old_query) }
48
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
49
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
50
+
51
+ its(:inversion) { is_expected.to disable_sql_request(new_query) }
52
+ its(:inversion) { is_expected.to enable_sql_request(old_query) }
53
+ its(:inversion) { is_expected.not_to change_schema }
54
+ end
55
+
56
+ context "with the same name" do
57
+ let(:migration) do
58
+ <<~RUBY
59
+ rename_materialized_view :admins, to: "public.admins"
60
+ RUBY
61
+ end
62
+
63
+ it { is_expected.to fail_validation.because(/new name must be different/i) }
64
+ end
65
+
66
+ context "when a materialized view was absent" do
67
+ context "without the `if_exists` option" do
68
+ let(:migration) do
69
+ <<~RUBY
70
+ rename_materialized_view :weird, to: :admin_users
71
+ RUBY
72
+ end
73
+
74
+ its(:execution) { is_expected.to raise_error(StandardError) }
75
+ end
76
+
77
+ context "with the `if_exists: true` option" do
78
+ let(:migration) do
79
+ <<~RUBY
80
+ rename_materialized_view :weird, to: :admin_users, if_exists: true
81
+ RUBY
82
+ end
83
+
84
+ its(:execution) { is_expected.not_to change_schema }
85
+ it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#change_procedure", since_version: 11 do
4
+ before do
5
+ run_migration <<~RUBY
6
+ create_procedure "set_foo(a integer)" do |p|
7
+ p.language "plpgsql"
8
+ p.body "BEGIN set custom.foo = a; END;"
9
+ p.comment "Set foo"
10
+ end
11
+
12
+ # Overload the procedure to ensure a proper one is found
13
+ create_procedure "set_foo(a text)",
14
+ language: "plpgsql",
15
+ body: "BEGIN set custom.foo = a; END;"
16
+ RUBY
17
+ end
18
+
19
+ let(:old_snippet) do
20
+ <<~RUBY
21
+ create_procedure "set_foo(a integer)" do |p|
22
+ p.language "plpgsql"
23
+ p.body "BEGIN set custom.foo = a; END;"
24
+ p.comment "Set foo"
25
+ end
26
+ RUBY
27
+ end
28
+
29
+ context "with implicitly reversible changes" do
30
+ let(:migration) do
31
+ <<~RUBY
32
+ change_procedure "set_foo(a integer)", security: :definer
33
+ RUBY
34
+ end
35
+ let(:new_snippet) do
36
+ <<~RUBY
37
+ create_procedure "set_foo(a integer)" do |p|
38
+ p.language "plpgsql"
39
+ p.security :definer
40
+ p.body "BEGIN set custom.foo = a; END;"
41
+ p.comment "Set foo"
42
+ end
43
+ RUBY
44
+ end
45
+
46
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
47
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
48
+ its(:inversion) { is_expected.not_to change_schema }
49
+ end
50
+
51
+ context "with explicitly reversible changes" do
52
+ let(:migration) do
53
+ <<~RUBY
54
+ change_procedure "set_foo(a integer)" do |p|
55
+ p.body <<~PLPGSQL.chomp, from: <<~PLPGSQL.chomp
56
+ DECLARE
57
+ b integer := 2 * a;
58
+ BEGIN
59
+ set custom.foo = b;
60
+ END;
61
+ PLPGSQL
62
+ BEGIN set custom.foo = a; END;
63
+ PLPGSQL
64
+ p.comment "Set doubled value to foo", from: "Set foo"
65
+ end
66
+ RUBY
67
+ end
68
+ let(:new_snippet) do
69
+ <<~RUBY
70
+ create_procedure "set_foo(a integer)" do |p|
71
+ p.language "plpgsql"
72
+ p.body <<~Q.chomp
73
+ DECLARE
74
+ b integer := 2 * a;
75
+ BEGIN
76
+ set custom.foo = b;
77
+ END;
78
+ Q
79
+ p.comment "Set doubled value to foo"
80
+ end
81
+ RUBY
82
+ end
83
+
84
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
85
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
86
+ its(:inversion) { is_expected.not_to change_schema }
87
+ end
88
+
89
+ context "with irreversible changes" do
90
+ let(:migration) do
91
+ <<~RUBY
92
+ change_procedure "set_foo(a integer)" do |p|
93
+ p.body <<~PLPGSQL.chomp
94
+ DECLARE
95
+ b integer := 2 * a;
96
+ BEGIN
97
+ set custom.foo = b;
98
+ END;
99
+ PLPGSQL
100
+ p.comment "Set doubled value to foo"
101
+ end
102
+ RUBY
103
+ end
104
+ let(:new_snippet) do
105
+ <<~RUBY
106
+ create_procedure "set_foo(a integer)" do |p|
107
+ p.language "plpgsql"
108
+ p.body <<~Q.chomp
109
+ DECLARE
110
+ b integer := 2 * a;
111
+ BEGIN
112
+ set custom.foo = b;
113
+ END;
114
+ Q
115
+ p.comment "Set doubled value to foo"
116
+ end
117
+ RUBY
118
+ end
119
+
120
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
121
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
122
+ it { is_expected.to be_irreversible.because_of(/body|comment/i) }
123
+ end
124
+
125
+ context "with no changes" do
126
+ let(:migration) do
127
+ <<~RUBY
128
+ change_procedure "set_foo(a integer)"
129
+ RUBY
130
+ end
131
+
132
+ it { is_expected.to fail_validation.because(/changes can't be blank/i) }
133
+ end
134
+
135
+ context "when the procedure is absent" do
136
+ let(:migration) do
137
+ <<~RUBY
138
+ change_procedure "unknown(integer)" do |p|
139
+ p.comment <<~COMMENT
140
+ New comment
141
+ COMMENT
142
+ end
143
+ RUBY
144
+ end
145
+
146
+ context "without the `if_exists` option" do
147
+ its(:execution) { is_expected.to raise_error(StandardError) }
148
+ end
149
+
150
+ context "with the `if_exists: true` option" do
151
+ let(:migration) do
152
+ <<~RUBY
153
+ change_procedure "unknown(integer)", if_exists: true do |p|
154
+ p.comment <<~COMMENT
155
+ New comment
156
+ COMMENT
157
+ end
158
+ RUBY
159
+ end
160
+
161
+ its(:execution) { is_expected.not_to change_schema }
162
+ it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
163
+ end
164
+ end
165
+
166
+ context "without name" do
167
+ let(:migration) do
168
+ <<~RUBY
169
+ change_procedure security: :invoker
170
+ RUBY
171
+ end
172
+
173
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
174
+ end
175
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#create_procedure", since_version: 11 do
4
+ context "with a minimal definition" do
5
+ let(:migration) do
6
+ <<~RUBY
7
+ create_procedure "set_foo(a integer)", body: "SET custom.foo = a"
8
+ RUBY
9
+ end
10
+ let(:snippet) do
11
+ <<~RUBY
12
+ create_procedure "set_foo(a integer)" do |p|
13
+ p.body "SET custom.foo = a"
14
+ end
15
+ RUBY
16
+ end
17
+ let(:query) { "CALL set_foo(42);" }
18
+
19
+ its(:execution) { is_expected.to enable_sql_request(query) }
20
+ its(:execution) { is_expected.to insert(snippet).into_schema }
21
+
22
+ its(:inversion) { is_expected.to disable_sql_request(query) }
23
+ its(:inversion) { is_expected.not_to change_schema }
24
+ end
25
+
26
+ context "with options" do
27
+ let(:migration) do
28
+ <<~RUBY
29
+ create_procedure "set_foo(a integer)" do |p|
30
+ p.language "plpgsql"
31
+ p.body "BEGIN set custom.foo = a; END;"
32
+ p.comment "Multiply 2 values"
33
+ end
34
+ RUBY
35
+ end
36
+
37
+ its(:execution) { is_expected.to insert(migration).into_schema }
38
+ its(:inversion) { is_expected.not_to change_schema }
39
+ end
40
+
41
+ context "when a procedure existed" do
42
+ before do
43
+ run_migration <<~RUBY
44
+ create_procedure "set_foo", body: "SET custom.foo = 42"
45
+ RUBY
46
+ end
47
+
48
+ context "without replace_existing: true" do
49
+ let(:migration) do
50
+ <<~RUBY
51
+ create_procedure "set_foo", body: "SET custom.foo = 666"
52
+ RUBY
53
+ end
54
+
55
+ its(:execution) { is_expected.to raise_error(StandardError) }
56
+ end
57
+
58
+ context "with replace_existing: true" do
59
+ let(:migration) do
60
+ <<~RUBY
61
+ create_procedure "set_foo",
62
+ body: "SET custom.foo = 666",
63
+ replace_existing: true
64
+ RUBY
65
+ end
66
+ let(:old_snippet) do
67
+ <<~RUBY
68
+ create_procedure "set_foo()" do |p|
69
+ p.body "SET custom.foo = 42"
70
+ end
71
+ RUBY
72
+ end
73
+ let(:new_snippet) do
74
+ <<~RUBY
75
+ create_procedure "set_foo()" do |p|
76
+ p.body "SET custom.foo = 666"
77
+ end
78
+ RUBY
79
+ end
80
+
81
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
82
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
83
+ it { is_expected.to be_irreversible.because_of(/replace_existing: true/i) }
84
+ end
85
+ end
86
+
87
+ context "when a procedure contains SQL injection" do
88
+ # Running it would inject the SQL code going after $$
89
+ let(:migration) do
90
+ <<~RUBY
91
+ create_procedure "set_foo(a integer)" do |p|
92
+ p.body <<~Q.chomp
93
+ SET custom.foo = a$$;DROP TABLE priceless;
94
+ Q
95
+ end
96
+ RUBY
97
+ end
98
+
99
+ it { is_expected.to fail_validation.because_of(/SQL injection/i) }
100
+ end
101
+
102
+ context "when a procedure has named $-quotations" do
103
+ # This code is safe because `$greeting$` doesn't closes `$$`
104
+ let(:migration) do
105
+ <<~RUBY
106
+ create_procedure "greet()" do |p|
107
+ p.body "SET custom.foo = $greeting$Hi$greeting$;"
108
+ end
109
+ RUBY
110
+ end
111
+
112
+ its(:execution) { is_expected.to insert(migration).into_schema }
113
+ end
114
+
115
+ context "without arguments" do
116
+ let(:migration) do
117
+ <<~RUBY
118
+ create_procedure "set_foo", body: "SET custom.foo = 42"
119
+ RUBY
120
+ end
121
+ let(:snippet) do
122
+ <<~RUBY
123
+ create_procedure "set_foo()" do |p|
124
+ p.body "SET custom.foo = 42"
125
+ end
126
+ RUBY
127
+ end
128
+
129
+ its(:execution) { is_expected.to insert(snippet).into_schema }
130
+ end
131
+
132
+ context "without name" do
133
+ let(:migration) do
134
+ <<~RUBY
135
+ create_procedure body: "SET custom.foo = 42;"
136
+ RUBY
137
+ end
138
+
139
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
140
+ end
141
+
142
+ context "without body" do
143
+ let(:migration) do
144
+ <<~RUBY
145
+ create_procedure "foo ()"
146
+ RUBY
147
+ end
148
+
149
+ it { is_expected.to fail_validation.because(/body can't be blank/i) }
150
+ end
151
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#drop_procedure", since_version: 11 do
4
+ context "when a procedure was present" do
5
+ before { run_migration(snippet) }
6
+
7
+ let(:snippet) do
8
+ <<~RUBY
9
+ create_procedure "set_foo(a integer)" do |p|
10
+ p.body "SET custom.foo = a"
11
+ end
12
+ RUBY
13
+ end
14
+ let(:query) { "CALL set_foo(42);" }
15
+
16
+ context "with a procedure name only" do
17
+ let(:migration) do
18
+ <<~RUBY
19
+ drop_procedure "set_foo"
20
+ RUBY
21
+ end
22
+
23
+ its(:execution) { is_expected.to disable_sql_request(query) }
24
+ its(:execution) { is_expected.to remove(snippet).from_schema }
25
+
26
+ it { is_expected.to be_irreversible.because(/body can't be blank/i) }
27
+ end
28
+
29
+ context "with a procedure signature" do
30
+ let(:migration) do
31
+ <<~RUBY
32
+ drop_procedure "set_foo(int)"
33
+ RUBY
34
+ end
35
+
36
+ its(:execution) { is_expected.to disable_sql_request(query) }
37
+ its(:execution) { is_expected.to remove(snippet).from_schema }
38
+
39
+ it { is_expected.to be_irreversible.because(/body can't be blank/i) }
40
+ end
41
+
42
+ context "with a procedure body" do
43
+ let(:migration) do
44
+ <<~RUBY
45
+ drop_procedure "set_foo(a int)", body: "SET custom.foo = a"
46
+ RUBY
47
+ end
48
+
49
+ its(:execution) { is_expected.to disable_sql_request(query) }
50
+ its(:execution) { is_expected.to remove(snippet).from_schema }
51
+
52
+ its(:inversion) { is_expected.not_to change_schema }
53
+ end
54
+
55
+ context "with additional options" do
56
+ let(:migration) do
57
+ <<~RUBY
58
+ drop_procedure "set_foo(a integer)" do |p|
59
+ p.language "plpgsql"
60
+ p.body "BEGIN SET custom.foo = a; END;"
61
+ p.comment "Multiply 2 integers"
62
+ end
63
+ RUBY
64
+ end
65
+ let(:old_snippet) do
66
+ <<~RUBY.indent(2)
67
+ create_procedure "set_foo(a integer)" do |p|
68
+ p.body "SET custom.foo = a"
69
+ end
70
+ RUBY
71
+ end
72
+ let(:new_snippet) do
73
+ <<~RUBY.indent(2)
74
+ create_procedure "set_foo(a integer)" do |p|
75
+ p.language "plpgsql"
76
+ p.body "BEGIN SET custom.foo = a; END;"
77
+ p.comment "Multiply 2 integers"
78
+ end
79
+ RUBY
80
+ end
81
+
82
+ its(:execution) { is_expected.to disable_sql_request(query) }
83
+ its(:execution) { is_expected.to remove(snippet).from_schema }
84
+
85
+ its(:inversion) { is_expected.to enable_sql_request(query) }
86
+ its(:inversion) { is_expected.to remove(old_snippet).from_schema }
87
+ its(:inversion) { is_expected.to insert(new_snippet).into_schema }
88
+ end
89
+
90
+ context "without a name" do
91
+ let(:migration) do
92
+ <<~RUBY
93
+ drop_procedure
94
+ RUBY
95
+ end
96
+
97
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
98
+ end
99
+ end
100
+
101
+ context "when several procedures existed" do
102
+ before do
103
+ run_migration <<~RUBY
104
+ create_procedure "set_foo(a text)", body: "SET custom.foo = a"
105
+ create_procedure "set_foo(a integer)", body: "SET custom.foo = a"
106
+ RUBY
107
+ end
108
+
109
+ context "with a name only" do
110
+ let(:migration) do
111
+ <<~RUBY
112
+ drop_procedure "set_foo"
113
+ RUBY
114
+ end
115
+
116
+ its(:execution) { is_expected.to raise_exception(StandardError) }
117
+ end
118
+
119
+ context "with a signature" do
120
+ let(:migration) do
121
+ <<~RUBY
122
+ drop_procedure "set_foo(text)"
123
+ RUBY
124
+ end
125
+ let(:snippet) do
126
+ <<~RUBY
127
+ create_procedure "set_foo(a text)" do |p|
128
+ p.body "SET custom.foo = a"
129
+ end
130
+ RUBY
131
+ end
132
+
133
+ its(:execution) { is_expected.to remove(snippet).from_schema }
134
+ end
135
+ end
136
+
137
+ context "when a procedure was absent" do
138
+ context "without the :if_exists option" do
139
+ let(:migration) do
140
+ <<~RUBY
141
+ drop_procedure "set_foo"
142
+ RUBY
143
+ end
144
+
145
+ its(:execution) { is_expected.to raise_error(StandardError) }
146
+ end
147
+
148
+ context "with the if_exists: true option" do
149
+ let(:migration) do
150
+ <<~RUBY
151
+ drop_procedure "set_foo", if_exists: true
152
+ RUBY
153
+ end
154
+
155
+ its(:execution) { is_expected.not_to raise_error }
156
+ it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#rename_procedure", since_version: 11 do
4
+ before do
5
+ run_migration <<~RUBY
6
+ create_schema "meta"
7
+ create_procedure "set_foo(a int)", body: "SET custom.foo = a"
8
+ RUBY
9
+ end
10
+
11
+ let(:old_query) { "CALL set_foo(42);" }
12
+ let(:old_snippet) do
13
+ <<~RUBY
14
+ create_procedure "set_foo(a integer)" do |p|
15
+ p.body "SET custom.foo = a"
16
+ end
17
+ RUBY
18
+ end
19
+
20
+ context "with new name and schema" do
21
+ let(:migration) do
22
+ <<~RUBY
23
+ rename_procedure "set_foo", to: "meta.set_foo_value"
24
+ RUBY
25
+ end
26
+ let(:new_snippet) do
27
+ <<~RUBY
28
+ create_procedure "meta.set_foo_value(a integer)" do |p|
29
+ p.body "SET custom.foo = a"
30
+ end
31
+ RUBY
32
+ end
33
+ let(:new_query) { "CALL meta.set_foo_value(42);" }
34
+
35
+ its(:execution) { is_expected.to enable_sql_request(new_query) }
36
+ its(:execution) { is_expected.to disable_sql_request(old_query) }
37
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
38
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
39
+
40
+ its(:inversion) { is_expected.to disable_sql_request(new_query) }
41
+ its(:inversion) { is_expected.to enable_sql_request(old_query) }
42
+ its(:inversion) { is_expected.not_to change_schema }
43
+ end
44
+
45
+ context "with the same name and schema" do
46
+ let(:migration) do
47
+ <<~RUBY
48
+ rename_procedure "set_foo", to: "public.set_foo"
49
+ RUBY
50
+ end
51
+
52
+ it { is_expected.to fail_validation.because(/new name must be different/i) }
53
+ end
54
+
55
+ context "when several procedures exist" do
56
+ before do
57
+ run_migration <<~RUBY
58
+ create_procedure "set_foo(a text)", body: "SET custom.foo = a"
59
+ RUBY
60
+ end
61
+
62
+ context "without a signature" do
63
+ let(:migration) do
64
+ <<~RUBY
65
+ rename_procedure "set_foo", to: "set_foo_value"
66
+ RUBY
67
+ end
68
+
69
+ its(:execution) { is_expected.to raise_error(StandardError) }
70
+ end
71
+
72
+ context "with a signature" do
73
+ let(:migration) do
74
+ <<~RUBY
75
+ rename_procedure "set_foo(int)", to: "meta.set_foo_value"
76
+ RUBY
77
+ end
78
+ let(:new_snippet) do
79
+ <<~RUBY
80
+ create_procedure "meta.set_foo_value(a integer)" do |p|
81
+ p.body "SET custom.foo = a"
82
+ end
83
+ RUBY
84
+ end
85
+ let(:new_query) { "CALL meta.set_foo_value(42);" }
86
+
87
+ its(:execution) { is_expected.to enable_sql_request(new_query) }
88
+ its(:execution) { is_expected.to disable_sql_request(old_query) }
89
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
90
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
91
+
92
+ its(:inversion) { is_expected.to disable_sql_request(new_query) }
93
+ its(:inversion) { is_expected.to enable_sql_request(old_query) }
94
+ its(:inversion) { is_expected.not_to change_schema }
95
+ end
96
+ end
97
+
98
+ context "without a name" do
99
+ let(:migration) do
100
+ <<~RUBY
101
+ rename_procedure to: "meta.set_foo"
102
+ RUBY
103
+ end
104
+
105
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
106
+ end
107
+ end