CartoDB/cartodb20

View on GitHub
lib/carto/valid_table_name_proposer.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'active_support/core_ext'
require_relative 'db/sanitize.rb'

module Carto
  class ValidTableNameProposer
    include ::LoggerHelper

    DEFAULT_SEPARATOR = '_'.freeze
    DEFAULT_TABLE_NAME = 'untitled_table'.freeze
    MAX_RENAME_RETRIES = 10000
    NON_COLLISIONABLE_STRING_LENGTH = 61

    def propose_valid_table_name(contendent = DEFAULT_TABLE_NAME.dup, taken_names:)
      contendent = DEFAULT_TABLE_NAME.dup unless contendent.present?

      sanitized_contendent = Carto::DB::Sanitize.sanitize_identifier(contendent)
      used_table_names = taken_names +
                         Carto::DB::Sanitize::SYSTEM_TABLE_NAMES +
                         Carto::DB::Sanitize::RESERVED_TABLE_NAMES

      find_unused_name_with_prefix(used_table_names, sanitized_contendent)
    end

    private

    def find_unused_name_with_prefix(names, prefix, separator: DEFAULT_SEPARATOR)
      proposal = prefix

      (1..MAX_RENAME_RETRIES).each do |appendix|
        # We exclude the proposal either if the taken names array already have the proposal, or if the taken names
        # array contains a string with the first 62 chars equal to the proposal. With this we avoid typnames collision
        # when moving schemas
        return proposal unless names.any? { |name| name_can_have_typname_collision(name, proposal) }

        proposal = Carto::DB::Sanitize.append_with_truncate_and_sanitize(prefix, "#{separator}#{appendix}")
      end

      log_error(message: 'Physical tables: Out of rename retries', table: { name: prefix })

      raise "Out of retries (#{MAX_RENAME_RETRIES}) renaming #{proposal}"
    end

    def name_can_have_typname_collision(name, proposal)
      name == proposal || name[0..NON_COLLISIONABLE_STRING_LENGTH] == proposal[0..NON_COLLISIONABLE_STRING_LENGTH]
    end
  end
end