2021-10-27 15:23:28 +05:30
# frozen_string_literal: true
module Gitlab
module Database
module AsyncIndexes
module MigrationHelpers
def unprepare_async_index ( table_name , column_name , ** options )
2022-05-07 20:08:51 +05:30
Gitlab :: Database :: QueryAnalyzers :: RestrictAllowedSchemas . require_ddl_mode!
2021-10-27 15:23:28 +05:30
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 )
2022-05-07 20:08:51 +05:30
Gitlab :: Database :: QueryAnalyzers :: RestrictAllowedSchemas . require_ddl_mode!
2021-10-27 15:23:28 +05:30
return unless async_index_creation_available?
PostgresAsyncIndex . find_by ( name : index_name ) . try do | async_index |
2023-04-23 21:23:45 +05:30
async_index . destroy!
2021-10-27 15:23:28 +05:30
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 )
2022-05-07 20:08:51 +05:30
Gitlab :: Database :: QueryAnalyzers :: RestrictAllowedSchemas . require_ddl_mode!
2021-10-27 15:23:28 +05:30
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 )
2021-11-11 11:23:49 +05:30
async_index = PostgresAsyncIndex . find_or_create_by! ( name : index_name ) do | rec |
2021-10-27 15:23:28 +05:30
rec . table_name = table_name
rec . definition = definition
end
2021-11-11 11:23:49 +05:30
async_index . definition = definition
async_index . save! # No-op if definition is not changed
2021-10-27 15:23:28 +05:30
Gitlab :: AppLogger . info (
message : 'Prepared index for async creation' ,
table_name : async_index . table_name ,
index_name : async_index . name )
async_index
end
2023-06-20 00:43:36 +05:30
def prepare_async_index_from_sql ( definition )
Gitlab :: Database :: QueryAnalyzers :: RestrictAllowedSchemas . require_ddl_mode!
return unless async_index_creation_available?
table_name , index_name = extract_table_and_index_names_from_concurrent_index! ( definition )
if index_name_exists? ( table_name , index_name )
Gitlab :: AppLogger . warn (
message : 'Index not prepared because it already exists' ,
table_name : table_name ,
index_name : index_name )
return
end
async_index = Gitlab :: Database :: AsyncIndexes :: PostgresAsyncIndex . find_or_create_by! ( name : index_name ) do | rec |
rec . table_name = table_name
rec . definition = definition
end
Gitlab :: AppLogger . info (
message : 'Prepared index for async creation' ,
table_name : async_index . table_name ,
index_name : async_index . name )
async_index
end
2022-08-27 11:52:29 +05:30
# Prepares an index for asynchronous destruction.
#
# Stores the index information in the postgres_async_indexes table to be removed later. The
# index will be always be removed CONCURRENTLY, so that option does not need to be given.
#
# If the requested index has already been removed, it is not stored in the table for
# asynchronous destruction.
def prepare_async_index_removal ( table_name , column_name , options = { } )
index_name = options . fetch ( :name )
raise 'prepare_async_index_removal must get an index name defined' if index_name . blank?
unless index_exists? ( table_name , column_name , ** options )
Gitlab :: AppLogger . warn " Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{ table_name } , index_name: #{ index_name } "
return
end
definition = " DROP INDEX CONCURRENTLY #{ quote_column_name ( index_name ) } "
async_index = PostgresAsyncIndex . find_or_create_by! ( name : index_name ) do | rec |
rec . table_name = table_name
rec . definition = definition
end
Gitlab :: AppLogger . info (
message : 'Prepared index for async destruction' ,
table_name : async_index . table_name ,
index_name : async_index . name
)
async_index
end
2021-10-27 15:23:28 +05:30
def async_index_creation_available?
2023-06-20 00:43:36 +05:30
table_exists? ( :postgres_async_indexes )
end
private
delegate :table_exists? , to : :connection , private : true
def extract_table_and_index_names_from_concurrent_index! ( definition )
statement = index_statement_from! ( definition )
raise 'Index statement not found!' unless statement
raise 'Index must be created concurrently!' unless statement . concurrent
raise 'Table does not exist!' unless table_exists? ( statement . relation . relname )
[ statement . relation . relname , statement . idxname ]
end
# This raises `PgQuery::ParseError` if the given statement
# is syntactically incorrect, therefore, validates that the
# index definition is correct.
def index_statement_from! ( definition )
parsed_query = PgQuery . parse ( definition )
parsed_query . tree . stmts [ 0 ] . stmt . index_stmt
2021-10-27 15:23:28 +05:30
end
end
end
end
end