From ae377cef02968c5e604a65d9756aa8bc0a1d851d Mon Sep 17 00:00:00 2001 From: Simon Claessens Date: Thu, 21 Jan 2021 12:13:43 +0100 Subject: [PATCH] Implement the rename_view method --- lib/scenic/adapters/postgres.rb | 45 ++++++++++++++++++++++++++++++++- lib/scenic/command_recorder.rb | 27 ++++++++++---------- lib/scenic/statements.rb | 33 ++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/lib/scenic/adapters/postgres.rb b/lib/scenic/adapters/postgres.rb index 2c94e015..e45ab2b0 100644 --- a/lib/scenic/adapters/postgres.rb +++ b/lib/scenic/adapters/postgres.rb @@ -118,6 +118,21 @@ def drop_view(name) execute "DROP VIEW #{quote_table_name(name)};" end + # Renames a view in the database + # + # This is typically called in a migration via {Statements#rename_view}. + # + # @param from_name The previous name of the view to rename. + # @param to_name The next name of the view to rename. + # + # @return [void] + def rename_view(from_name, to_name) + execute <<~SQL + ALTER VIEW #{quote_table_name(from_name)} + RENAME TO #{quote_table_name(to_name)}; + SQL + end + # Creates a materialized view in the database # # @param name The name of the materialized view to create @@ -183,6 +198,34 @@ def drop_materialized_view(name) execute "DROP MATERIALIZED VIEW #{quote_table_name(name)};" end + # Renames a materialized view in the database + # + # This is typically called in a migration via {Statements#rename_view}. + # + # @param from_name The previous name of the materialized view to rename. + # @param to_name The next name of the materialized view to rename. + # @raise [MaterializedViewsNotSupportedError] if the version of Postgres + # in use does not support materialized views. + # + # @return [void] + def rename_materialized_view(from_name, to_name, rename_indexes: false) + raise_unless_materialized_views_supported + execute <<~SQL + ALTER MATERIALIZED VIEW #{quote_table_name(from_name)} + RENAME TO #{quote_table_name(to_name)}; + SQL + + if rename_indexes + Indexes.new(connection: connection) + .on(to_name) + .map(&:index_name) + .select { |name| name.match?(from_name) } + .each do |name| + rename_index to_name, name, name.sub(from_name, to_name) + end + end + end + # Refreshes a materialized view from its SQL schema. # # This is typically called from application code via {Scenic.database}. @@ -225,7 +268,7 @@ def refresh_materialized_view(name, concurrently: false, cascade: false) private attr_reader :connectable - delegate :execute, :quote_table_name, to: :connection + delegate :execute, :quote_table_name, :rename_index, to: :connection def connection Connection.new(connectable.connection) diff --git a/lib/scenic/command_recorder.rb b/lib/scenic/command_recorder.rb index 2638d455..f18173b4 100644 --- a/lib/scenic/command_recorder.rb +++ b/lib/scenic/command_recorder.rb @@ -3,20 +3,9 @@ module Scenic # @api private module CommandRecorder - def create_view(*args) - record(:create_view, args) - end - - def drop_view(*args) - record(:drop_view, args) - end - - def update_view(*args) - record(:update_view, args) - end - - def replace_view(*args) - record(:replace_view, args) + METHODS = %i[create_view drop_view update_view replace_view rename_view] + METHODS.each do |method| + define_method(method) { |*args| record(method, args) } end def invert_create_view(args) @@ -36,6 +25,16 @@ def invert_replace_view(args) perform_scenic_inversion(:replace_view, args) end + def invert_rename_view(args) + options = args.extract_options! + old_name, new_name = args + + args = [new_name, old_name] + args << options unless options.empty? + + [:rename_view, args] + end + private def perform_scenic_inversion(method, args) diff --git a/lib/scenic/statements.rb b/lib/scenic/statements.rb index 84bf24b0..3d9c1177 100644 --- a/lib/scenic/statements.rb +++ b/lib/scenic/statements.rb @@ -146,6 +146,31 @@ def replace_view(name, version: nil, revert_to_version: nil, materialized: false Scenic.database.replace_view(name, sql_definition) end + # Rename a database view by name. + # + # @param from_name [String, Symbol] The previous name of the database view. + # @param from_name [String, Symbol] The next name of the database view. + # @param materialized [Boolean, Hash] True if updating a materialized view. + # Set to { rename_indexes: true } to rename materialized view indexes + # by substituing in their name the previous view name + # to the next view name. Defaults to false. + # @return The database response from executing the rename statement. + # + # @example Rename a view + # drop_view(:engggggement_reports, :engagement_reports) + # + def rename_view(from_name, to_name, materialized: false) + if materialized + Scenic.database.rename_materialized_view( + from_name, + to_name, + rename_indexes: rename_indexes(materialized), + ) + else + Scenic.database.rename_view(from_name, to_name) + end + end + private def definition(name, version) @@ -159,5 +184,13 @@ def no_data(materialized) false end end + + def rename_indexes(materialized) + if materialized.is_a?(Hash) + materialized.fetch(:rename_indexes, false) + else + false + end + end end end