summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/database/postgres_foreign_key_spec.rb')
-rw-r--r--spec/lib/gitlab/database/postgres_foreign_key_spec.rb191
1 files changed, 173 insertions, 18 deletions
diff --git a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
index b0e08ca1e67..a8dbc4be16f 100644
--- a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
+++ b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb
@@ -2,28 +2,32 @@
require 'spec_helper'
-RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model do
+RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_category: :database do
# PostgresForeignKey does not `behaves_like 'a postgres model'` because it does not correspond 1-1 with a single entry
# in pg_class
before do
- ActiveRecord::Base.connection.execute(<<~SQL)
- CREATE TABLE public.referenced_table (
- id bigserial primary key not null
- );
-
- CREATE TABLE public.other_referenced_table (
- id bigserial primary key not null
- );
-
- CREATE TABLE public.constrained_table (
- id bigserial primary key not null,
- referenced_table_id bigint not null,
- other_referenced_table_id bigint not null,
- CONSTRAINT fk_constrained_to_referenced FOREIGN KEY(referenced_table_id) REFERENCES referenced_table(id),
- CONSTRAINT fk_constrained_to_other_referenced FOREIGN KEY(other_referenced_table_id)
- REFERENCES other_referenced_table(id)
- );
+ ApplicationRecord.connection.execute(<<~SQL)
+ CREATE TABLE public.referenced_table (
+ id bigserial primary key not null,
+ id_b bigserial not null,
+ UNIQUE (id, id_b)
+ );
+
+ CREATE TABLE public.other_referenced_table (
+ id bigserial primary key not null
+ );
+
+ CREATE TABLE public.constrained_table (
+ id bigserial primary key not null,
+ referenced_table_id bigint not null,
+ referenced_table_id_b bigint not null,
+ other_referenced_table_id bigint not null,
+ CONSTRAINT fk_constrained_to_referenced FOREIGN KEY(referenced_table_id, referenced_table_id_b) REFERENCES referenced_table(id, id_b) on delete restrict,
+ CONSTRAINT fk_constrained_to_other_referenced FOREIGN KEY(other_referenced_table_id)
+ REFERENCES other_referenced_table(id)
+ );
+
SQL
end
@@ -39,6 +43,14 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model do
end
end
+ describe '#by_referenced_table_name' do
+ it 'finds the foreign keys for the referenced table' do
+ expected = described_class.find_by!(name: 'fk_constrained_to_referenced')
+
+ expect(described_class.by_referenced_table_name('referenced_table')).to contain_exactly(expected)
+ end
+ end
+
describe '#by_constrained_table_identifier' do
it 'throws an error when the identifier name is not fully qualified' do
expect { described_class.by_constrained_table_identifier('constrained_table') }.to raise_error(ArgumentError, /not fully qualified/)
@@ -50,4 +62,147 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model do
expect(described_class.by_constrained_table_identifier('public.constrained_table')).to match_array(expected)
end
end
+
+ describe '#by_constrained_table_name' do
+ it 'finds the foreign keys for the constrained table' do
+ expected = described_class.where(name: %w[fk_constrained_to_referenced fk_constrained_to_other_referenced]).to_a
+
+ expect(described_class.by_constrained_table_name('constrained_table')).to match_array(expected)
+ end
+ end
+
+ describe '#by_name' do
+ it 'finds foreign keys by name' do
+ expect(described_class.by_name('fk_constrained_to_referenced').pluck(:name)).to contain_exactly('fk_constrained_to_referenced')
+ end
+ end
+
+ context 'when finding columns for foreign keys' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:fks) { described_class.by_constrained_table_name('constrained_table') }
+
+ where(:fk, :expected_constrained, :expected_referenced) do
+ lazy { described_class.find_by(name: 'fk_constrained_to_referenced') } | %w[referenced_table_id referenced_table_id_b] | %w[id id_b]
+ lazy { described_class.find_by(name: 'fk_constrained_to_other_referenced') } | %w[other_referenced_table_id] | %w[id]
+ end
+
+ with_them do
+ it 'finds the correct constrained column names' do
+ expect(fk.constrained_columns).to eq(expected_constrained)
+ end
+
+ it 'finds the correct referenced column names' do
+ expect(fk.referenced_columns).to eq(expected_referenced)
+ end
+
+ describe '#by_constrained_columns' do
+ it 'finds the correct foreign key' do
+ expect(fks.by_constrained_columns(expected_constrained)).to contain_exactly(fk)
+ end
+ end
+
+ describe '#by_referenced_columns' do
+ it 'finds the correct foreign key' do
+ expect(fks.by_referenced_columns(expected_referenced)).to contain_exactly(fk)
+ end
+ end
+ end
+ end
+
+ describe '#on_delete_action' do
+ before do
+ ApplicationRecord.connection.execute(<<~SQL)
+ create table public.referenced_table_all_on_delete_actions (
+ id bigserial primary key not null
+ );
+
+ create table public.constrained_table_all_on_delete_actions (
+ id bigserial primary key not null,
+ ref_id_no_action bigint not null constraint fk_no_action references referenced_table_all_on_delete_actions(id),
+ ref_id_restrict bigint not null constraint fk_restrict references referenced_table_all_on_delete_actions(id) on delete restrict,
+ ref_id_nullify bigint not null constraint fk_nullify references referenced_table_all_on_delete_actions(id) on delete set null,
+ ref_id_cascade bigint not null constraint fk_cascade references referenced_table_all_on_delete_actions(id) on delete cascade,
+ ref_id_set_default bigint not null constraint fk_set_default references referenced_table_all_on_delete_actions(id) on delete set default
+ )
+ SQL
+ end
+
+ let(:fks) { described_class.by_constrained_table_name('constrained_table_all_on_delete_actions') }
+
+ context 'with an invalid on_delete_action' do
+ it 'raises an error' do
+ # the correct value is :nullify, not :set_null
+ expect { fks.by_on_delete_action(:set_null) }.to raise_error(ArgumentError)
+ end
+ end
+
+ where(:fk_name, :expected_on_delete_action) do
+ [
+ %w[fk_no_action no_action],
+ %w[fk_restrict restrict],
+ %w[fk_nullify nullify],
+ %w[fk_cascade cascade],
+ %w[fk_set_default set_default]
+ ]
+ end
+
+ with_them do
+ subject(:fk) { fks.find_by(name: fk_name) }
+
+ it 'has the appropriate on delete action' do
+ expect(fk.on_delete_action).to eq(expected_on_delete_action)
+ end
+
+ describe '#by_on_delete_action' do
+ it 'finds the key by on delete action' do
+ expect(fks.by_on_delete_action(expected_on_delete_action)).to contain_exactly(fk)
+ end
+ end
+ end
+ end
+
+ context 'when supporting foreign keys to inherited tables in postgres 12' do
+ before do
+ skip('not supported before postgres 12') if ApplicationRecord.database.version.to_f < 12
+
+ ApplicationRecord.connection.execute(<<~SQL)
+ create table public.parent (
+ id bigserial primary key not null
+ ) partition by hash(id);
+
+ create table public.child partition of parent for values with (modulus 2, remainder 1);
+
+ create table public.referencing_partitioned (
+ id bigserial not null primary key,
+ constraint fk_inherited foreign key (id) references parent(id)
+ )
+ SQL
+ end
+
+ describe '#is_inherited' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:fk, :inherited) do
+ lazy { described_class.find_by(name: 'fk_inherited') } | false
+ lazy { described_class.by_referenced_table_identifier('public.child').first! } | true
+ lazy { described_class.find_by(name: 'fk_constrained_to_referenced') } | false
+ end
+
+ with_them do
+ it 'has the appropriate inheritance value' do
+ expect(fk.is_inherited).to eq(inherited)
+ end
+ end
+ end
+
+ describe '#not_inherited' do
+ let(:fks) { described_class.by_constrained_table_identifier('public.referencing_partitioned') }
+
+ it 'lists all non-inherited foreign keys' do
+ expect(fks.pluck(:referenced_table_name)).to contain_exactly('parent', 'child')
+ expect(fks.not_inherited.pluck(:referenced_table_name)).to contain_exactly('parent')
+ end
+ end
+ end
end