Commit 08c4349d authored by Furkan Ayhan's avatar Furkan Ayhan

Merge branch 'fix-lfk-from-table-to-table-definition' into 'master'

Fix LFK foreign key definition

See merge request gitlab-org/gitlab!75626
parents b00b43e5 50eb9786
...@@ -16,7 +16,7 @@ module LooseForeignKeys ...@@ -16,7 +16,7 @@ module LooseForeignKeys
def execute def execute
result = connection.execute(build_query) result = connection.execute(build_query)
{ affected_rows: result.cmd_tuples, table: loose_foreign_key_definition.to_table } { affected_rows: result.cmd_tuples, table: loose_foreign_key_definition.from_table }
end end
def async_delete? def async_delete?
...@@ -48,15 +48,15 @@ module LooseForeignKeys ...@@ -48,15 +48,15 @@ module LooseForeignKeys
end end
def arel_table def arel_table
@arel_table ||= Arel::Table.new(loose_foreign_key_definition.to_table) @arel_table ||= Arel::Table.new(loose_foreign_key_definition.from_table)
end end
def primary_keys def primary_keys
@primary_keys ||= connection.primary_keys(loose_foreign_key_definition.to_table).map { |key| arel_table[key] } @primary_keys ||= connection.primary_keys(loose_foreign_key_definition.from_table).map { |key| arel_table[key] }
end end
def quoted_table_name def quoted_table_name
@quoted_table_name ||= Arel.sql(connection.quote_table_name(loose_foreign_key_definition.to_table)) @quoted_table_name ||= Arel.sql(connection.quote_table_name(loose_foreign_key_definition.from_table))
end end
def delete_query def delete_query
......
...@@ -62,28 +62,28 @@ following information: ...@@ -62,28 +62,28 @@ following information:
- The data cleanup method (`async_delete` or `async_nullify`) - The data cleanup method (`async_delete` or `async_nullify`)
The YAML file is located at `lib/gitlab/database/gitlab_loose_foreign_keys.yml`. The file groups The YAML file is located at `lib/gitlab/database/gitlab_loose_foreign_keys.yml`. The file groups
foreign key definitions by the name of the parent table. The parent table can have multiple loose foreign key definitions by the name of the child table. The child table can have multiple loose
foreign key definitions, therefore we store them as an array. foreign key definitions, therefore we store them as an array.
Example definition: Example definition:
```yaml ```yaml
projects: ci_pipelines:
- to_table: ci_pipelines - table: projects
column: project_id column: project_id
on_delete: async_delete on_delete: async_delete
``` ```
If the `projects` key is already present in the YAML file, then a new entry can be added If the `ci_pipelines` key is already present in the YAML file, then a new entry can be added
to the array: to the array:
```yaml ```yaml
projects: ci_pipelines:
- to_table: ci_pipelines - table: projects
column: project_id column: project_id
on_delete: async_delete on_delete: async_delete
- to_table: another_table - table: another_table
column: project_id column: another_id
on_delete: :async_nullify on_delete: :async_nullify
``` ```
......
chat_names: ci_pipeline_chat_data:
- to_table: ci_pipeline_chat_data - table: chat_names
column: chat_name_id column: chat_name_id
on_delete: async_delete on_delete: async_delete
ci_builds: dast_scanner_profiles_builds:
- to_table: dast_site_profiles_builds - table: ci_builds
column: ci_build_id column: ci_build_id
on_delete: async_delete on_delete: async_delete
- to_table: dast_scanner_profiles_builds dast_scanner_profiles_builds:
- table: ci_builds
column: ci_build_id column: ci_build_id
on_delete: async_delete on_delete: async_delete
ci_pipelines: dast_profiles_pipelines:
- to_table: dast_profiles_pipelines - table: ci_pipelines
column: ci_pipeline_id column: ci_pipeline_id
on_delete: async_delete on_delete: async_delete
ci_runners: clusters_applications_runners:
- to_table: clusters_applications_runners - table: ci_runners
column: runner_id column: runner_id
on_delete: async_nullify on_delete: async_nullify
...@@ -4,25 +4,25 @@ module Gitlab ...@@ -4,25 +4,25 @@ module Gitlab
module Database module Database
module LooseForeignKeys module LooseForeignKeys
def self.definitions_by_table def self.definitions_by_table
@definitions_by_table ||= definitions.group_by(&:from_table).with_indifferent_access.freeze @definitions_by_table ||= definitions.group_by(&:to_table).with_indifferent_access.freeze
end end
def self.definitions def self.definitions
@definitions ||= loose_foreign_keys_yaml.flat_map do |parent_table_name, configs| @definitions ||= loose_foreign_keys_yaml.flat_map do |child_table_name, configs|
configs.map { |config| build_definition(parent_table_name, config) } configs.map { |config| build_definition(child_table_name, config) }
end.freeze end.freeze
end end
def self.build_definition(parent_table_name, config) def self.build_definition(child_table_name, config)
to_table = config.fetch('to_table') parent_table_name = config.fetch('table')
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
child_table_name,
parent_table_name, parent_table_name,
to_table,
{ {
column: config.fetch('column'), column: config.fetch('column'),
on_delete: config.fetch('on_delete').to_sym, on_delete: config.fetch('on_delete').to_sym,
gitlab_schema: GitlabSchema.table_schema(to_table) gitlab_schema: GitlabSchema.table_schema(child_table_name)
} }
) )
end end
......
...@@ -29,7 +29,6 @@ RSpec.describe 'Database schema' do ...@@ -29,7 +29,6 @@ RSpec.describe 'Database schema' do
ci_builds: %w[erased_by_id runner_id trigger_request_id user_id], ci_builds: %w[erased_by_id runner_id trigger_request_id user_id],
ci_namespace_monthly_usages: %w[namespace_id], ci_namespace_monthly_usages: %w[namespace_id],
ci_pipelines: %w[user_id], ci_pipelines: %w[user_id],
ci_pipeline_chat_data: %w[chat_name_id], # it uses the loose foreign key featue
ci_runner_projects: %w[runner_id], ci_runner_projects: %w[runner_id],
ci_trigger_requests: %w[commit_id], ci_trigger_requests: %w[commit_id],
cluster_providers_aws: %w[security_group_id vpc_id access_key_id], cluster_providers_aws: %w[security_group_id vpc_id access_key_id],
...@@ -102,6 +101,8 @@ RSpec.describe 'Database schema' do ...@@ -102,6 +101,8 @@ RSpec.describe 'Database schema' do
let(:indexes) { connection.indexes(table) } let(:indexes) { connection.indexes(table) }
let(:columns) { connection.columns(table) } let(:columns) { connection.columns(table) }
let(:foreign_keys) { connection.foreign_keys(table) } let(:foreign_keys) { connection.foreign_keys(table) }
let(:loose_foreign_keys) { Gitlab::Database::LooseForeignKeys.definitions.group_by(&:from_table).fetch(table, []) }
let(:all_foreign_keys) { foreign_keys + loose_foreign_keys }
# take the first column in case we're using a composite primary key # take the first column in case we're using a composite primary key
let(:primary_key_column) { Array(connection.primary_key(table)).first } let(:primary_key_column) { Array(connection.primary_key(table)).first }
...@@ -114,7 +115,7 @@ RSpec.describe 'Database schema' do ...@@ -114,7 +115,7 @@ RSpec.describe 'Database schema' do
columns = columns.split(',') if columns.is_a?(String) columns = columns.split(',') if columns.is_a?(String)
columns.first.chomp columns.first.chomp
end end
foreign_keys_columns = foreign_keys.map(&:column) foreign_keys_columns = all_foreign_keys.map(&:column)
# Add the primary key column to the list of indexed columns because # Add the primary key column to the list of indexed columns because
# postgres and mysql both automatically create an index on the primary # postgres and mysql both automatically create an index on the primary
...@@ -129,7 +130,7 @@ RSpec.describe 'Database schema' do ...@@ -129,7 +130,7 @@ RSpec.describe 'Database schema' do
context 'columns ending with _id' do context 'columns ending with _id' do
let(:column_names) { columns.map(&:name) } let(:column_names) { columns.map(&:name) }
let(:column_names_with_id) { column_names.select { |column_name| column_name.ends_with?('_id') } } let(:column_names_with_id) { column_names.select { |column_name| column_name.ends_with?('_id') } }
let(:foreign_keys_columns) { foreign_keys.map(&:column) } let(:foreign_keys_columns) { all_foreign_keys.map(&:column).uniq } # we can have FK and loose FK present at the same time
let(:ignored_columns) { ignored_fk_columns(table) } let(:ignored_columns) { ignored_fk_columns(table) }
it 'do have the foreign keys' do it 'do have the foreign keys' do
......
...@@ -24,19 +24,19 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do ...@@ -24,19 +24,19 @@ RSpec.describe Gitlab::Database::LooseForeignKeys do
Gitlab::Database.schemas_to_base_models.fetch(parent_table_schema) Gitlab::Database.schemas_to_base_models.fetch(parent_table_schema)
end end
it 'all `from_table` tables are present' do it 'all `to_table` tables are present' do
definitions.each do |definition| definitions.each do |definition|
base_models_for(definition.from_table).each do |model| base_models_for(definition.to_table).each do |model|
expect(model.connection).to be_table_exist(definition.from_table) expect(model.connection).to be_table_exist(definition.to_table)
end end
end end
end end
it 'all `to_table` tables are present' do it 'all `from_table` tables are present' do
definitions.each do |definition| definitions.each do |definition|
base_models_for(definition.to_table).each do |model| base_models_for(definition.from_table).each do |model|
expect(model.connection).to be_table_exist(definition.to_table) expect(model.connection).to be_table_exist(definition.from_table)
expect(model.connection).to be_column_exist(definition.to_table, definition.column) expect(model.connection).to be_column_exist(definition.from_table, definition.column)
end end
end end
end end
......
...@@ -24,8 +24,8 @@ RSpec.describe LooseForeignKeys::BatchCleanerService do ...@@ -24,8 +24,8 @@ RSpec.describe LooseForeignKeys::BatchCleanerService do
let(:loose_foreign_key_definitions) do let(:loose_foreign_key_definitions) do
[ [
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
'_test_loose_fk_parent_table',
'_test_loose_fk_child_table_1', '_test_loose_fk_child_table_1',
'_test_loose_fk_parent_table',
{ {
column: 'parent_id', column: 'parent_id',
on_delete: :async_delete, on_delete: :async_delete,
...@@ -33,8 +33,8 @@ RSpec.describe LooseForeignKeys::BatchCleanerService do ...@@ -33,8 +33,8 @@ RSpec.describe LooseForeignKeys::BatchCleanerService do
} }
), ),
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
'_test_loose_fk_parent_table',
'_test_loose_fk_child_table_2', '_test_loose_fk_child_table_2',
'_test_loose_fk_parent_table',
{ {
column: 'parent_id_with_different_column', column: 'parent_id_with_different_column',
on_delete: :async_nullify, on_delete: :async_nullify,
......
...@@ -13,8 +13,8 @@ RSpec.describe LooseForeignKeys::CleanerService do ...@@ -13,8 +13,8 @@ RSpec.describe LooseForeignKeys::CleanerService do
let(:loose_fk_definition) do let(:loose_fk_definition) do
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
'projects',
'issues', 'issues',
'projects',
{ {
column: 'project_id', column: 'project_id',
on_delete: :async_nullify, on_delete: :async_nullify,
...@@ -80,8 +80,8 @@ RSpec.describe LooseForeignKeys::CleanerService do ...@@ -80,8 +80,8 @@ RSpec.describe LooseForeignKeys::CleanerService do
let(:loose_fk_definition) do let(:loose_fk_definition) do
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
'users',
'project_authorizations', 'project_authorizations',
'users',
{ {
column: 'user_id', column: 'user_id',
on_delete: :async_delete, on_delete: :async_delete,
......
...@@ -31,8 +31,8 @@ RSpec.describe LooseForeignKeys::CleanupWorker do ...@@ -31,8 +31,8 @@ RSpec.describe LooseForeignKeys::CleanupWorker do
{ {
'_test_loose_fk_parent_table_1' => [ '_test_loose_fk_parent_table_1' => [
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
'_test_loose_fk_parent_table_1',
'_test_loose_fk_child_table_1_1', '_test_loose_fk_child_table_1_1',
'_test_loose_fk_parent_table_1',
{ {
column: 'parent_id', column: 'parent_id',
on_delete: :async_delete, on_delete: :async_delete,
...@@ -40,8 +40,8 @@ RSpec.describe LooseForeignKeys::CleanupWorker do ...@@ -40,8 +40,8 @@ RSpec.describe LooseForeignKeys::CleanupWorker do
} }
), ),
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
'_test_loose_fk_parent_table_1',
'_test_loose_fk_child_table_1_2', '_test_loose_fk_child_table_1_2',
'_test_loose_fk_parent_table_1',
{ {
column: 'parent_id_with_different_column', column: 'parent_id_with_different_column',
on_delete: :async_nullify, on_delete: :async_nullify,
...@@ -51,8 +51,8 @@ RSpec.describe LooseForeignKeys::CleanupWorker do ...@@ -51,8 +51,8 @@ RSpec.describe LooseForeignKeys::CleanupWorker do
], ],
'_test_loose_fk_parent_table_2' => [ '_test_loose_fk_parent_table_2' => [
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new( ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(
'_test_loose_fk_parent_table_2',
'_test_loose_fk_child_table_2_1', '_test_loose_fk_child_table_2_1',
'_test_loose_fk_parent_table_2',
{ {
column: 'parent_id', column: 'parent_id',
on_delete: :async_delete, on_delete: :async_delete,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment