BigW Consortium Gitlab

20170518231126_fix_wrongly_renamed_routes.rb 3.05 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.

class FixWronglyRenamedRoutes < ActiveRecord::Migration
  include Gitlab::Database::RenameReservedPathsMigration::V1

  DOWNTIME = false

  disable_ddl_transaction!

  DISALLOWED_ROOT_PATHS = %w[
    -
    abuse_reports
    api
    autocomplete
    explore
    health_check
    import
    invites
    jwt
    koding
    member
    notification_settings
    oauth
    sent_notifications
    unicorn_test
    uploads
    users
  ]

  FIXED_PATHS = DISALLOWED_ROOT_PATHS.map { |p| "#{p}0" }

  class Route < Gitlab::Database::RenameReservedPathsMigration::V1::MigrationClasses::Route
    self.table_name = 'routes'
  end

  def routes
    @routes ||= Route.arel_table
  end

  def namespaces
    @namespaces ||= Arel::Table.new(:namespaces)
  end

  def wildcard_collection(collection)
    collection.map { |word| "#{word}%" }
  end

  # The routes that got incorrectly renamed before, still have a namespace that
  # contains the correct path.
  # This query fetches all rows from the `routes` table that meet the following
  # conditions using `api` as an example:
  # - route.path ILIKE `api0%`
  # - route.source_type = `Namespace`
  # - namespace.parent_id IS NULL
  # - namespace.path ILIKE `api%`
  # - NOT(namespace.path ILIKE `api0%`)
  # This gives us all root-routes, that were renamed, but their namespace was not.
  #
  def wrongly_renamed
    Route.joins("INNER JOIN namespaces ON routes.source_id = namespaces.id")
      .where(
        routes[:source_type].eq('Namespace')
          .and(namespaces[:parent_id].eq(nil))
      )
      .where(namespaces[:path].matches_any(wildcard_collection(DISALLOWED_ROOT_PATHS)))
      .where.not(namespaces[:path].matches_any(wildcard_collection(FIXED_PATHS)))
      .where(routes[:path].matches_any(wildcard_collection(FIXED_PATHS)))
  end

  # Using the query above, we just fetch the `route.path` & the `namespace.path`
  # `route.path` is the part of the route that is now incorrect
  # `namespace.path` is what it should be
  # We can use `route.path` to find all the namespaces that need to be fixed
  # And we can use `namespace.path` to apply the correct name.
  #
  def paths_and_corrections
    connection.select_all(
      wrongly_renamed.select(routes[:path], namespaces[:path].as('namespace_path')).to_sql
    )
  end

  # This can be used to limit the `update_in_batches` call to all routes for a
  # single namespace, note the `/` that's what went wrong in the initial migration.
  #
  def routes_in_namespace_query(namespace)
    routes[:path].matches_any([namespace, "#{namespace}/%"])
  end

  def up
    paths_and_corrections.each do |root_namespace|
      wrong_path = root_namespace['path']
      correct_path = root_namespace['namespace_path']
      replace_statement = replace_sql(Route.arel_table[:path], wrong_path, correct_path)

      update_column_in_batches(:routes, :path, replace_statement) do |table, query|
        query.where(routes_in_namespace_query(wrong_path))
      end
    end
  end

  def down
  end
end