CartoDB/cartodb20

View on GitHub
app/models/visualization/support_tables.rb

Summary

Maintainability
A
25 mins
Test Coverage
require_relative './member'

module CartoDB
  module Visualization
    class SupportTables

      def initialize(database_connection, config={})
        @database = database_connection
        @parent_id = config.fetch(:parent_id, nil)
        @parent_kind = config.fetch(:parent_kind, nil)
        @public_user_roles_list = config.fetch(:public_user_roles)
        @tables_list = nil
      end

      def reset
        @tables_list = nil
      end

      # Only intended to be used if from the Visualization Relator (who will set the parent)
      # PG12_DEPRECATED in postgis 3+
      def load_actual_list(parent_name=nil)
        return [] if @parent_id.nil? || @parent_kind != Visualization::Member::KIND_RASTER ||
          !@database.table_exists?('raster_overviews')
        parent = Visualization::Member.new(id:@parent_id).fetch
        table_data = @database.fetch(%Q{
          SELECT o_table_catalog AS catalog, o_table_schema AS schema, o_table_name AS name
          FROM raster_overviews
          WHERE r_table_catalog = '#{parent.user.database_name}'
          AND r_table_schema = '#{parent.user.database_schema}'
          AND r_table_name = '#{parent_name.nil? ? parent.name : parent_name}'
        }).all

        table_data.nil? ? [] : table_data
      end

      def delete_all
        tables.each { |table|
          @database.execute(%Q{
            DROP TABLE "#{table[:schema]}"."#{table[:name]}"
          })
        }
      end

      # @param existing_parent_name String
      # @param new_parent_name String
      # @param recreate_relations Bool If true will recreate constraints and permissions from overviews
      # @param seek_parent_name String|nil If specified, seeking of tables will be performed using this name
      def rename(existing_parent_name, new_parent_name, recreate_relations=true, seek_parent_name=nil)
        begin
          schema = nil
          support_tables_new_names = []
          tables_list = tables(seek_parent_name)
          tables_list.each { |item|
            schema = item[:schema]
            new_support_table_name = item[:name].dup
            # CONVENTION: support_tables will always end in "_tablename", so we substitute using parent name
            new_support_table_name.slice!(-existing_parent_name.length, existing_parent_name.length)
            new_support_table_name = "#{new_support_table_name}#{new_parent_name}"

            @database.execute(%Q{
              ALTER TABLE "#{item[:schema]}"."#{item[:name]}" RENAME TO "#{new_support_table_name}"
            })

            support_tables_new_names.push(new_support_table_name)
          }
          renamed = true
        rescue StandardError
          renamed = false
        end

        if renamed && recreate_relations
          support_tables_new_names.each { |table_name|
            recreate_raster_constraints_if_exists(table_name, new_parent_name, schema)
            update_permissions(table_name, @public_user_roles_list, schema)
          }
        end

        { success: renamed, names: support_tables_new_names }
      end

      def change_schema(new_schema, parent_table_name)
        tables.each { |item|
          @database.execute(%Q{
            ALTER TABLE "#{item[:schema]}"."#{item[:name]}"
            SET SCHEMA "#{new_schema}"
          })
          # Constraints are not automatically updated upon schema change or table renaming
          recreate_raster_constraints_if_exists(item[:name], parent_table_name, new_schema)
          update_permissions(item[:name], @public_user_roles_list, new_schema)
        }
      end

      # For import purposes
      # @param new_list Array [ { :schema, :name } ]
      def tables=(new_list)
        @tables_list = new_list
      end


      private

      def tables(seek_parent_name=nil)
        @tables_list ||= load_actual_list(seek_parent_name)
      end

      def update_permissions(overview_table_name, db_roles_list, schema)
        overviews = @database.fetch(%Q{
          SELECT o_table_name, o_table_schema
          FROM raster_overviews
          WHERE o_table_name = '#{overview_table_name}'
          AND o_table_schema = '#{schema}'
        }).first
        return if overviews.nil?

        @database.transaction do
          db_roles_list.each { |role_name|
            @database.execute(%Q{
              GRANT SELECT ON TABLE "#{overviews[:o_table_schema]}"."#{overviews[:o_table_name]}" TO "#{role_name}"
            })
          }
        end
      end

      # @see http://postgis.net/docs/manual-dev/using_raster_dataman.html#RT_Raster_Overviews
      def recreate_raster_constraints_if_exists(overview_table_name, raster_table_name, schema)
        constraint = @database.fetch(%Q{
          SELECT o_table_name, o_raster_column, r_table_name, r_raster_column, overview_factor
          FROM raster_overviews
          WHERE o_table_name = '#{overview_table_name}'
          AND o_table_schema = '#{schema}'
        }).first
        return if constraint.nil?

        @database.transaction do
          # @see http://postgis.net/docs/RT_DropOverviewConstraints.html
          @database.execute(%Q{
            SELECT DropOverviewConstraints('#{schema}', '#{constraint[:o_table_name]}',
                                           '#{constraint[:o_raster_column]}')
          })
          # @see http://postgis.net/docs/manual-dev/RT_AddOverviewConstraints.html
          @database.execute(%Q{
            SELECT AddOverviewConstraints('#{schema}', '#{constraint[:o_table_name]}',
                                          '#{constraint[:o_raster_column]}', '#{schema}', '#{raster_table_name}',
                                          '#{constraint[:r_raster_column]}', #{constraint[:overview_factor]});
          })
        end
      end

    end
  end
end