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,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#add_index" do
4
+ context "when index added inside a table definition" do
5
+ let(:migration) do
6
+ <<~RUBY
7
+ create_table :users do |t|
8
+ t.string "name", index: true
9
+ end
10
+ RUBY
11
+ end
12
+ let(:snippet) do
13
+ <<~RUBY
14
+ create_table "users", force: :cascade do |t|
15
+ t.string "name"
16
+ end
17
+
18
+ add_index "users", ["name"], name: "index_users_on_name"
19
+ RUBY
20
+ end
21
+
22
+ its(:execution) { is_expected.to insert(snippet).into_schema }
23
+ end
24
+
25
+ context "when primary key added inside a table definition" do
26
+ let(:migration) do
27
+ <<~RUBY
28
+ create_table :users, primary_key: :name do |t|
29
+ end
30
+ RUBY
31
+ end
32
+ let(:snippet) do
33
+ <<~RUBY
34
+ create_table "users", primary_key: "name", force: :cascade do |t|
35
+ end
36
+ RUBY
37
+ end
38
+
39
+ its(:execution) { is_expected.to insert(snippet).into_schema }
40
+ end
41
+
42
+ context "when index is added outside of a table definition" do
43
+ let(:migration) do
44
+ <<~RUBY
45
+ create_table :users do |t|
46
+ t.string "name"
47
+ end
48
+
49
+ add_index :users, :name
50
+ RUBY
51
+ end
52
+ let(:snippet) do
53
+ <<~RUBY
54
+ create_table "users", force: :cascade do |t|
55
+ t.string "name"
56
+ end
57
+
58
+ add_index "users", ["name"], name: "index_users_on_name"
59
+ RUBY
60
+ end
61
+
62
+ its(:execution) { is_expected.to insert(snippet).into_schema }
63
+ end
64
+
65
+ context "with several indexes" do
66
+ let(:migration) do
67
+ <<~RUBY
68
+ create_table :users do |t|
69
+ t.string :role, index: true
70
+ t.string :name, index: true
71
+ end
72
+
73
+ create_table :roles do |t|
74
+ t.string :name, index: true
75
+ t.string :access, array: true, index: true
76
+ end
77
+ RUBY
78
+ end
79
+ let(:snippet) do
80
+ # Ordered by table and name
81
+ <<~RUBY
82
+ add_index "roles", ["access"], name: "index_roles_on_access"
83
+
84
+ add_index "roles", ["name"], name: "index_roles_on_name"
85
+
86
+ add_index "users", ["name"], name: "index_users_on_name"
87
+
88
+ add_index "users", ["role"], name: "index_users_on_role"
89
+ RUBY
90
+ end
91
+
92
+ its(:execution) { is_expected.to insert(snippet).into_schema }
93
+ end
94
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#change_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
+
13
+ before do
14
+ run_migration <<~RUBY
15
+ create_materialized_view "admins" do |v|
16
+ v.sql_definition <<~Q.chomp
17
+ SELECT users.id, users.name
18
+ FROM users
19
+ WHERE users.admin
20
+ Q
21
+ v.comment "Initial comment"
22
+ end
23
+
24
+ add_index :admins, :name, name: "view_index"
25
+ RUBY
26
+ end
27
+
28
+ context "with renaming a column" do
29
+ let(:migration) do
30
+ <<~RUBY
31
+ change_materialized_view "admins" do |v|
32
+ v.rename_column "name", to: "full_name"
33
+ end
34
+ RUBY
35
+ end
36
+ let(:snippet) do
37
+ <<~RUBY
38
+ create_materialized_view "admins" do |v|
39
+ v.sql_definition <<~Q.chomp
40
+ SELECT users.id, users.name AS full_name
41
+ FROM users
42
+ WHERE users.admin
43
+ Q
44
+ v.comment "Initial comment"
45
+ end
46
+ RUBY
47
+ end
48
+ let(:query) { "SELECT full_name FROM admins;" }
49
+
50
+ its(:execution) { is_expected.to enable_sql_request(query) }
51
+ its(:execution) { is_expected.to insert(snippet).into_schema }
52
+ its(:inversion) { is_expected.not_to change_schema }
53
+ end
54
+
55
+ context "with changing column storage" do
56
+ let(:migration) do
57
+ <<~RUBY
58
+ change_materialized_view "admins" do |v|
59
+ v.column "name", storage: "external", from_storage: "extended"
60
+ end
61
+ RUBY
62
+ end
63
+ let(:snippet) do
64
+ <<~RUBY
65
+ create_materialized_view "admins" do |v|
66
+ v.sql_definition <<~Q.chomp
67
+ SELECT users.id, users.name
68
+ FROM users
69
+ WHERE users.admin
70
+ Q
71
+ v.column "name", storage: :external
72
+ v.comment "Initial comment"
73
+ end
74
+ RUBY
75
+ end
76
+
77
+ its(:execution) { is_expected.to insert(snippet).into_schema }
78
+ its(:inversion) { is_expected.not_to change_schema }
79
+ end
80
+
81
+ context "with clustering a view by index" do
82
+ let(:migration) do
83
+ <<~RUBY
84
+ change_materialized_view "admins", cluster_on: "view_index"
85
+ RUBY
86
+ end
87
+
88
+ its(:execution) { is_expected.not_to raise_error }
89
+ its(:inversion) { is_expected.not_to change_schema }
90
+ end
91
+
92
+ context "with changing column's statistics" do
93
+ let(:migration) do
94
+ <<~RUBY
95
+ change_materialized_view "admins" do |v|
96
+ v.column "name", statistics: 10, n_distinct: 1
97
+ end
98
+ RUBY
99
+ end
100
+
101
+ its(:execution) { is_expected.not_to change_schema }
102
+ its(:inversion) { is_expected.not_to change_schema }
103
+ end
104
+
105
+ context "with a new comment" do
106
+ let(:migration) do
107
+ <<~RUBY
108
+ change_materialized_view "admins" do |v|
109
+ v.comment "Admin users only", from: "Initial comment"
110
+ end
111
+ RUBY
112
+ end
113
+ let(:snippet) do
114
+ <<~RUBY
115
+ create_materialized_view "admins" do |v|
116
+ v.sql_definition <<~Q.chomp
117
+ SELECT users.id, users.name
118
+ FROM users
119
+ WHERE users.admin
120
+ Q
121
+ v.comment "Admin users only"
122
+ end
123
+ RUBY
124
+ end
125
+
126
+ its(:execution) { is_expected.to insert(snippet).into_schema }
127
+ its(:inversion) { is_expected.not_to change_schema }
128
+ end
129
+
130
+ context "with an empty comment" do
131
+ let(:migration) do
132
+ <<~RUBY
133
+ change_materialized_view "admins" do |v|
134
+ v.comment "", from: "Initial comment"
135
+ end
136
+ RUBY
137
+ end
138
+ let(:snippet) do
139
+ <<~RUBY
140
+ create_materialized_view "admins" do |v|
141
+ v.sql_definition <<~Q.chomp
142
+ SELECT users.id, users.name
143
+ FROM users
144
+ WHERE users.admin
145
+ Q
146
+ end
147
+ RUBY
148
+ end
149
+
150
+ its(:execution) { is_expected.to insert(snippet).into_schema }
151
+ its(:inversion) { is_expected.not_to change_schema }
152
+ end
153
+
154
+ context "when a view was absent" do
155
+ context "without the `if_exists` option" do
156
+ let(:migration) do
157
+ <<~RUBY
158
+ change_materialized_view "weird" do |v|
159
+ v.column "name", storage: :extended
160
+ end
161
+ RUBY
162
+ end
163
+
164
+ its(:execution) { is_expected.to raise_error(StandardError) }
165
+ end
166
+
167
+ context "with the `if_exists: true` option" do
168
+ let(:migration) do
169
+ <<~RUBY
170
+ change_materialized_view "weird", if_exists: true do |v|
171
+ v.column "name", storage: :extended
172
+ end
173
+ RUBY
174
+ end
175
+
176
+ its(:execution) { is_expected.not_to change_schema }
177
+ it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
178
+ end
179
+ end
180
+
181
+ context "with no changes" do
182
+ let(:migration) do
183
+ <<~RUBY
184
+ change_materialized_view "admins"
185
+ RUBY
186
+ end
187
+
188
+ it { is_expected.to fail_validation.because(/changes can't be blank/i) }
189
+ end
190
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#create_materialized_view" do
4
+ before_all do
5
+ run_migration <<~RUBY
6
+ create_table "users", force: :cascade do |t|
7
+ t.string "name"
8
+ t.boolean "admin"
9
+ end
10
+ RUBY
11
+ end
12
+
13
+ let(:query) { "SELECT * FROM admin_users;" }
14
+
15
+ context "when a materialized view was absent" do
16
+ let(:migration) do
17
+ <<~RUBY
18
+ create_materialized_view "admin_users" do |v|
19
+ v.sql_definition <<~Q.chomp
20
+ SELECT users.id, users.name
21
+ FROM users
22
+ WHERE users.admin
23
+ Q
24
+ v.column "name", storage: :external
25
+ v.comment "Admin users only"
26
+ end
27
+ RUBY
28
+ end
29
+
30
+ its(:execution) { is_expected.to enable_sql_request(query) }
31
+ its(:execution) { is_expected.to insert(migration).into_schema }
32
+
33
+ its(:inversion) { is_expected.to disable_sql_request(query) }
34
+ its(:inversion) { is_expected.not_to change_schema }
35
+ end
36
+
37
+ context "with the `with_data: false` option" do
38
+ let(:migration) do
39
+ <<~RUBY
40
+ create_materialized_view "admin_users" do |v|
41
+ v.sql_definition <<~Q.chomp
42
+ SELECT users.id, users.name
43
+ FROM users
44
+ WHERE users.admin
45
+ Q
46
+ v.with_data false
47
+ end
48
+ RUBY
49
+ end
50
+
51
+ its(:execution) { is_expected.to disable_sql_request(query) }
52
+ its(:execution) { is_expected.to insert(migration).into_schema }
53
+ its(:inversion) { is_expected.not_to change_schema }
54
+ end
55
+
56
+ context "when a materialized view was present" do
57
+ before do
58
+ run_migration <<~RUBY
59
+ create_materialized_view "admin_users" do |v|
60
+ v.sql_definition "SELECT * FROM users"
61
+ end
62
+ RUBY
63
+ end
64
+
65
+ context "without the `if_not_exists` option" do
66
+ let(:migration) do
67
+ <<~RUBY
68
+ create_materialized_view "admin_users" do |v|
69
+ v.sql_definition "SELECT * FROM users WHERE admin"
70
+ end
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_materialized_view "admin_users", if_not_exists: true do |v|
81
+ v.sql_definition "SELECT * FROM users WHERE admin"
82
+ end
83
+ RUBY
84
+ end
85
+
86
+ its(:execution) { is_expected.not_to change_schema }
87
+ it { is_expected.to be_irreversible.because_of(/if_not_exists: true/i) }
88
+ end
89
+ end
90
+
91
+ context "without sql definition" do
92
+ context "with an existing version" do
93
+ let(:migration) do
94
+ <<~RUBY
95
+ create_materialized_view "admin_users", version: 1
96
+ RUBY
97
+ end
98
+ let(:snippet) do
99
+ <<~RUBY
100
+ create_materialized_view "admin_users" do |v|
101
+ v.sql_definition <<~Q.chomp
102
+ SELECT users.id, users.name
103
+ FROM users
104
+ WHERE users.admin
105
+ Q
106
+ end
107
+ RUBY
108
+ end
109
+
110
+ its(:execution) { is_expected.to insert(snippet).into_schema }
111
+ its(:inversion) { is_expected.not_to change_schema }
112
+ end
113
+
114
+ context "with an absent version" do
115
+ let(:migration) do
116
+ <<~RUBY
117
+ create_materialized_view "admin_users", version: 2
118
+ RUBY
119
+ end
120
+
121
+ its(:execution) { is_expected.to raise_error(StandardError) }
122
+ end
123
+
124
+ context "without version" do
125
+ let(:migration) do
126
+ <<~RUBY
127
+ create_materialized_view "admin_users"
128
+ RUBY
129
+ end
130
+
131
+ it { is_expected.to fail_validation.because(/sql definition can't be blank/i) }
132
+ end
133
+ end
134
+
135
+ context "without a name" do
136
+ let(:migration) do
137
+ <<~RUBY
138
+ create_materialized_view sql_definition: "SELECT * FROM users"
139
+ RUBY
140
+ end
141
+
142
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
143
+ end
144
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#drop_materialized_view" do
4
+ before_all do
5
+ run_migration <<~RUBY
6
+ create_table "users", force: :cascade do |t|
7
+ t.string "name"
8
+ t.boolean "admin"
9
+ end
10
+
11
+ create_table "weird"
12
+ RUBY
13
+ end
14
+ before { run_migration(snippet) }
15
+
16
+ let(:snippet) do
17
+ <<~RUBY
18
+ create_materialized_view "admin_users" do |v|
19
+ v.sql_definition <<~Q.chomp
20
+ SELECT users.id, users.name
21
+ FROM users
22
+ WHERE users.admin
23
+ Q
24
+ v.column "name", storage: :external
25
+ v.comment "Admin users only"
26
+ end
27
+ RUBY
28
+ end
29
+ let(:query) { "SELECT * FROM admin_users;" }
30
+
31
+ context "with a full definition" do
32
+ let(:migration) do
33
+ <<~RUBY
34
+ drop_materialized_view "admin_users" do |v|
35
+ v.sql_definition <<~Q.chomp
36
+ SELECT users.id, users.name
37
+ FROM users
38
+ WHERE users.admin
39
+ Q
40
+ v.column "name", storage: :external
41
+ v.comment "Admin users only"
42
+ end
43
+ RUBY
44
+ end
45
+
46
+ its(:execution) { is_expected.to disable_sql_request(query) }
47
+ its(:execution) { is_expected.to remove(snippet).from_schema }
48
+
49
+ its(:inversion) { is_expected.to enable_sql_request(query) }
50
+ its(:inversion) { is_expected.not_to change_schema }
51
+ end
52
+
53
+ context "with a version-based definition" do
54
+ let(:migration) do
55
+ <<~RUBY
56
+ drop_materialized_view "admin_users", revert_to_version: 1 do |v|
57
+ v.column "name", storage: :external
58
+ v.comment "Admin users only"
59
+ end
60
+ RUBY
61
+ end
62
+
63
+ its(:execution) { is_expected.to disable_sql_request(query) }
64
+ its(:execution) { is_expected.to remove(snippet).from_schema }
65
+
66
+ its(:inversion) { is_expected.to enable_sql_request(query) }
67
+ its(:inversion) { is_expected.not_to change_schema }
68
+ end
69
+
70
+ context "with a name only" do
71
+ let(:migration) do
72
+ <<~RUBY
73
+ drop_materialized_view "admin_users"
74
+ RUBY
75
+ end
76
+
77
+ its(:execution) { is_expected.to disable_sql_request(query) }
78
+ its(:execution) { is_expected.to remove(snippet).from_schema }
79
+ it { is_expected.to be_irreversible.because(/sql_definition can't be blank/i) }
80
+ end
81
+
82
+ context "when a view was absent" do
83
+ context "without the `if_exists` option" do
84
+ let(:migration) do
85
+ <<~RUBY
86
+ drop_materialized_view "unknown"
87
+ RUBY
88
+ end
89
+
90
+ its(:execution) { is_expected.to raise_error(StandardError) }
91
+ end
92
+
93
+ context "with the `replace_existing: true` option" do
94
+ let(:migration) do
95
+ <<~RUBY
96
+ drop_materialized_view "unknown", if_exists: true
97
+ RUBY
98
+ end
99
+
100
+ its(:execution) { is_expected.not_to change_schema }
101
+ it { is_expected.to be_irreversible.because_of(/if_exists: true/i) }
102
+ end
103
+ end
104
+
105
+ context "when a view was used" do
106
+ before do
107
+ run_migration <<~RUBY
108
+ create_function "do_nothing() admin_users",
109
+ body: "SELECT * FROM admin_users;"
110
+ RUBY
111
+ end
112
+
113
+ context "without the `:force` option" do
114
+ let(:migration) do
115
+ <<~RUBY
116
+ drop_materialized_view "admin_users"
117
+ RUBY
118
+ end
119
+
120
+ its(:execution) { is_expected.to raise_error(StandardError) }
121
+ end
122
+
123
+ context "with the `force: :cascade` option" do
124
+ let(:migration) do
125
+ <<~RUBY
126
+ drop_materialized_view "admin_users", force: :cascade
127
+ RUBY
128
+ end
129
+
130
+ its(:execution) { is_expected.to disable_sql_request(query) }
131
+ its(:execution) { is_expected.to remove(snippet).from_schema }
132
+ it { is_expected.to be_irreversible.because_of(/force: :cascade/i) }
133
+ end
134
+ end
135
+
136
+ context "without a name" do
137
+ let(:migration) do
138
+ <<~RUBY
139
+ drop_materialized_view sql_definition: "SELECT * FROM users"
140
+ RUBY
141
+ end
142
+
143
+ it { is_expected.to fail_validation.because(/name can't be blank/i) }
144
+ end
145
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe ActiveRecord::Migration, "#refresh_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(:query) { "SELECT * FROM admins;" }
15
+ let(:old_snippet) do
16
+ <<~RUBY
17
+ create_materialized_view "admins" do |v|
18
+ v.sql_definition <<~Q.chomp
19
+ SELECT users.id, users.name
20
+ FROM users
21
+ WHERE users.admin
22
+ Q
23
+ v.with_data false
24
+ end
25
+ RUBY
26
+ end
27
+ let(:new_snippet) do
28
+ <<~RUBY
29
+ create_materialized_view "admins" do |v|
30
+ v.sql_definition <<~Q.chomp
31
+ SELECT users.id, users.name
32
+ FROM users
33
+ WHERE users.admin
34
+ Q
35
+ end
36
+ RUBY
37
+ end
38
+
39
+ context "without options" do
40
+ let(:migration) do
41
+ <<~RUBY
42
+ refresh_materialized_view :admins
43
+ RUBY
44
+ end
45
+
46
+ its(:execution) { is_expected.to enable_sql_request(query) }
47
+ its(:execution) { is_expected.to insert(new_snippet).into_schema }
48
+ its(:execution) { is_expected.to remove(old_snippet).from_schema }
49
+
50
+ # Inversion (up, then down) keeps the schema in the migrated (valid) state
51
+ its(:execution) { is_expected.to enable_sql_request(query) }
52
+ its(:inversion) { is_expected.to insert(new_snippet).into_schema }
53
+ its(:inversion) { is_expected.to remove(old_snippet).from_schema }
54
+ end
55
+
56
+ context "with the `with_data: false` option" do
57
+ let(:migration) do
58
+ <<~RUBY
59
+ refresh_materialized_view :admins, with_data: false
60
+ RUBY
61
+ end
62
+
63
+ its(:execution) { is_expected.to disable_sql_request(query) }
64
+ its(:inversion) { is_expected.not_to change_schema }
65
+ end
66
+
67
+ context "with both `with_data` and `algorithm` option" do
68
+ let(:migration) do
69
+ <<~RUBY
70
+ refresh_materialized_view :admins do |v|
71
+ v.with_data false
72
+ v.algorithm :concurrently
73
+ end
74
+ RUBY
75
+ end
76
+
77
+ it { is_expected.to fail_validation.because(/algorithm must be blank/i) }
78
+ end
79
+ end