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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +87 -0
- data/.gitignore +9 -0
- data/.rspec +4 -0
- data/.rubocop.yml +92 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +31 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +141 -0
- data/Rakefile +16 -0
- data/bin/console +8 -0
- data/bin/rake +19 -0
- data/bin/rspec +19 -0
- data/bin/setup +8 -0
- data/bin/yard +19 -0
- data/lib/pg_trunk/core/adapters/postgres.rb +80 -0
- data/lib/pg_trunk/core/dependencies_resolver.rb +101 -0
- data/lib/pg_trunk/core/generators.rb +140 -0
- data/lib/pg_trunk/core/operation/attributes.rb +78 -0
- data/lib/pg_trunk/core/operation/callbacks.rb +40 -0
- data/lib/pg_trunk/core/operation/generators.rb +51 -0
- data/lib/pg_trunk/core/operation/inversion.rb +70 -0
- data/lib/pg_trunk/core/operation/registration.rb +55 -0
- data/lib/pg_trunk/core/operation/ruby_builder.rb +112 -0
- data/lib/pg_trunk/core/operation/ruby_helpers.rb +99 -0
- data/lib/pg_trunk/core/operation/sql_helpers.rb +44 -0
- data/lib/pg_trunk/core/operation/validations.rb +21 -0
- data/lib/pg_trunk/core/operation.rb +78 -0
- data/lib/pg_trunk/core/qualified_name.rb +165 -0
- data/lib/pg_trunk/core/railtie/command_recorder.rb +30 -0
- data/lib/pg_trunk/core/railtie/custom_types.rb +37 -0
- data/lib/pg_trunk/core/railtie/migration.rb +50 -0
- data/lib/pg_trunk/core/railtie/migrator.rb +22 -0
- data/lib/pg_trunk/core/railtie/schema_dumper.rb +75 -0
- data/lib/pg_trunk/core/railtie/schema_migration.rb +22 -0
- data/lib/pg_trunk/core/railtie/statements.rb +21 -0
- data/lib/pg_trunk/core/railtie.rb +35 -0
- data/lib/pg_trunk/core/registry.rb +159 -0
- data/lib/pg_trunk/core/serializers/array_of_hashes_serializer.rb +28 -0
- data/lib/pg_trunk/core/serializers/array_of_strings_serializer.rb +29 -0
- data/lib/pg_trunk/core/serializers/array_of_symbols_serializer.rb +28 -0
- data/lib/pg_trunk/core/serializers/array_serializer.rb +22 -0
- data/lib/pg_trunk/core/serializers/lowercase_string_serializer.rb +21 -0
- data/lib/pg_trunk/core/serializers/multiline_text_serializer.rb +21 -0
- data/lib/pg_trunk/core/serializers/qualified_name_serializer.rb +27 -0
- data/lib/pg_trunk/core/serializers/symbol_serializer.rb +22 -0
- data/lib/pg_trunk/core/serializers.rb +16 -0
- data/lib/pg_trunk/core/validators/all_items_valid_validator.rb +15 -0
- data/lib/pg_trunk/core/validators/difference_validator.rb +19 -0
- data/lib/pg_trunk/core/validators.rb +10 -0
- data/lib/pg_trunk/core.rb +21 -0
- data/lib/pg_trunk/generators.rb +7 -0
- data/lib/pg_trunk/operations/check_constraints/add_check_constraint.rb +109 -0
- data/lib/pg_trunk/operations/check_constraints/base.rb +69 -0
- data/lib/pg_trunk/operations/check_constraints/drop_check_constraint.rb +60 -0
- data/lib/pg_trunk/operations/check_constraints/rename_check_constraint.rb +54 -0
- data/lib/pg_trunk/operations/check_constraints/validate_check_constraint.rb +39 -0
- data/lib/pg_trunk/operations/check_constraints.rb +14 -0
- data/lib/pg_trunk/operations/composite_types/base.rb +61 -0
- data/lib/pg_trunk/operations/composite_types/change_composite_type.rb +136 -0
- data/lib/pg_trunk/operations/composite_types/column.rb +118 -0
- data/lib/pg_trunk/operations/composite_types/create_composite_type.rb +99 -0
- data/lib/pg_trunk/operations/composite_types/drop_composite_type.rb +67 -0
- data/lib/pg_trunk/operations/composite_types/rename_composite_type.rb +44 -0
- data/lib/pg_trunk/operations/composite_types.rb +15 -0
- data/lib/pg_trunk/operations/domains/base.rb +46 -0
- data/lib/pg_trunk/operations/domains/change_domain.rb +140 -0
- data/lib/pg_trunk/operations/domains/constraint.rb +93 -0
- data/lib/pg_trunk/operations/domains/create_domain.rb +124 -0
- data/lib/pg_trunk/operations/domains/drop_domain.rb +65 -0
- data/lib/pg_trunk/operations/domains/rename_domain.rb +44 -0
- data/lib/pg_trunk/operations/domains.rb +15 -0
- data/lib/pg_trunk/operations/enums/base.rb +47 -0
- data/lib/pg_trunk/operations/enums/change.rb +55 -0
- data/lib/pg_trunk/operations/enums/change_enum.rb +119 -0
- data/lib/pg_trunk/operations/enums/create_enum.rb +83 -0
- data/lib/pg_trunk/operations/enums/drop_enum.rb +63 -0
- data/lib/pg_trunk/operations/enums/rename_enum.rb +44 -0
- data/lib/pg_trunk/operations/enums.rb +15 -0
- data/lib/pg_trunk/operations/foreign_keys/add_foreign_key.rb +174 -0
- data/lib/pg_trunk/operations/foreign_keys/base.rb +155 -0
- data/lib/pg_trunk/operations/foreign_keys/drop_foreign_key.rb +76 -0
- data/lib/pg_trunk/operations/foreign_keys/rename_foreign_key.rb +63 -0
- data/lib/pg_trunk/operations/foreign_keys.rb +16 -0
- data/lib/pg_trunk/operations/functions/base.rb +54 -0
- data/lib/pg_trunk/operations/functions/change_function.rb +108 -0
- data/lib/pg_trunk/operations/functions/create_function.rb +198 -0
- data/lib/pg_trunk/operations/functions/drop_function.rb +88 -0
- data/lib/pg_trunk/operations/functions/rename_function.rb +57 -0
- data/lib/pg_trunk/operations/functions.rb +14 -0
- data/lib/pg_trunk/operations/indexes/add_index.rb +68 -0
- data/lib/pg_trunk/operations/indexes.rb +10 -0
- data/lib/pg_trunk/operations/materialized_views/base.rb +79 -0
- data/lib/pg_trunk/operations/materialized_views/change_materialized_view.rb +139 -0
- data/lib/pg_trunk/operations/materialized_views/column.rb +94 -0
- data/lib/pg_trunk/operations/materialized_views/create_materialized_view.rb +170 -0
- data/lib/pg_trunk/operations/materialized_views/drop_materialized_view.rb +70 -0
- data/lib/pg_trunk/operations/materialized_views/refresh_materialized_view.rb +48 -0
- data/lib/pg_trunk/operations/materialized_views/rename_materialized_view.rb +61 -0
- data/lib/pg_trunk/operations/materialized_views.rb +17 -0
- data/lib/pg_trunk/operations/procedures/base.rb +42 -0
- data/lib/pg_trunk/operations/procedures/change_procedure.rb +107 -0
- data/lib/pg_trunk/operations/procedures/create_procedure.rb +146 -0
- data/lib/pg_trunk/operations/procedures/drop_procedure.rb +66 -0
- data/lib/pg_trunk/operations/procedures/rename_procedure.rb +57 -0
- data/lib/pg_trunk/operations/procedures.rb +14 -0
- data/lib/pg_trunk/operations/statistics/base.rb +94 -0
- data/lib/pg_trunk/operations/statistics/create_statistics.rb +181 -0
- data/lib/pg_trunk/operations/statistics/drop_statistics.rb +75 -0
- data/lib/pg_trunk/operations/statistics/rename_statistics.rb +48 -0
- data/lib/pg_trunk/operations/statistics.rb +13 -0
- data/lib/pg_trunk/operations/tables/create_table.rb +75 -0
- data/lib/pg_trunk/operations/tables.rb +10 -0
- data/lib/pg_trunk/operations/triggers/base.rb +119 -0
- data/lib/pg_trunk/operations/triggers/change_trigger.rb +82 -0
- data/lib/pg_trunk/operations/triggers/create_trigger.rb +208 -0
- data/lib/pg_trunk/operations/triggers/drop_trigger.rb +66 -0
- data/lib/pg_trunk/operations/triggers/rename_trigger.rb +71 -0
- data/lib/pg_trunk/operations/triggers.rb +14 -0
- data/lib/pg_trunk/operations/views/base.rb +38 -0
- data/lib/pg_trunk/operations/views/change_view.rb +90 -0
- data/lib/pg_trunk/operations/views/create_view.rb +115 -0
- data/lib/pg_trunk/operations/views/drop_view.rb +69 -0
- data/lib/pg_trunk/operations/views/rename_view.rb +58 -0
- data/lib/pg_trunk/operations/views.rb +14 -0
- data/lib/pg_trunk/operations.rb +23 -0
- data/lib/pg_trunk/version.rb +6 -0
- data/lib/pg_trunk.rb +27 -0
- data/pg_trunk.gemspec +34 -0
- data/spec/dummy/.gitignore +16 -0
- data/spec/dummy/Rakefile +15 -0
- data/spec/dummy/bin/bundle +6 -0
- data/spec/dummy/bin/rails +6 -0
- data/spec/dummy/bin/rake +6 -0
- data/spec/dummy/config/application.rb +18 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/database.yml +14 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/db/materialized_views/admin_users_v01.sql +1 -0
- data/spec/dummy/db/migrate/.keep +0 -0
- data/spec/dummy/db/schema.rb +18 -0
- data/spec/dummy/db/views/admin_users_v01.sql +1 -0
- data/spec/dummy/db/views/admin_users_v02.sql +1 -0
- data/spec/operations/check_constraints/add_check_constraint_spec.rb +85 -0
- data/spec/operations/check_constraints/drop_check_constraint_spec.rb +111 -0
- data/spec/operations/check_constraints/rename_check_constraint_spec.rb +90 -0
- data/spec/operations/composite_types/change_composite_type_spec.rb +257 -0
- data/spec/operations/composite_types/create_composite_type_spec.rb +55 -0
- data/spec/operations/composite_types/drop_composite_type_spec.rb +109 -0
- data/spec/operations/composite_types/rename_composite_type_spec.rb +74 -0
- data/spec/operations/dependency_resolver_spec.rb +177 -0
- data/spec/operations/domains/change_domain_spec.rb +287 -0
- data/spec/operations/domains/create_domain_spec.rb +69 -0
- data/spec/operations/domains/drop_domain_spec.rb +119 -0
- data/spec/operations/domains/rename_domain_spec.rb +70 -0
- data/spec/operations/enums/change_enum_spec.rb +157 -0
- data/spec/operations/enums/create_enum_spec.rb +40 -0
- data/spec/operations/enums/drop_enum_spec.rb +120 -0
- data/spec/operations/enums/rename_enum_spec.rb +72 -0
- data/spec/operations/foreign_keys/add_foreign_key_spec.rb +208 -0
- data/spec/operations/foreign_keys/drop_foreign_key_spec.rb +167 -0
- data/spec/operations/foreign_keys/rename_foreign_key_spec.rb +101 -0
- data/spec/operations/functions/change_function_spec.rb +166 -0
- data/spec/operations/functions/create_function_spec.rb +192 -0
- data/spec/operations/functions/drop_function_spec.rb +182 -0
- data/spec/operations/functions/rename_function_spec.rb +101 -0
- data/spec/operations/indexes/add_index_spec.rb +94 -0
- data/spec/operations/materialized_views/change_materialized_view_spec.rb +190 -0
- data/spec/operations/materialized_views/create_materialized_view_spec.rb +144 -0
- data/spec/operations/materialized_views/drop_materialized_view_spec.rb +145 -0
- data/spec/operations/materialized_views/refresh_materialized_view_spec.rb +79 -0
- data/spec/operations/materialized_views/rename_materialized_view_spec.rb +88 -0
- data/spec/operations/procedures/change_procedure_spec.rb +175 -0
- data/spec/operations/procedures/create_procedure_spec.rb +151 -0
- data/spec/operations/procedures/drop_procedure_spec.rb +159 -0
- data/spec/operations/procedures/rename_procedure_spec.rb +107 -0
- data/spec/operations/statistics/create_statistics_spec.rb +230 -0
- data/spec/operations/statistics/drop_statistics_spec.rb +106 -0
- data/spec/operations/statistics/rename_statistics_spec.rb +129 -0
- data/spec/operations/tables/create_table_spec.rb +53 -0
- data/spec/operations/tables/rename_table_spec.rb +37 -0
- data/spec/operations/triggers/change_trigger_spec.rb +195 -0
- data/spec/operations/triggers/create_trigger_spec.rb +104 -0
- data/spec/operations/triggers/drop_trigger_spec.rb +124 -0
- data/spec/operations/triggers/rename_trigger_spec.rb +160 -0
- data/spec/operations/views/change_view_spec.rb +144 -0
- data/spec/operations/views/create_view_spec.rb +134 -0
- data/spec/operations/views/drop_view_spec.rb +146 -0
- data/spec/operations/views/rename_view_spec.rb +85 -0
- data/spec/pg_trunk/dependencies_resolver_spec.rb +43 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/migrations_helper.rb +376 -0
- metadata +348 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#create_trigger(table, name = nil, **options, &block)
|
|
4
|
+
# Create a trigger for a table
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] table (nil) The qualified name of the table
|
|
7
|
+
# @param [#to_s] name (nil) The name of the trigger
|
|
8
|
+
# @option [Boolean] :replace_existing (false) If the trigger should overwrite an existing one
|
|
9
|
+
# @option [#to_s] :function (nil) The qualified name of the function to be called
|
|
10
|
+
# @option [Symbol] :type (nil) When the trigger should be run
|
|
11
|
+
# Supported values: :before, :after, :instead_of
|
|
12
|
+
# @option [Array<Symbol>] :events List of events running the trigger
|
|
13
|
+
# Supported values in the array: :insert, :update, :delete, :truncate
|
|
14
|
+
# @option [Boolean] :constraint (false) If the trigger is a constraint
|
|
15
|
+
# @option [Symbol] :initially (:immediate) If the constraint check should be deferred
|
|
16
|
+
# Supported values: :immediate (default), :deferred
|
|
17
|
+
# @option [#to_s] :when (nil) The SQL snippet definiing a condition for the trigger
|
|
18
|
+
# @option [Symbol] :for_each (:statement) Define if a trigger should be run for every row
|
|
19
|
+
# Supported values: :statement (default), :row
|
|
20
|
+
# @option [#to_s] :comment (nil) The commend describing the trigger
|
|
21
|
+
# @yield [Proc] the block with the trigger's definition
|
|
22
|
+
# @yieldparam The receiver of methods specifying the trigger
|
|
23
|
+
#
|
|
24
|
+
# The trigger can be created either using inline syntax
|
|
25
|
+
#
|
|
26
|
+
# create_trigger "users", "do_something",
|
|
27
|
+
# function: "do_something()",
|
|
28
|
+
# for_each: :row,
|
|
29
|
+
# type: :after,
|
|
30
|
+
# events: %i[insert update],
|
|
31
|
+
# comment: "Does something useful"
|
|
32
|
+
#
|
|
33
|
+
# or using a block:
|
|
34
|
+
#
|
|
35
|
+
# create_trigger do |t|
|
|
36
|
+
# t.table "users"
|
|
37
|
+
# t.name "do_something"
|
|
38
|
+
# t.function "do_something()"
|
|
39
|
+
# t.for_each :row
|
|
40
|
+
# t.type :after
|
|
41
|
+
# t.events %i[insert update]
|
|
42
|
+
# t.comment "Does something useful"
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# With a `replace_existing: true` option,
|
|
46
|
+
# it will be created using the `CREATE OR REPLACE` clause.
|
|
47
|
+
# (Available in PostgreSQL v14+).
|
|
48
|
+
#
|
|
49
|
+
# create_trigger "users", "do_something",
|
|
50
|
+
# function: "do_something()",
|
|
51
|
+
# type: :after,
|
|
52
|
+
# events: %i[insert update],
|
|
53
|
+
# replace_previous: true
|
|
54
|
+
#
|
|
55
|
+
# In this case the migration is irreversible because we
|
|
56
|
+
# don't know if and how to restore its previous definition.
|
|
57
|
+
|
|
58
|
+
module PGTrunk::Operations::Triggers
|
|
59
|
+
# @private
|
|
60
|
+
class CreateTrigger < Base
|
|
61
|
+
validates :function, :type, :events, presence: true
|
|
62
|
+
validates :if_exists, :new_name, absence: true
|
|
63
|
+
|
|
64
|
+
from_sql do |_version|
|
|
65
|
+
<<~SQL
|
|
66
|
+
WITH t AS (
|
|
67
|
+
SELECT
|
|
68
|
+
t.oid,
|
|
69
|
+
t.tgname AS name,
|
|
70
|
+
(
|
|
71
|
+
CASE WHEN t.tgconstraint != 0 THEN true END
|
|
72
|
+
) AS constraint,
|
|
73
|
+
(
|
|
74
|
+
CASE
|
|
75
|
+
WHEN t.tgdeferrable AND t.tginitdeferred THEN 'deferred'
|
|
76
|
+
WHEN t.tgdeferrable AND NOT t.tginitdeferred THEN 'immediate'
|
|
77
|
+
END
|
|
78
|
+
) AS "initially",
|
|
79
|
+
pg_get_triggerdef(t.oid, true) AS snippet,
|
|
80
|
+
(
|
|
81
|
+
CASE
|
|
82
|
+
WHEN (t.tgtype::int::bit(7) & b'0000001')::int = 0 THEN 'statement'
|
|
83
|
+
ELSE 'row'
|
|
84
|
+
END
|
|
85
|
+
) AS for_each,
|
|
86
|
+
(
|
|
87
|
+
SELECT array_agg(attname)
|
|
88
|
+
FROM (
|
|
89
|
+
SELECT a.attname
|
|
90
|
+
FROM unnest(t.tgattr) col(num)
|
|
91
|
+
JOIN pg_attribute a ON a.attnum = col.num
|
|
92
|
+
WHERE a.attrelid = t.tgrelid
|
|
93
|
+
) list
|
|
94
|
+
) AS columns,
|
|
95
|
+
(
|
|
96
|
+
CASE
|
|
97
|
+
WHEN ((tgtype::int::bit(7) & b'0000010')::int != 0) THEN 'before'
|
|
98
|
+
WHEN ((tgtype::int::bit(7) & b'0000010')::int = 0) THEN 'after'
|
|
99
|
+
ELSE 'instead_of'
|
|
100
|
+
END
|
|
101
|
+
) AS type,
|
|
102
|
+
array_remove(
|
|
103
|
+
ARRAY[
|
|
104
|
+
(CASE WHEN (tgtype::int::bit(7) & b'0000100')::int != 0 THEN 'insert' END),
|
|
105
|
+
(CASE WHEN (tgtype::int::bit(7) & b'0001000')::int != 0 THEN 'delete' END),
|
|
106
|
+
(CASE WHEN (tgtype::int::bit(7) & b'0010000')::int != 0 THEN 'update' END),
|
|
107
|
+
(CASE WHEN (tgtype::int::bit(7) & b'0100000')::int != 0 THEN 'truncate' END)
|
|
108
|
+
]::text[],
|
|
109
|
+
NULL
|
|
110
|
+
) AS events,
|
|
111
|
+
(c.relnamespace::regnamespace || '.' || c.relname) AS "table",
|
|
112
|
+
(f.pronamespace::regnamespace || '.' || f.proname || '()') AS function,
|
|
113
|
+
d.description AS comment
|
|
114
|
+
FROM pg_trigger t
|
|
115
|
+
JOIN pg_proc f ON f.oid = t.tgfoid
|
|
116
|
+
JOIN pg_class c ON c.oid = t.tgrelid
|
|
117
|
+
LEFT JOIN pg_description d ON d.objoid = t.oid
|
|
118
|
+
)
|
|
119
|
+
SELECT
|
|
120
|
+
oid,
|
|
121
|
+
name,
|
|
122
|
+
"table",
|
|
123
|
+
function,
|
|
124
|
+
"constraint",
|
|
125
|
+
"initially",
|
|
126
|
+
for_each,
|
|
127
|
+
(
|
|
128
|
+
CASE
|
|
129
|
+
WHEN regexp_match(snippet, 'WHEN') IS NOT NULL
|
|
130
|
+
THEN
|
|
131
|
+
regexp_replace(
|
|
132
|
+
regexp_replace(snippet, '^.+WHEN [(]', ''),
|
|
133
|
+
'[)] EXECUTE.+',
|
|
134
|
+
''
|
|
135
|
+
)
|
|
136
|
+
END
|
|
137
|
+
) AS "when",
|
|
138
|
+
type,
|
|
139
|
+
events,
|
|
140
|
+
columns,
|
|
141
|
+
comment
|
|
142
|
+
FROM t
|
|
143
|
+
SQL
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def to_sql(version)
|
|
147
|
+
[
|
|
148
|
+
create_trigger(version),
|
|
149
|
+
*create_comment,
|
|
150
|
+
register_trigger,
|
|
151
|
+
].join(" ")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def invert
|
|
155
|
+
irreversible!("replace_existing: true") if replace_existing
|
|
156
|
+
DropTrigger.new(**to_h)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
def create_trigger(version)
|
|
162
|
+
sql = "CREATE"
|
|
163
|
+
sql << " OR REPLACE" if replace_existing && version >= "14"
|
|
164
|
+
sql << " CONSTRAINT" if constraint
|
|
165
|
+
sql << " TRIGGER #{name.name.inspect}"
|
|
166
|
+
sql << " BEFORE #{events_sql}" if type == :before
|
|
167
|
+
sql << " AFTER #{events_sql}" if type == :after
|
|
168
|
+
sql << " INSTEAD OF #{events_sql}" if type == :instead_of
|
|
169
|
+
sql << " ON #{table.to_sql}"
|
|
170
|
+
sql << " DEFERRABLE" if initially.present?
|
|
171
|
+
sql << " INITIALLY DEFERRED" if initially == :deferred
|
|
172
|
+
sql << " FOR EACH ROW" if for_each&.== :row
|
|
173
|
+
sql << " WHEN (#{self.when})" if self.when.present?
|
|
174
|
+
sql << " EXECUTE PROCEDURE #{function.to_sql(true)};"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def create_comment
|
|
178
|
+
return unless comment
|
|
179
|
+
|
|
180
|
+
<<~SQL.squish
|
|
181
|
+
COMMENT ON TRIGGER #{name.name.inspect} ON #{table.to_sql}
|
|
182
|
+
IS $comment$#{comment}$comment$;
|
|
183
|
+
SQL
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def register_trigger
|
|
187
|
+
<<~SQL.squish
|
|
188
|
+
INSERT INTO pg_trunk (oid, classid)
|
|
189
|
+
SELECT t.oid, 'pg_trigger'::regclass
|
|
190
|
+
FROM pg_trigger t JOIN pg_class c ON t.tgrelid = c.oid
|
|
191
|
+
WHERE c.relname = #{table.quoted}
|
|
192
|
+
AND c.relnamespace = #{table.namespace}
|
|
193
|
+
AND t.tgname = #{name.quoted}
|
|
194
|
+
ON CONFLICT DO NOTHING;
|
|
195
|
+
SQL
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def events_sql
|
|
199
|
+
events.map do |event|
|
|
200
|
+
if event == :update && columns.present?
|
|
201
|
+
"UPDATE OF #{columns.join(', ')}"
|
|
202
|
+
else
|
|
203
|
+
event.to_s.upcase
|
|
204
|
+
end
|
|
205
|
+
end.join(" OR ")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#drop_trigger(table, name = nil, **options, &block)
|
|
4
|
+
# Drop a trigger for a table
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] table (nil) The qualified name of the table
|
|
7
|
+
# @param [#to_s] name (nil) The name of the trigger
|
|
8
|
+
# @option [Boolean] :if_exists (false) Suppress the error when the trigger is absent
|
|
9
|
+
# @option [#to_s] :function (nil) The qualified name of the function to be called
|
|
10
|
+
# @option [Symbol] :type (nil) When the trigger should be run
|
|
11
|
+
# Supported values: :before, :after, :instead_of
|
|
12
|
+
# @option [Array<Symbol>] :events List of events running the trigger
|
|
13
|
+
# Supported values in the array: :insert, :update, :delete, :truncate
|
|
14
|
+
# @option [Boolean] :constraint (false) If the trigger is a constraint
|
|
15
|
+
# @option [Symbol] :initially (:immediate) If the constraint check should be deferred
|
|
16
|
+
# Supported values: :immediate (default), :deferred
|
|
17
|
+
# @option [#to_s] :when (nil) The SQL snippet definiing a condition for the trigger
|
|
18
|
+
# @option [Symbol] :for_each (:statement) Define if a trigger should be run for every row
|
|
19
|
+
# Supported values: :statement (default), :row
|
|
20
|
+
# @option [#to_s] :comment (nil) The commend describing the trigger
|
|
21
|
+
# @yield [Proc] the block with the trigger's definition
|
|
22
|
+
# @yieldparam The receiver of methods specifying the trigger
|
|
23
|
+
#
|
|
24
|
+
# A trigger can be dropped by a table and name:
|
|
25
|
+
#
|
|
26
|
+
# drop_trigger "users", "do_something"
|
|
27
|
+
#
|
|
28
|
+
# the default name can be restored from its attributes as well.
|
|
29
|
+
#
|
|
30
|
+
# drop_trigger "users" do |t|
|
|
31
|
+
# t.function "send_notifications()"
|
|
32
|
+
# t.for_each :row
|
|
33
|
+
# t.type :after
|
|
34
|
+
# t.events %i[update]
|
|
35
|
+
# t.columns %w[email phone]
|
|
36
|
+
# t.comment "Does something"
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# Notice, that you have to specify all attributes to make
|
|
40
|
+
# the operation reversible.
|
|
41
|
+
#
|
|
42
|
+
# The operation can be called with `if_exists` option. In this case
|
|
43
|
+
# it would do nothing when no trigger existed.
|
|
44
|
+
#
|
|
45
|
+
# drop_trigger "users", "unknown_trigger", if_exists: true
|
|
46
|
+
#
|
|
47
|
+
# This option, though, makes the operation irreversible because of
|
|
48
|
+
# uncertainty of the previous state of the database.
|
|
49
|
+
|
|
50
|
+
module PGTrunk::Operations::Triggers
|
|
51
|
+
# @private
|
|
52
|
+
class DropTrigger < Base
|
|
53
|
+
validates :replace_existing, :new_name, absence: true
|
|
54
|
+
|
|
55
|
+
def to_sql(_version)
|
|
56
|
+
sql = "DROP TRIGGER"
|
|
57
|
+
sql << " IF EXISTS" if if_exists
|
|
58
|
+
sql << " #{name.name.inspect} ON #{table.to_sql};"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def invert
|
|
62
|
+
irreversible!("if_exists: true") if if_exists
|
|
63
|
+
CreateTrigger.new(**to_h)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#rename_trigger(table, name = nil, **options, &block)
|
|
4
|
+
# Rename a trigger
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] table (nil) The qualified name of the table
|
|
7
|
+
# @param [#to_s] name (nil) The name of the trigger
|
|
8
|
+
# @option [#to_s] :to (nil) The new name of the trigger
|
|
9
|
+
# @param [#to_s] table (nil) The qualified name of the table
|
|
10
|
+
# @param [#to_s] name (nil) The current name of the trigger
|
|
11
|
+
# @option [#to_s] :to (nil) The new name for the trigger
|
|
12
|
+
# @option [#to_s] :function (nil) The qualified name of the function to be called
|
|
13
|
+
# @option [Symbol] :type (nil) When the trigger should be run
|
|
14
|
+
# Supported values: :before, :after, :instead_of
|
|
15
|
+
# @option [Array<Symbol>] :events List of events running the trigger
|
|
16
|
+
# Supported values in the array: :insert, :update, :delete, :truncate
|
|
17
|
+
# @option [Symbol] :for_each (:statement) Define if a trigger should be run for every row
|
|
18
|
+
# Supported values: :statement (default), :row
|
|
19
|
+
# @yield [Proc] the block with the trigger's definition
|
|
20
|
+
# @yieldparam The receiver of methods specifying the trigger
|
|
21
|
+
#
|
|
22
|
+
# A trigger can be renamed by either setting a new name explicitly
|
|
23
|
+
#
|
|
24
|
+
# rename_trigger "users", "do_something", to: "do_something_different"
|
|
25
|
+
#
|
|
26
|
+
# or resetting it to the default (generated) value.
|
|
27
|
+
#
|
|
28
|
+
# rename_trigger "users", "do_something"
|
|
29
|
+
#
|
|
30
|
+
# The previously generated name of the trigger can be get
|
|
31
|
+
# from its parameters. In this case all the essentials
|
|
32
|
+
# parameters must be specified:
|
|
33
|
+
#
|
|
34
|
+
# rename_trigger "users", to: "do_something_different" do |t|
|
|
35
|
+
# t.function "do_something()"
|
|
36
|
+
# t.for_each :row
|
|
37
|
+
# t.type :after
|
|
38
|
+
# t.events %i[insert update]
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# In the same way, when you reset the name to default,
|
|
42
|
+
# all the essential parameters must be got to make the trigger
|
|
43
|
+
# invertible.
|
|
44
|
+
#
|
|
45
|
+
# rename_trigger "users", "do_something" do |t|
|
|
46
|
+
# t.function "do_something()"
|
|
47
|
+
# t.for_each :row
|
|
48
|
+
# t.type :after
|
|
49
|
+
# t.events %i[insert update]
|
|
50
|
+
# end
|
|
51
|
+
|
|
52
|
+
module PGTrunk::Operations::Triggers
|
|
53
|
+
# @private
|
|
54
|
+
class RenameTrigger < Base
|
|
55
|
+
after_initialize { self.new_name = generated_name if new_name.blank? }
|
|
56
|
+
|
|
57
|
+
validates :if_exists, :constraint, :initially, :when, :replace_existing, absence: true
|
|
58
|
+
validates :new_name, presence: true
|
|
59
|
+
|
|
60
|
+
def to_sql(_version)
|
|
61
|
+
<<~SQL.squish
|
|
62
|
+
ALTER TRIGGER #{name.name.inspect} ON #{table.to_sql}
|
|
63
|
+
RENAME TO #{new_name.name.inspect};
|
|
64
|
+
SQL
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def invert
|
|
68
|
+
self.class.new(**to_h, name: new_name, to: name)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# nodoc
|
|
4
|
+
module PGTrunk::Operations
|
|
5
|
+
# @private
|
|
6
|
+
# Namespace for operations with triggers
|
|
7
|
+
module Triggers
|
|
8
|
+
require_relative "triggers/base"
|
|
9
|
+
require_relative "triggers/change_trigger"
|
|
10
|
+
require_relative "triggers/create_trigger"
|
|
11
|
+
require_relative "triggers/drop_trigger"
|
|
12
|
+
require_relative "triggers/rename_trigger"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module PGTrunk::Operations::Views
|
|
4
|
+
# @abstract
|
|
5
|
+
# @private
|
|
6
|
+
# Base class for operations with views
|
|
7
|
+
class Base < PGTrunk::Operation
|
|
8
|
+
# All attributes that can be used by view-related commands
|
|
9
|
+
attribute :check, :pg_trunk_symbol
|
|
10
|
+
attribute :force, :pg_trunk_symbol
|
|
11
|
+
attribute :replace_existing, :boolean
|
|
12
|
+
attribute :sql_definition, :pg_trunk_multiline_text
|
|
13
|
+
attribute :version, :integer, aliases: :revert_to_version
|
|
14
|
+
|
|
15
|
+
# Load missed `sql_definition` from the external file
|
|
16
|
+
after_initialize { self.sql_definition ||= read_snippet_from(:views) }
|
|
17
|
+
|
|
18
|
+
# Ensure correctness of present values
|
|
19
|
+
validates :check, inclusion: %i[local cascaded], allow_nil: true
|
|
20
|
+
validates :force, inclusion: %i[cascade restrict], allow_nil: true
|
|
21
|
+
|
|
22
|
+
# Use comparison by name from pg_trunk operations base class (default)
|
|
23
|
+
# Support name as the only positional argument (default)
|
|
24
|
+
|
|
25
|
+
ruby_snippet do |s|
|
|
26
|
+
s.ruby_param(name.lean) if name.present?
|
|
27
|
+
s.ruby_param(to: new_name.lean) if new_name.present?
|
|
28
|
+
s.ruby_param(replace_existing: true) if replace_existing
|
|
29
|
+
s.ruby_param(if_exists: true) if if_exists
|
|
30
|
+
s.ruby_param(force: :cascade) if force == :cascade
|
|
31
|
+
|
|
32
|
+
s.ruby_line(:version, version, from: from_version)
|
|
33
|
+
s.ruby_line(:sql_definition, sql_definition, from: from_sql_definition)
|
|
34
|
+
s.ruby_line(:check, check, from: from_check)
|
|
35
|
+
s.ruby_line(:comment, comment, from: from_comment)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#change_view(name, **options, &block)
|
|
4
|
+
# Modify a view
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] name (nil) The qualified name of the view
|
|
7
|
+
# @option [Boolean] :if_exists (false) Suppress the error when the view is absent
|
|
8
|
+
# @yield [Proc] the block with the view's definition
|
|
9
|
+
# @yieldparam The receiver of methods specifying the view
|
|
10
|
+
#
|
|
11
|
+
# The operation replaces the view with a new definition(s):
|
|
12
|
+
#
|
|
13
|
+
# change_view "admin_users" do |v|
|
|
14
|
+
# v.sql_definition: <<~SQL, from: <<~SQL
|
|
15
|
+
# SELECT id, name FROM users WHERE admin;
|
|
16
|
+
# SQL
|
|
17
|
+
# SELECT * FROM users WHERE admin;
|
|
18
|
+
# SQL
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# For some compatibility to the `scenic` gem, we also support
|
|
22
|
+
# adding a definition via its version:
|
|
23
|
+
#
|
|
24
|
+
# change_view "admin_users" do |v|
|
|
25
|
+
# v.version 2, from: 1
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# It is expected, that both `db/views/admin_users_v01.sql`
|
|
29
|
+
# and `db/views/admin_users_v02.sql` to contain SQL snippets.
|
|
30
|
+
#
|
|
31
|
+
# Please, notice that neither deletion of columns,
|
|
32
|
+
# nor changing their types is supported by the PostgreSQL.
|
|
33
|
+
#
|
|
34
|
+
# You can also (re)set a comment describing the view,
|
|
35
|
+
# and the check option (either `:local` or `:cascaded`):
|
|
36
|
+
#
|
|
37
|
+
# change_view "admin_users" do |v|
|
|
38
|
+
# v.check :local, from: :cascaded
|
|
39
|
+
# v.comment "Admin users only", from: ""
|
|
40
|
+
# end
|
|
41
|
+
|
|
42
|
+
module PGTrunk::Operations::Views
|
|
43
|
+
# @private
|
|
44
|
+
class ChangeView < Base
|
|
45
|
+
validates :replace_existing, :force, :new_name, absence: true
|
|
46
|
+
validate { errors.add :base, "Changes can't be blank" if changes.blank? }
|
|
47
|
+
validate do
|
|
48
|
+
next if if_exists || name.blank?
|
|
49
|
+
|
|
50
|
+
errors.add :base, "Can't find the view #{name.lean}" unless create_view
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_sql(server_version)
|
|
54
|
+
create_view&.to_sql(server_version)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def invert
|
|
58
|
+
irreversible!("if_exists: true") if if_exists
|
|
59
|
+
undefined = inversion.select { |_, v| v.nil? }.keys.join(", ").presence
|
|
60
|
+
raise IrreversibleMigration.new(self, nil, <<~MSG.squish) if undefined
|
|
61
|
+
Undefined values to revert #{undefined}.
|
|
62
|
+
MSG
|
|
63
|
+
|
|
64
|
+
self.class.new(**inversion, name: name)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def changes
|
|
70
|
+
@changes ||= to_h.slice(:sql_definition, :check, :comment).compact
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def inversion
|
|
74
|
+
@inversion ||= {}.tap do |inv|
|
|
75
|
+
inv[:version] = from_version if version
|
|
76
|
+
inv[:sql_definition] = from_sql_definition unless version
|
|
77
|
+
inv[:check] = from_check if check
|
|
78
|
+
inv[:comment] = from_comment if comment
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create_view
|
|
83
|
+
return if name.blank?
|
|
84
|
+
|
|
85
|
+
@create_view ||= CreateView.find { |o| o.name == name }&.tap do |op|
|
|
86
|
+
op.attributes = { **changes, replace_existing: true }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#create_view(name, **options, &block)
|
|
4
|
+
# Create a view
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] name (nil) The qualified name of the view
|
|
7
|
+
# @option [Boolean] :replace_existing (false) If the view should overwrite an existing one
|
|
8
|
+
# @option [#to_s] :sql_definition (nil) The snippet containing the query
|
|
9
|
+
# @option [#to_i] :version (nil)
|
|
10
|
+
# The alternative way to set sql_definition by referencing to a file containing the snippet
|
|
11
|
+
# @option [#to_s] :check (nil) Controls the behavior of automatically updatable views
|
|
12
|
+
# Supported values: :local, :cascaded
|
|
13
|
+
# @option [#to_s] :comment (nil) The comment describing the view
|
|
14
|
+
# @yield [Proc] the block with the view's definition
|
|
15
|
+
# @yieldparam The receiver of methods specifying the view
|
|
16
|
+
#
|
|
17
|
+
# The operation creates the view using its `sql_definition`:
|
|
18
|
+
#
|
|
19
|
+
# create_view("views.admin_users", sql_definition: <<~SQL)
|
|
20
|
+
# SELECT id, name FROM users WHERE admin;
|
|
21
|
+
# SQL
|
|
22
|
+
#
|
|
23
|
+
# For compatibility to the `scenic` gem, we also support
|
|
24
|
+
# adding a definition via its version:
|
|
25
|
+
#
|
|
26
|
+
# create_view "admin_users", version: 1
|
|
27
|
+
#
|
|
28
|
+
# It is expected, that a `db/views/admin_users_v01.sql`
|
|
29
|
+
# to contain the SQL snippet.
|
|
30
|
+
#
|
|
31
|
+
# You can also set a comment describing the view, and the check option
|
|
32
|
+
# (either `:local` or `:cascaded`):
|
|
33
|
+
#
|
|
34
|
+
# create_view "admin_users" do |v|
|
|
35
|
+
# v.sql_definition "SELECT id, name FROM users WHERE admin;"
|
|
36
|
+
# v.check :local
|
|
37
|
+
# v.comment "Admin users only"
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# With the `replace_existing: true` option the operation
|
|
41
|
+
# would use `CREATE OR REPLACE VIEW` command, so it
|
|
42
|
+
# can be used to "update" (or reload) the existing view.
|
|
43
|
+
#
|
|
44
|
+
# create_view "admin_users", version: 1, replace_existing: true
|
|
45
|
+
#
|
|
46
|
+
# This option makes an operation irreversible due to uncertainty
|
|
47
|
+
# of the previous state of the database.
|
|
48
|
+
|
|
49
|
+
module PGTrunk::Operations::Views
|
|
50
|
+
# @private
|
|
51
|
+
class CreateView < Base
|
|
52
|
+
validates :sql_definition, presence: true
|
|
53
|
+
validates :if_exists, :force, :new_name, absence: true
|
|
54
|
+
|
|
55
|
+
from_sql do |_version|
|
|
56
|
+
<<~SQL
|
|
57
|
+
SELECT
|
|
58
|
+
c.oid,
|
|
59
|
+
(c.relnamespace::regnamespace || '.' || c.relname) AS name,
|
|
60
|
+
replace(pg_get_viewdef(c.oid, 60), ';', '') AS sql_definition,
|
|
61
|
+
(
|
|
62
|
+
SELECT option_value
|
|
63
|
+
FROM pg_options_to_table(c.reloptions)
|
|
64
|
+
WHERE option_name = 'check_option'
|
|
65
|
+
LIMIT 1
|
|
66
|
+
) AS check,
|
|
67
|
+
d.description AS comment
|
|
68
|
+
FROM pg_class c
|
|
69
|
+
JOIN pg_trunk e ON e.oid = c.oid
|
|
70
|
+
AND e.classid = 'pg_class'::regclass
|
|
71
|
+
LEFT JOIN pg_description d ON d.objoid = c.oid
|
|
72
|
+
AND d.classoid = 'pg_class'::regclass
|
|
73
|
+
WHERE c.relkind = 'v';
|
|
74
|
+
SQL
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_sql(_version)
|
|
78
|
+
[create_view, *create_comment, register_view].join(" ")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def invert
|
|
82
|
+
irreversible!("replace_existing: true") if replace_existing
|
|
83
|
+
DropView.new(**to_h)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def create_view
|
|
89
|
+
sql = "CREATE"
|
|
90
|
+
sql << " OR REPLACE" if replace_existing
|
|
91
|
+
sql << " VIEW #{name.to_sql}"
|
|
92
|
+
sql << " AS (#{sql_definition})"
|
|
93
|
+
sql << " WITH #{check.to_s.upcase} CHECK OPTION" if check.present?
|
|
94
|
+
sql << ";"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def create_comment
|
|
98
|
+
return if comment.blank?
|
|
99
|
+
|
|
100
|
+
"COMMENT ON VIEW #{name.to_sql} IS $comment$#{comment}$comment$;"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def register_view
|
|
104
|
+
<<~SQL.squish
|
|
105
|
+
INSERT INTO pg_trunk (oid, classid)
|
|
106
|
+
SELECT oid, 'pg_class'::regclass
|
|
107
|
+
FROM pg_class
|
|
108
|
+
WHERE relname = #{name.quoted}
|
|
109
|
+
AND relnamespace = #{name.namespace}
|
|
110
|
+
AND relkind = 'v'
|
|
111
|
+
ON CONFLICT DO NOTHING;
|
|
112
|
+
SQL
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# @!method ActiveRecord::Migration#drop_view(name, **options, &block)
|
|
4
|
+
# Drop a view
|
|
5
|
+
#
|
|
6
|
+
# @param [#to_s] name (nil) The qualified name of the view
|
|
7
|
+
# @option [Boolean] :replace_existing (false) If the view should overwrite an existing one
|
|
8
|
+
# @option [Boolean] :if_exists (false) Suppress the error when the view is absent
|
|
9
|
+
# @option [Symbol] :force (:restrict) How to process dependent objects (`:cascade` or `:restrict`)
|
|
10
|
+
# @option [#to_s] :sql_definition (nil) The snippet containing the query
|
|
11
|
+
# @option [#to_i] :revert_to_version (nil)
|
|
12
|
+
# The alternative way to set sql_definition by referencing to a file containing the snippet
|
|
13
|
+
# @option [#to_s] :check (nil) Controls the behavior of automatically updatable views
|
|
14
|
+
# Supported values: :local, :cascaded
|
|
15
|
+
# @option [#to_s] :comment (nil) The comment describing the view
|
|
16
|
+
# @yield [Proc] the block with the view's definition
|
|
17
|
+
# @yieldparam The receiver of methods specifying the view
|
|
18
|
+
#
|
|
19
|
+
# The operation drops the existing view identified by its
|
|
20
|
+
# qualified name (it can include a schema).
|
|
21
|
+
#
|
|
22
|
+
# drop_view "views.admin_users"
|
|
23
|
+
#
|
|
24
|
+
# To make the operation invertible, use the same options
|
|
25
|
+
# as in the `create_view` operation.
|
|
26
|
+
#
|
|
27
|
+
# drop_view "views.admin_users" do |v|
|
|
28
|
+
# v.sql_definition "SELECT name, email FROM users WHERE admin;"
|
|
29
|
+
# v.check :local
|
|
30
|
+
# v.comment "Admin users only"
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# You can also use a version-base SQL definition like:
|
|
34
|
+
#
|
|
35
|
+
# drop_view "views.admin_users", revert_to_version: 1
|
|
36
|
+
#
|
|
37
|
+
# With the `force: :cascade` option the operation would remove
|
|
38
|
+
# all the objects which depend on the view.
|
|
39
|
+
#
|
|
40
|
+
# drop_view "views.admin_users", force: :cascade
|
|
41
|
+
#
|
|
42
|
+
# With the `if_exists: true` option the operation won't fail
|
|
43
|
+
# even when the view was absent in the database.
|
|
44
|
+
#
|
|
45
|
+
# drop_view "views.admin_users", if_exists: true
|
|
46
|
+
#
|
|
47
|
+
# Both options make an operation irreversible due to uncertainty
|
|
48
|
+
# of the previous state of the database.
|
|
49
|
+
|
|
50
|
+
module PGTrunk::Operations::Views
|
|
51
|
+
# @private
|
|
52
|
+
class DropView < Base
|
|
53
|
+
validates :replace_existing, :new_name, absence: true
|
|
54
|
+
|
|
55
|
+
def to_sql(_version)
|
|
56
|
+
sql = "DROP VIEW"
|
|
57
|
+
sql << " IF EXISTS" if if_exists
|
|
58
|
+
sql << " #{name.to_sql}"
|
|
59
|
+
sql << " CASCADE" if force == :cascade
|
|
60
|
+
sql << ";"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def invert
|
|
64
|
+
irreversible!("if_exists: true") if if_exists
|
|
65
|
+
irreversible!("force: :cascade") if force == :cascade
|
|
66
|
+
CreateView.new(**to_h.except(:force))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|