mat_views 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +6 -6
- data/app/jobs/mat_views/application_job.rb +105 -0
- data/app/jobs/mat_views/create_view_job.rb +16 -118
- data/app/jobs/mat_views/delete_view_job.rb +18 -125
- data/app/jobs/mat_views/refresh_view_job.rb +7 -127
- data/app/models/mat_views/mat_view_run.rb +23 -3
- data/lib/ext/exception.rb +20 -0
- data/lib/generators/mat_views/install/templates/create_mat_view_runs.rb +2 -2
- data/lib/mat_views/jobs/adapter.rb +8 -5
- data/lib/mat_views/service_response.rb +30 -15
- data/lib/mat_views/services/base_service.rb +179 -24
- data/lib/mat_views/services/concurrent_refresh.rb +35 -121
- data/lib/mat_views/services/create_view.rb +64 -47
- data/lib/mat_views/services/delete_view.rb +41 -87
- data/lib/mat_views/services/regular_refresh.rb +35 -92
- data/lib/mat_views/services/swap_refresh.rb +75 -117
- data/lib/mat_views/version.rb +1 -1
- data/lib/mat_views.rb +3 -2
- data/lib/tasks/helpers.rb +19 -19
- data/lib/tasks/mat_views_tasks.rake +35 -29
- metadata +3 -2
@@ -21,102 +21,106 @@ module MatViews
|
|
21
21
|
# 2. In a transaction: rename original → old, tmp → original, drop old.
|
22
22
|
# 3. Recreate declared unique indexes (if any).
|
23
23
|
#
|
24
|
-
#
|
25
|
-
# - `:estimated`
|
26
|
-
# - `:exact` → accurate, using `COUNT(*)`
|
27
|
-
# - `nil` → skip row count
|
24
|
+
# Options:
|
25
|
+
# - `row_count_strategy:` (Symbol, default: :none) → one of `:estimated`, `:exact`, or `:none or nil` to control row count reporting
|
28
26
|
#
|
29
|
-
#
|
27
|
+
# Returns a {MatViews::ServiceResponse}
|
30
28
|
#
|
31
|
-
# @
|
32
|
-
#
|
33
|
-
#
|
29
|
+
# @see MatViews::Services::ConcurrentRefresh
|
30
|
+
# @see MatViews::Services::RegularRefresh
|
31
|
+
#
|
32
|
+
# @example Direct usage
|
33
|
+
# svc = MatViews::Services::SwapRefresh.new(definition, **options)
|
34
|
+
# response = svc.run
|
35
|
+
# response.success? # => true/false
|
36
|
+
#
|
37
|
+
# @example via job, this is the typical usage and will create a run record in the DB
|
38
|
+
# When definition.refresh_strategy == "concurrent"
|
39
|
+
# MatViews::Jobs::Adapter.enqueue(MatViews::Services::SwapRefresh, definition.id, **options)
|
34
40
|
#
|
35
41
|
class SwapRefresh < BaseService
|
36
|
-
|
37
|
-
# Row count strategy (`:estimated`, `:exact`, `nil`).
|
38
|
-
#
|
39
|
-
# @return [Symbol, nil]
|
40
|
-
attr_reader :row_count_strategy
|
41
|
-
|
42
|
-
##
|
43
|
-
# @param definition [MatViews::MatViewDefinition]
|
44
|
-
# @param row_count_strategy [Symbol, nil]
|
45
|
-
def initialize(definition, row_count_strategy: :estimated)
|
46
|
-
super(definition)
|
47
|
-
@row_count_strategy = row_count_strategy
|
48
|
-
end
|
42
|
+
private
|
49
43
|
|
50
44
|
##
|
51
45
|
# Execute the swap refresh.
|
52
46
|
#
|
53
47
|
# @return [MatViews::ServiceResponse]
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
payload = { view: "#{schema}.#{rel}" }
|
65
|
-
payload[:row_count] = fetch_rows_count if row_count_strategy.present?
|
66
|
-
|
67
|
-
ok(:updated, payload: payload, meta: { steps: steps, row_count_strategy: row_count_strategy, swap: true })
|
68
|
-
rescue StandardError => e
|
69
|
-
error_response(e,
|
70
|
-
meta: {
|
71
|
-
steps: steps,
|
72
|
-
backtrace: Array(e.backtrace),
|
73
|
-
row_count_strategy: row_count_strategy,
|
74
|
-
swap: true
|
75
|
-
},
|
76
|
-
payload: { view: "#{schema}.#{rel}" })
|
77
|
-
end
|
48
|
+
# - `status: :updated` on success, with `response` containing:
|
49
|
+
# - `view` - the qualified view name
|
50
|
+
# - `row_count_before` - if requested and available
|
51
|
+
# - `row_count_after` - if requested and available
|
52
|
+
# - `status: :error` with `error` on failure, with `error` containing:
|
53
|
+
# - serlialized exception class, message, and backtrace in a hash
|
54
|
+
#
|
55
|
+
def _run
|
56
|
+
self.response = { view: "#{schema}.#{rel}" }
|
78
57
|
|
79
|
-
|
58
|
+
response[:row_count_before] = fetch_rows_count
|
59
|
+
response[:sql] = swap_view
|
60
|
+
response[:row_count_after] = fetch_rows_count
|
80
61
|
|
81
|
-
|
82
|
-
|
83
|
-
# ────────────────────────────────────────────────────────────────
|
62
|
+
ok(:updated)
|
63
|
+
end
|
84
64
|
|
85
65
|
##
|
86
66
|
# Ensure name validity and existence of original view.
|
87
67
|
#
|
88
68
|
# @api private
|
89
69
|
# @return [MatViews::ServiceResponse, nil]
|
90
|
-
def prepare
|
91
|
-
|
92
|
-
|
70
|
+
def prepare
|
71
|
+
raise_err "Invalid view name format: #{definition.name.inspect}" unless valid_name?
|
72
|
+
raise_err "Materialized view #{schema}.#{rel} does not exist" unless view_exists?
|
93
73
|
|
94
74
|
nil
|
95
75
|
end
|
96
76
|
|
77
|
+
##
|
78
|
+
# Assign the request parameters.
|
79
|
+
# Called by {#run} before {#prepare}.
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
# @return [void]
|
83
|
+
#
|
84
|
+
def assign_request
|
85
|
+
self.request = { row_count_strategy: row_count_strategy, swap: true }
|
86
|
+
end
|
87
|
+
|
97
88
|
##
|
98
89
|
# Perform rename/drop/index recreation in a transaction.
|
99
90
|
#
|
100
91
|
# @api private
|
101
92
|
# @return [Array<String>] SQL steps executed
|
102
|
-
def
|
103
|
-
|
93
|
+
def swap_view
|
94
|
+
conn.execute(create_temp_view_sql)
|
95
|
+
steps = [
|
96
|
+
move_current_to_old_sql,
|
97
|
+
move_temp_to_current_sql,
|
98
|
+
drop_old_view_sql,
|
99
|
+
recreate_declared_unique_indexes_sql
|
100
|
+
].compact
|
104
101
|
conn.transaction do
|
105
|
-
|
106
|
-
|
107
|
-
conn.execute(rename_orig_sql)
|
102
|
+
steps.each { |step| conn.execute(step) }
|
103
|
+
end
|
108
104
|
|
109
|
-
|
110
|
-
|
111
|
-
|
105
|
+
# prepend the create step
|
106
|
+
steps.unshift(create_temp_view_sql)
|
107
|
+
steps
|
108
|
+
end
|
112
109
|
|
113
|
-
|
114
|
-
|
115
|
-
|
110
|
+
def create_temp_view_sql
|
111
|
+
@create_temp_view_sql ||= %(CREATE MATERIALIZED VIEW #{q_tmp} AS #{definition.sql} WITH DATA)
|
112
|
+
end
|
116
113
|
|
117
|
-
|
118
|
-
|
119
|
-
|
114
|
+
def move_current_to_old_sql
|
115
|
+
%(ALTER MATERIALIZED VIEW #{qualified_rel} RENAME TO #{conn.quote_column_name(old_rel)})
|
116
|
+
end
|
117
|
+
|
118
|
+
def move_temp_to_current_sql
|
119
|
+
%(ALTER MATERIALIZED VIEW #{q_tmp} RENAME TO #{conn.quote_column_name(rel)})
|
120
|
+
end
|
121
|
+
|
122
|
+
def drop_old_view_sql
|
123
|
+
%(DROP MATERIALIZED VIEW #{q_old})
|
120
124
|
end
|
121
125
|
|
122
126
|
##
|
@@ -159,62 +163,16 @@ module MatViews
|
|
159
163
|
# Recreate declared unique indexes on the swapped-in view.
|
160
164
|
#
|
161
165
|
# @api private
|
162
|
-
# @
|
163
|
-
|
164
|
-
# @param steps [Array<String>] collected SQL
|
165
|
-
def recreate_declared_unique_indexes!(schema:, rel:, steps:)
|
166
|
+
# @return [String] SQL statements to execute
|
167
|
+
def recreate_declared_unique_indexes_sql
|
166
168
|
cols = Array(definition.unique_index_columns).map(&:to_s).reject(&:empty?)
|
167
|
-
return if cols.empty?
|
169
|
+
return nil if cols.empty?
|
168
170
|
|
169
|
-
quoted_cols = cols.map { |
|
171
|
+
quoted_cols = cols.map { |col| conn.quote_column_name(col) }.join(', ')
|
170
172
|
idx_name = conn.quote_column_name("#{rel}_uniq_#{cols.join('_')}")
|
171
173
|
q_rel = conn.quote_table_name("#{schema}.#{rel}")
|
172
174
|
|
173
|
-
|
174
|
-
steps << sql
|
175
|
-
conn.execute(sql)
|
176
|
-
end
|
177
|
-
|
178
|
-
# ────────────────────────────────────────────────────────────────
|
179
|
-
# rows counting
|
180
|
-
# ────────────────────────────────────────────────────────────────
|
181
|
-
|
182
|
-
##
|
183
|
-
# Fetch the row count based on the configured strategy.
|
184
|
-
#
|
185
|
-
# @api private
|
186
|
-
# @return [Integer, nil]
|
187
|
-
def fetch_rows_count
|
188
|
-
case row_count_strategy
|
189
|
-
when :estimated then estimated_rows_count
|
190
|
-
when :exact then exact_rows_count
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
##
|
195
|
-
# Approximate row count via `pg_class.reltuples`.
|
196
|
-
#
|
197
|
-
# @api private
|
198
|
-
# @return [Integer]
|
199
|
-
def estimated_rows_count
|
200
|
-
conn.select_value(<<~SQL).to_i
|
201
|
-
SELECT COALESCE(c.reltuples::bigint, 0)
|
202
|
-
FROM pg_class c
|
203
|
-
JOIN pg_namespace n ON n.oid = c.relnamespace
|
204
|
-
WHERE c.relkind IN ('m','r','p')
|
205
|
-
AND n.nspname = #{conn.quote(schema)}
|
206
|
-
AND c.relname = #{conn.quote(rel)}
|
207
|
-
LIMIT 1
|
208
|
-
SQL
|
209
|
-
end
|
210
|
-
|
211
|
-
##
|
212
|
-
# Accurate row count via `COUNT(*)`.
|
213
|
-
#
|
214
|
-
# @api private
|
215
|
-
# @return [Integer]
|
216
|
-
def exact_rows_count
|
217
|
-
conn.select_value("SELECT COUNT(*) FROM #{qualified_rel}").to_i
|
175
|
+
%(CREATE UNIQUE INDEX #{idx_name} ON #{q_rel} (#{quoted_cols}))
|
218
176
|
end
|
219
177
|
end
|
220
178
|
end
|
data/lib/mat_views/version.rb
CHANGED
data/lib/mat_views.rb
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
# This source code is licensed under the MIT license found in the
|
6
6
|
# LICENSE file in the root directory of this source tree.
|
7
7
|
|
8
|
+
require 'ext/exception'
|
8
9
|
require 'mat_views/version'
|
9
10
|
require 'mat_views/engine'
|
10
11
|
require 'mat_views/configuration'
|
@@ -40,7 +41,7 @@ module MatViews
|
|
40
41
|
class << self
|
41
42
|
# Global configuration for MatViews
|
42
43
|
# @return [MatViews::Configuration]
|
43
|
-
|
44
|
+
attr_reader :configuration
|
44
45
|
|
45
46
|
# Configure MatViews via block.
|
46
47
|
#
|
@@ -50,7 +51,7 @@ module MatViews
|
|
50
51
|
# config.job_queue = :materialized
|
51
52
|
# end
|
52
53
|
def configure
|
53
|
-
|
54
|
+
@configuration ||= Configuration.new
|
54
55
|
yield(configuration)
|
55
56
|
end
|
56
57
|
end
|
data/lib/tasks/helpers.rb
CHANGED
@@ -51,12 +51,12 @@ module MatViews
|
|
51
51
|
end
|
52
52
|
|
53
53
|
# Parse row count strategy from arg or ROW_COUNT_STRATEGY env.
|
54
|
-
# Defaults to :
|
54
|
+
# Defaults to :none if blank.
|
55
55
|
def parse_row_count_strategy(arg)
|
56
|
-
|
57
|
-
return :
|
56
|
+
str = (arg || ENV.fetch('ROW_COUNT_STRATEGY', nil)).to_s.strip
|
57
|
+
return :none if str.empty?
|
58
58
|
|
59
|
-
|
59
|
+
str.to_sym
|
60
60
|
end
|
61
61
|
|
62
62
|
# Check if a materialized view exists in schema.
|
@@ -78,14 +78,15 @@ module MatViews
|
|
78
78
|
# @return [MatViews::MatViewDefinition]
|
79
79
|
# @raise [RuntimeError] if no definition found or mismatch with DB
|
80
80
|
def find_definition_by_name!(raw_name)
|
81
|
-
|
81
|
+
raw_name_string = raw_name&.to_s&.strip
|
82
|
+
raise 'view_name is required' unless raw_name_string && !raw_name_string.empty?
|
82
83
|
|
83
84
|
schema, rel =
|
84
|
-
if
|
85
|
-
parts =
|
85
|
+
if raw_name_string.include?('.')
|
86
|
+
parts = raw_name_string.split('.', 2)
|
86
87
|
[parts[0], parts[1]]
|
87
88
|
else
|
88
|
-
[nil,
|
89
|
+
[nil, raw_name_string]
|
89
90
|
end
|
90
91
|
|
91
92
|
defn = MatViews::MatViewDefinition.find_by(name: rel)
|
@@ -126,13 +127,13 @@ module MatViews
|
|
126
127
|
#
|
127
128
|
# @param definition_id [Integer] MatViewDefinition ID
|
128
129
|
# @param force [Boolean] whether to force creation
|
130
|
+
# @param row_count_strategy [Symbol] :estimated or :exact or :none
|
129
131
|
# @return [void]
|
130
|
-
def enqueue_create
|
131
|
-
q = MatViews.configuration.job_queue || :default
|
132
|
+
def enqueue_create(definition_id, force, row_count_strategy)
|
132
133
|
MatViews::Jobs::Adapter.enqueue(
|
133
134
|
MatViews::CreateViewJob,
|
134
|
-
queue:
|
135
|
-
args: [definition_id, force]
|
135
|
+
queue: MatViews.configuration.job_queue || :default,
|
136
|
+
args: [definition_id, force, row_count_strategy]
|
136
137
|
)
|
137
138
|
end
|
138
139
|
|
@@ -144,11 +145,10 @@ module MatViews
|
|
144
145
|
#
|
145
146
|
# This method allows scheduling a refresh operation with the specified row count strategy.
|
146
147
|
# It uses the configured job adapter to enqueue the job.
|
147
|
-
def enqueue_refresh
|
148
|
-
q = MatViews.configuration.job_queue || :default
|
148
|
+
def enqueue_refresh(definition_id, row_count_strategy)
|
149
149
|
MatViews::Jobs::Adapter.enqueue(
|
150
150
|
MatViews::RefreshViewJob,
|
151
|
-
queue:
|
151
|
+
queue: MatViews.configuration.job_queue || :default,
|
152
152
|
args: [definition_id, row_count_strategy]
|
153
153
|
)
|
154
154
|
end
|
@@ -168,16 +168,16 @@ module MatViews
|
|
168
168
|
#
|
169
169
|
# @param definition_id [Integer] MatViewDefinition ID
|
170
170
|
# @param cascade [Boolean] whether to drop with CASCADE
|
171
|
+
# @param row_count_strategy [Symbol] :estimated or :exact or :none
|
171
172
|
# @return [void]
|
172
173
|
#
|
173
174
|
# This method schedules a job to delete the materialized view, optionally with CASCADE.
|
174
175
|
# It uses the configured job adapter to enqueue the job.
|
175
|
-
def enqueue_delete
|
176
|
-
q = MatViews.configuration.job_queue || :default
|
176
|
+
def enqueue_delete(definition_id, cascade, row_count_strategy)
|
177
177
|
MatViews::Jobs::Adapter.enqueue(
|
178
178
|
MatViews::DeleteViewJob,
|
179
|
-
queue:
|
180
|
-
args: [definition_id, cascade]
|
179
|
+
queue: MatViews.configuration.job_queue || :default,
|
180
|
+
args: [definition_id, cascade, row_count_strategy]
|
181
181
|
)
|
182
182
|
end
|
183
183
|
end
|
@@ -10,33 +10,36 @@ namespace :mat_views do
|
|
10
10
|
# ───────────── CREATE ─────────────
|
11
11
|
|
12
12
|
desc 'Enqueue a CREATE for a specific view by its name (optionally schema-qualified)'
|
13
|
-
task :create_by_name, %i[view_name force yes] => :environment do |_t, args|
|
13
|
+
task :create_by_name, %i[view_name force row_count_strategy yes] => :environment do |_t, args|
|
14
|
+
rcs = helpers.parse_row_count_strategy(args[:row_count_strategy])
|
14
15
|
force = helpers.parse_force?(args[:force])
|
15
16
|
skip = helpers.skip_confirm?(args[:yes])
|
16
17
|
defn = helpers.find_definition_by_name!(args[:view_name])
|
17
18
|
|
18
|
-
helpers.confirm!("Enqueue CREATE for view=#{defn.name} (id=#{defn.id}), force=#{force}", skip: skip)
|
19
|
-
helpers.enqueue_create
|
20
|
-
helpers.logger.info("[mat_views] Enqueued CreateViewJob for definition ##{defn.id} (#{defn.name}), force=#{force}")
|
19
|
+
helpers.confirm!("Enqueue CREATE for view=#{defn.name} (id=#{defn.id}), force=#{force}, row_count_strategy=#{rcs}", skip: skip)
|
20
|
+
helpers.enqueue_create(defn.id, force, rcs)
|
21
|
+
helpers.logger.info("[mat_views] Enqueued CreateViewJob for definition ##{defn.id} (#{defn.name}), force=#{force}, row_count_strategy=#{rcs}.")
|
21
22
|
end
|
22
23
|
|
23
24
|
desc 'Enqueue a CREATE for a specific view by its definition ID'
|
24
|
-
task :create_by_id, %i[definition_id force yes] => :environment do |_t, args|
|
25
|
+
task :create_by_id, %i[definition_id force row_count_strategy yes] => :environment do |_t, args|
|
25
26
|
raise 'mat_views:create_by_id requires a definition_id parameter' if args[:definition_id].to_s.strip.empty?
|
26
27
|
|
28
|
+
rcs = helpers.parse_row_count_strategy(args[:row_count_strategy])
|
27
29
|
force = helpers.parse_force?(args[:force])
|
28
30
|
skip = helpers.skip_confirm?(args[:yes])
|
29
31
|
|
30
32
|
defn = MatViews::MatViewDefinition.find_by(id: args[:definition_id])
|
31
33
|
raise "No MatViews::MatViewDefinition found for id=#{args[:definition_id]}" unless defn
|
32
34
|
|
33
|
-
helpers.confirm!("Enqueue CREATE for id=#{defn.id} (#{defn.name}), force=#{force}", skip: skip)
|
34
|
-
helpers.enqueue_create
|
35
|
-
helpers.logger.info("[mat_views] Enqueued CreateViewJob for definition ##{defn.id} (#{defn.name}), force=#{force}")
|
35
|
+
helpers.confirm!("Enqueue CREATE for id=#{defn.id} (#{defn.name}), force=#{force}, row_count_strategy=#{rcs}", skip: skip)
|
36
|
+
helpers.enqueue_create(defn.id, force, rcs)
|
37
|
+
helpers.logger.info("[mat_views] Enqueued CreateViewJob for definition ##{defn.id} (#{defn.name}), force=#{force}, row_count_strategy=#{rcs}.")
|
36
38
|
end
|
37
39
|
|
38
40
|
desc 'Enqueue CREATE jobs for ALL defined materialized views'
|
39
|
-
task :create_all, %i[force yes] => :environment do |_t, args|
|
41
|
+
task :create_all, %i[force row_count_strategy yes] => :environment do |_t, args|
|
42
|
+
rcs = helpers.parse_row_count_strategy(args[:row_count_strategy])
|
40
43
|
force = helpers.parse_force?(args[:force])
|
41
44
|
skip = helpers.skip_confirm?(args[:yes])
|
42
45
|
|
@@ -47,9 +50,9 @@ namespace :mat_views do
|
|
47
50
|
next
|
48
51
|
end
|
49
52
|
|
50
|
-
helpers.confirm!("Enqueue CREATE for ALL (#{count}) views, force=#{force}", skip: skip)
|
51
|
-
scope.find_each { |defn| helpers.enqueue_create
|
52
|
-
helpers.logger.info("[mat_views] Enqueued #{count} CreateViewJob(s), force=#{force}.")
|
53
|
+
helpers.confirm!("Enqueue CREATE for ALL (#{count}) views, force=#{force}, row_count_strategy=#{rcs}", skip: skip)
|
54
|
+
scope.find_each { |defn| helpers.enqueue_create(defn.id, force, rcs) }
|
55
|
+
helpers.logger.info("[mat_views] Enqueued #{count} CreateViewJob(s), force=#{force}, row_count_strategy=#{rcs}.")
|
53
56
|
end
|
54
57
|
|
55
58
|
# ───────────── REFRESH ─────────────
|
@@ -61,8 +64,8 @@ namespace :mat_views do
|
|
61
64
|
defn = helpers.find_definition_by_name!(args[:view_name])
|
62
65
|
|
63
66
|
helpers.confirm!("Enqueue REFRESH for view=#{defn.name} (id=#{defn.id}), row_count_strategy=#{rcs}", skip: skip)
|
64
|
-
helpers.enqueue_refresh
|
65
|
-
helpers.logger.info("[mat_views] Enqueued RefreshViewJob for definition ##{defn.id} (#{defn.name}), row_count_strategy=#{rcs}")
|
67
|
+
helpers.enqueue_refresh(defn.id, rcs)
|
68
|
+
helpers.logger.info("[mat_views] Enqueued RefreshViewJob for definition ##{defn.id} (#{defn.name}), row_count_strategy=#{rcs}.")
|
66
69
|
end
|
67
70
|
|
68
71
|
desc 'Enqueue a REFRESH for a specific view by its definition ID'
|
@@ -76,8 +79,8 @@ namespace :mat_views do
|
|
76
79
|
raise "No MatViews::MatViewDefinition found for id=#{args[:definition_id]}" unless defn
|
77
80
|
|
78
81
|
helpers.confirm!("Enqueue REFRESH for id=#{defn.id} (#{defn.name}), row_count_strategy=#{rcs}", skip: skip)
|
79
|
-
helpers.enqueue_refresh
|
80
|
-
helpers.logger.info("[mat_views] Enqueued RefreshViewJob for definition ##{defn.id} (#{defn.name}), row_count_strategy=#{rcs}")
|
82
|
+
helpers.enqueue_refresh(defn.id, rcs)
|
83
|
+
helpers.logger.info("[mat_views] Enqueued RefreshViewJob for definition ##{defn.id} (#{defn.name}), row_count_strategy=#{rcs}.")
|
81
84
|
end
|
82
85
|
|
83
86
|
desc 'Enqueue REFRESH jobs for ALL defined materialized views'
|
@@ -93,40 +96,43 @@ namespace :mat_views do
|
|
93
96
|
end
|
94
97
|
|
95
98
|
helpers.confirm!("Enqueue REFRESH for ALL (#{count}) views, row_count_strategy=#{rcs}", skip: skip)
|
96
|
-
scope.find_each { |defn| helpers.enqueue_refresh
|
99
|
+
scope.find_each { |defn| helpers.enqueue_refresh(defn.id, rcs) }
|
97
100
|
helpers.logger.info("[mat_views] Enqueued #{count} RefreshViewJob(s), row_count_strategy=#{rcs}.")
|
98
101
|
end
|
99
102
|
|
100
103
|
# ───────────── DELETE ─────────────
|
101
104
|
|
102
105
|
desc 'Enqueue a DELETE (DROP MATERIALIZED VIEW) for a specific view by its name (optionally schema-qualified)'
|
103
|
-
task :delete_by_name, %i[view_name cascade yes] => :environment do |_t, args|
|
106
|
+
task :delete_by_name, %i[view_name cascade row_count_strategy yes] => :environment do |_t, args|
|
104
107
|
cascade = helpers.parse_cascade?(args[:cascade])
|
108
|
+
rcs = helpers.parse_row_count_strategy(args[:row_count_strategy])
|
105
109
|
skip = helpers.skip_confirm?(args[:yes])
|
106
110
|
defn = helpers.find_definition_by_name!(args[:view_name])
|
107
111
|
|
108
|
-
helpers.confirm!("Enqueue DELETE for view=#{defn.name} (id=#{defn.id}), cascade=#{cascade}", skip: skip)
|
109
|
-
helpers.enqueue_delete
|
110
|
-
helpers.logger.info("[mat_views] Enqueued DeleteViewJob for definition ##{defn.id} (#{defn.name}), cascade=#{cascade}")
|
112
|
+
helpers.confirm!("Enqueue DELETE for view=#{defn.name} (id=#{defn.id}), cascade=#{cascade}, row_count_strategy=#{rcs}", skip: skip)
|
113
|
+
helpers.enqueue_delete(defn.id, cascade, rcs)
|
114
|
+
helpers.logger.info("[mat_views] Enqueued DeleteViewJob for definition ##{defn.id} (#{defn.name}), cascade=#{cascade}, row_count_strategy=#{rcs}.")
|
111
115
|
end
|
112
116
|
|
113
117
|
desc 'Enqueue a DELETE (DROP MATERIALIZED VIEW) for a specific view by its definition ID'
|
114
|
-
task :delete_by_id, %i[definition_id cascade yes] => :environment do |_t, args|
|
118
|
+
task :delete_by_id, %i[definition_id cascade row_count_strategy yes] => :environment do |_t, args|
|
115
119
|
raise 'mat_views:delete_by_id requires a definition_id parameter' if args[:definition_id].to_s.strip.empty?
|
116
120
|
|
121
|
+
rcs = helpers.parse_row_count_strategy(args[:row_count_strategy])
|
117
122
|
cascade = helpers.parse_cascade?(args[:cascade])
|
118
123
|
skip = helpers.skip_confirm?(args[:yes])
|
119
124
|
|
120
125
|
defn = MatViews::MatViewDefinition.find_by(id: args[:definition_id])
|
121
126
|
raise "No MatViews::MatViewDefinition found for id=#{args[:definition_id]}" unless defn
|
122
127
|
|
123
|
-
helpers.confirm!("Enqueue DELETE for id=#{defn.id} (#{defn.name}), cascade=#{cascade}", skip: skip)
|
124
|
-
helpers.enqueue_delete
|
125
|
-
helpers.logger.info("[mat_views] Enqueued DeleteViewJob for definition ##{defn.id} (#{defn.name}), cascade=#{cascade}")
|
128
|
+
helpers.confirm!("Enqueue DELETE for id=#{defn.id} (#{defn.name}), cascade=#{cascade}, row_count_strategy=#{rcs}", skip: skip)
|
129
|
+
helpers.enqueue_delete(defn.id, cascade, rcs)
|
130
|
+
helpers.logger.info("[mat_views] Enqueued DeleteViewJob for definition ##{defn.id} (#{defn.name}), cascade=#{cascade}, row_count_strategy=#{rcs}.")
|
126
131
|
end
|
127
132
|
|
128
133
|
desc 'Enqueue DELETE jobs for ALL defined materialized views'
|
129
|
-
task :delete_all, %i[cascade yes] => :environment do |_t, args|
|
134
|
+
task :delete_all, %i[cascade row_count_strategy yes] => :environment do |_t, args|
|
135
|
+
rcs = helpers.parse_row_count_strategy(args[:row_count_strategy])
|
130
136
|
cascade = helpers.parse_cascade?(args[:cascade])
|
131
137
|
skip = helpers.skip_confirm?(args[:yes])
|
132
138
|
|
@@ -137,9 +143,9 @@ namespace :mat_views do
|
|
137
143
|
next
|
138
144
|
end
|
139
145
|
|
140
|
-
helpers.confirm!("Enqueue DELETE for ALL (#{count}) views, cascade=#{cascade}", skip: skip)
|
141
|
-
scope.find_each { |defn| helpers.enqueue_delete
|
142
|
-
helpers.logger.info("[mat_views] Enqueued #{count} DeleteViewJob(s), cascade=#{cascade}.")
|
146
|
+
helpers.confirm!("Enqueue DELETE for ALL (#{count}) views, cascade=#{cascade}, row_count_strategy=#{rcs}", skip: skip)
|
147
|
+
scope.find_each { |defn| helpers.enqueue_delete(defn.id, cascade, rcs) }
|
148
|
+
helpers.logger.info("[mat_views] Enqueued #{count} DeleteViewJob(s), cascade=#{cascade}, row_count_strategy=#{rcs}.")
|
143
149
|
end
|
144
150
|
end
|
145
151
|
# rubocop:enable Metrics/BlockLength
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mat_views
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nitesh Purohit
|
@@ -49,6 +49,7 @@ files:
|
|
49
49
|
- app/models/mat_views/mat_view_definition.rb
|
50
50
|
- app/models/mat_views/mat_view_run.rb
|
51
51
|
- config/routes.rb
|
52
|
+
- lib/ext/exception.rb
|
52
53
|
- lib/generators/mat_views/install/install_generator.rb
|
53
54
|
- lib/generators/mat_views/install/templates/create_mat_view_definitions.rb
|
54
55
|
- lib/generators/mat_views/install/templates/create_mat_view_runs.rb
|
@@ -89,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
90
|
- !ruby/object:Gem::Version
|
90
91
|
version: '0'
|
91
92
|
requirements: []
|
92
|
-
rubygems_version: 3.6.
|
93
|
+
rubygems_version: 3.6.9
|
93
94
|
specification_version: 4
|
94
95
|
summary: Manage and refresh PostgreSQL materialized views in Rails
|
95
96
|
test_files: []
|