Skip to content

Commit a828baa

Browse files
drnicderekpriorcalebhearth
committed
Only refresh concurrently if view is populated
This implementation will still error if you ask for a concurrent refresh of a non-populated view if your database does not support concurrent refreshes. I'm happy with this because the user has asked for a thing that we know cannot succeed, even under the right circumstances, and we should tell them that., and we should tell them that. Co-authored-by: Derek Prior <[email protected]> Co-authored-by: Caleb Hearth <[email protected]>
1 parent 1b70bf7 commit a828baa

File tree

2 files changed

+24
-10
lines changed

2 files changed

+24
-10
lines changed

lib/scenic/adapters/postgres.rb

+16-10
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module Scenic
1414
module Adapters
1515
# An adapter for managing Postgres views.
1616
#
17-
# These methods are used interally by Scenic and are not intended for direct
17+
# These methods are used internally by Scenic and are not intended for direct
1818
# use. Methods that alter database schema are intended to be called via
1919
# {Statements}, while {#refresh_materialized_view} is called via
2020
# {Scenic.database}.
@@ -124,7 +124,7 @@ def drop_view(name)
124124
# @param sql_definition The SQL schema that defines the materialized view.
125125
# @param no_data [Boolean] Default: false. Set to true to create
126126
# materialized view without running the associated query. You will need
127-
# to perform a non-concurrent refresh to populate with data.
127+
# to perform a refresh to populate with data.
128128
#
129129
# This is typically called in a migration via {Statements#create_view}.
130130
#
@@ -154,7 +154,7 @@ def create_materialized_view(name, sql_definition, no_data: false)
154154
# @param sql_definition The SQL schema for the updated view.
155155
# @param no_data [Boolean] Default: false. Set to true to create
156156
# materialized view without running the associated query. You will need
157-
# to perform a non-concurrent refresh to populate with data.
157+
# to perform a refresh to populate with data.
158158
#
159159
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
160160
# in use does not support materialized views.
@@ -193,7 +193,10 @@ def drop_materialized_view(name)
193193
# refreshed without locking the view for select but requires that the
194194
# table have at least one unique index that covers all rows. Attempts to
195195
# refresh concurrently without a unique index will raise a descriptive
196-
# error.
196+
# error. This option is ignored if the view is not populated, as it
197+
# would cause an error to be raised by Postgres. Default: false.
198+
# @param cascade [Boolean] Whether to refresh dependent materialized
199+
# views. Default: false.
197200
#
198201
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
199202
# in use does not support materialized views.
@@ -205,26 +208,29 @@ def drop_materialized_view(name)
205208
# Scenic.database.refresh_materialized_view(:search_results)
206209
# @example Concurrent refresh
207210
# Scenic.database.refresh_materialized_view(:posts, concurrently: true)
211+
# @example Cascade refresh
212+
# Scenic.database.refresh_materialized_view(:posts, cascade: true)
208213
#
209214
# @return [void]
210215
def refresh_materialized_view(name, concurrently: false, cascade: false)
211216
raise_unless_materialized_views_supported
212217

218+
if concurrently
219+
raise_unless_concurrent_refresh_supported
220+
end
221+
213222
if cascade
214223
refresh_dependencies_for(name, concurrently: concurrently)
215224
end
216225

217-
if concurrently
218-
raise_unless_concurrent_refresh_supported
226+
if concurrently && populated?(name)
219227
execute "REFRESH MATERIALIZED VIEW CONCURRENTLY #{quote_table_name(name)};"
220228
else
221229
execute "REFRESH MATERIALIZED VIEW #{quote_table_name(name)};"
222230
end
223231
end
224232

225-
# True if supplied relation name is populated. Useful for checking the
226-
# state of materialized views which may error if created `WITH NO DATA`
227-
# and used before they are refreshed. True for all other relation types.
233+
# True if supplied relation name is populated.
228234
#
229235
# @param name The name of the relation
230236
#
@@ -235,7 +241,7 @@ def refresh_materialized_view(name, concurrently: false, cascade: false)
235241
def populated?(name)
236242
raise_unless_materialized_views_supported
237243

238-
schemaless_name = name.split(".").last
244+
schemaless_name = name.to_s.split(".").last
239245

240246
sql = "SELECT relispopulated FROM pg_class WHERE relname = '#{schemaless_name}'"
241247
relations = execute(sql)

spec/scenic/adapters/postgres_spec.rb

+8
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ module Adapters
149149
adapter.refresh_materialized_view(:tests, concurrently: true)
150150
}.to raise_error e
151151
end
152+
153+
it "falls back to non-concurrent refresh if not populated" do
154+
adapter = Postgres.new
155+
adapter.create_materialized_view(:testing, "SELECT unnest('{1, 2}'::int[])", no_data: true)
156+
157+
expect { adapter.refresh_materialized_view(:testing, concurrently: true) }
158+
.not_to raise_error
159+
end
152160
end
153161
end
154162

0 commit comments

Comments
 (0)