Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only REFRESH CONCURRENTLY if view populated? #429

Merged
merged 1 commit into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions lib/scenic/adapters/postgres.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module Scenic
module Adapters
# An adapter for managing Postgres views.
#
# These methods are used interally by Scenic and are not intended for direct
# These methods are used internally by Scenic and are not intended for direct
# use. Methods that alter database schema are intended to be called via
# {Statements}, while {#refresh_materialized_view} is called via
# {Scenic.database}.
Expand Down Expand Up @@ -124,7 +124,7 @@ def drop_view(name)
# @param sql_definition The SQL schema that defines the materialized view.
# @param no_data [Boolean] Default: false. Set to true to create
# materialized view without running the associated query. You will need
# to perform a non-concurrent refresh to populate with data.
# to perform a refresh to populate with data.
#
# This is typically called in a migration via {Statements#create_view}.
#
Expand Down Expand Up @@ -154,7 +154,7 @@ def create_materialized_view(name, sql_definition, no_data: false)
# @param sql_definition The SQL schema for the updated view.
# @param no_data [Boolean] Default: false. Set to true to create
# materialized view without running the associated query. You will need
# to perform a non-concurrent refresh to populate with data.
# to perform a refresh to populate with data.
#
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
# in use does not support materialized views.
Expand Down Expand Up @@ -193,7 +193,10 @@ def drop_materialized_view(name)
# refreshed without locking the view for select but requires that the
# table have at least one unique index that covers all rows. Attempts to
# refresh concurrently without a unique index will raise a descriptive
# error.
# error. This option is ignored if the view is not populated, as it
# would cause an error to be raised by Postgres. Default: false.
# @param cascade [Boolean] Whether to refresh dependent materialized
# views. Default: false.
#
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
# in use does not support materialized views.
Expand All @@ -205,26 +208,29 @@ def drop_materialized_view(name)
# Scenic.database.refresh_materialized_view(:search_results)
# @example Concurrent refresh
# Scenic.database.refresh_materialized_view(:posts, concurrently: true)
# @example Cascade refresh
# Scenic.database.refresh_materialized_view(:posts, cascade: true)
#
# @return [void]
def refresh_materialized_view(name, concurrently: false, cascade: false)
raise_unless_materialized_views_supported

if concurrently
raise_unless_concurrent_refresh_supported
end

if cascade
refresh_dependencies_for(name, concurrently: concurrently)
end

if concurrently
raise_unless_concurrent_refresh_supported
if concurrently && populated?(name)
execute "REFRESH MATERIALIZED VIEW CONCURRENTLY #{quote_table_name(name)};"
else
execute "REFRESH MATERIALIZED VIEW #{quote_table_name(name)};"
end
end

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

schemaless_name = name.split(".").last
schemaless_name = name.to_s.split(".").last

sql = "SELECT relispopulated FROM pg_class WHERE relname = '#{schemaless_name}'"
relations = execute(sql)
Expand Down
8 changes: 8 additions & 0 deletions spec/scenic/adapters/postgres_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ module Adapters
adapter.refresh_materialized_view(:tests, concurrently: true)
}.to raise_error e
end

it "falls back to non-concurrent refresh if not populated" do
adapter = Postgres.new
adapter.create_materialized_view(:testing, "SELECT unnest('{1, 2}'::int[])", no_data: true)

expect { adapter.refresh_materialized_view(:testing, concurrently: true) }
.not_to raise_error
end
end
end

Expand Down
Loading