summaryrefslogtreecommitdiff
path: root/lib/gitlab/database/async_indexes/migration_helpers.rb
blob: e9846dd4e8570b0d14a2e5d79b5a97f0a373d436 (plain)
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
# frozen_string_literal: true

module Gitlab
  module Database
    module AsyncIndexes
      module MigrationHelpers
        def unprepare_async_index(table_name, column_name, **options)
          Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!

          return unless async_index_creation_available?

          index_name = options[:name] || index_name(table_name, column_name)

          raise 'Specifying index name is mandatory - specify name: argument' unless index_name

          unprepare_async_index_by_name(table_name, index_name)
        end

        def unprepare_async_index_by_name(table_name, index_name, **options)
          Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!

          return unless async_index_creation_available?

          PostgresAsyncIndex.find_by(name: index_name).try do |async_index|
            async_index.destroy
          end
        end

        # Prepares an index for asynchronous creation.
        #
        # Stores the index information in the postgres_async_indexes table to be created later. The
        # index will be always be created CONCURRENTLY, so that option does not need to be given.
        # If an existing asynchronous definition exists with the same name, the existing entry will be
        # updated with the new definition.
        #
        # If the requested index has already been created, it is not stored in the table for
        # asynchronous creation.
        def prepare_async_index(table_name, column_name, **options)
          Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.require_ddl_mode!

          return unless async_index_creation_available?

          index_name = options[:name] || index_name(table_name, column_name)

          raise 'Specifying index name is mandatory - specify name: argument' unless index_name

          options = options.merge({ algorithm: :concurrently })

          if index_exists?(table_name, column_name, **options)
            Gitlab::AppLogger.warn(
              message: 'Index not prepared because it already exists',
              table_name: table_name,
              index_name: index_name)

            return
          end

          index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)

          create_index = ActiveRecord::ConnectionAdapters::CreateIndexDefinition.new(index, algorithm, if_not_exists)
          schema_creation = ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaCreation.new(ApplicationRecord.connection)
          definition = schema_creation.accept(create_index)

          async_index = PostgresAsyncIndex.find_or_create_by!(name: index_name) do |rec|
            rec.table_name = table_name
            rec.definition = definition
          end

          async_index.definition = definition
          async_index.save! # No-op if definition is not changed

          Gitlab::AppLogger.info(
            message: 'Prepared index for async creation',
            table_name: async_index.table_name,
            index_name: async_index.name)

          async_index
        end

        def async_index_creation_available?
          connection.table_exists?(:postgres_async_indexes)
        end
      end
    end
  end
end