schema_spec.rb 5.18 KB
Newer Older
1 2 3
# frozen_string_literal: true

require 'spec_helper'
4
require Rails.root.join('ee', 'spec', 'db', 'schema_support') if Gitlab.ee?
5 6 7 8 9 10

describe 'Database schema' do
  let(:connection) { ActiveRecord::Base.connection }
  let(:tables) { connection.tables }

  # Use if you are certain that this column should not have a foreign key
11
  # EE: edit the ee/spec/db/schema_support.rb
12 13
  IGNORED_FK_COLUMNS = {
    abuse_reports: %w[reporter_id user_id],
14 15 16 17
    application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_site_id],
    approvers: %w[target_id user_id],
    approvals: %w[user_id],
    approver_groups: %w[target_id],
18 19
    audit_events: %w[author_id entity_id],
    award_emoji: %w[awardable_id user_id],
20
    boards: %w[milestone_id],
21 22 23 24 25 26 27 28 29
    chat_names: %w[chat_id service_id team_id user_id],
    chat_teams: %w[team_id],
    ci_builds: %w[erased_by_id runner_id trigger_request_id user_id],
    ci_pipelines: %w[user_id],
    ci_runner_projects: %w[runner_id],
    ci_trigger_requests: %w[commit_id],
    cluster_providers_gcp: %w[gcp_project_id operation_id],
    deploy_keys_projects: %w[deploy_key_id],
    deployments: %w[deployable_id environment_id user_id],
30
    draft_notes: %w[discussion_id commit_id],
31 32
    emails: %w[user_id],
    events: %w[target_id],
33
    epics: %w[updated_by_id last_edited_by_id start_date_sourcing_milestone_id due_date_sourcing_milestone_id],
34
    forked_project_links: %w[forked_from_project_id],
35 36 37 38 39 40 41
    geo_event_log: %w[hashed_storage_attachments_event_id],
    geo_job_artifact_deleted_events: %w[job_artifact_id],
    geo_lfs_object_deleted_events: %w[lfs_object_id],
    geo_node_statuses: %w[last_event_id cursor_last_event_id],
    geo_nodes: %w[oauth_application_id],
    geo_repository_deleted_events: %w[project_id],
    geo_upload_deleted_events: %w[upload_id model_id],
42
    identities: %w[user_id],
Felipe Artur's avatar
Felipe Artur committed
43
    issues: %w[last_edited_by_id state_id],
44
    jira_tracker_data: %w[jira_issue_transition_id],
45 46 47
    keys: %w[user_id],
    label_links: %w[target_id],
    lfs_objects_projects: %w[lfs_object_id project_id],
48
    ldap_group_links: %w[group_id],
49
    members: %w[source_id created_by_id],
Felipe Artur's avatar
Felipe Artur committed
50
    merge_requests: %w[last_edited_by_id state_id],
51 52 53 54 55 56 57 58
    namespaces: %w[owner_id parent_id],
    notes: %w[author_id commit_id noteable_id updated_by_id resolved_by_id discussion_id],
    notification_settings: %w[source_id],
    oauth_access_grants: %w[resource_owner_id application_id],
    oauth_access_tokens: %w[resource_owner_id application_id],
    oauth_applications: %w[owner_id],
    project_group_links: %w[group_id],
    project_statistics: %w[namespace_id],
59
    projects: %w[creator_id namespace_id ci_id mirror_user_id],
60 61 62 63 64 65 66
    redirect_routes: %w[source_id],
    repository_languages: %w[programming_language_id],
    routes: %w[source_id],
    sent_notifications: %w[project_id noteable_id recipient_id commit_id in_reply_to_discussion_id],
    snippets: %w[author_id],
    spam_logs: %w[user_id],
    subscriptions: %w[user_id subscribable_id],
67
    slack_integrations: %w[team_id user_id],
68 69 70 71 72
    taggings: %w[tag_id taggable_id tagger_id],
    timelogs: %w[user_id],
    todos: %w[target_id commit_id],
    uploads: %w[model_id],
    user_agent_details: %w[subject_id],
73
    users: %w[color_scheme_id created_by_id theme_id email_opted_in_source_id],
74
    users_star_projects: %w[user_id],
75 76 77
    vulnerability_identifiers: %w[external_id],
    vulnerability_scanners: %w[external_id],
    web_hooks: %w[service_id group_id],
78
    suggestions: %w[commit_id]
79 80 81 82 83 84 85 86
  }.with_indifferent_access.freeze

  context 'for table' do
    ActiveRecord::Base.connection.tables.sort.each do |table|
      describe table do
        let(:indexes) { connection.indexes(table) }
        let(:columns) { connection.columns(table) }
        let(:foreign_keys) { connection.foreign_keys(table) }
87
        let(:primary_key_column) { connection.primary_key(table) }
88 89 90 91 92 93 94

        context 'all foreign keys' do
          # for index to be effective, the FK constraint has to be at first place
          it 'are indexed' do
            first_indexed_column = indexes.map(&:columns).map(&:first)
            foreign_keys_columns = foreign_keys.map(&:column)

95 96 97 98 99 100
            # Add the primary key column to the list of indexed columns because
            # postgres and mysql both automatically create an index on the primary
            # key. Also, the rails connection.indexes() method does not return
            # automatically generated indexes (like the primary key index).
            first_indexed_column = first_indexed_column.push(primary_key_column)

101 102 103 104 105 106 107 108
            expect(first_indexed_column.uniq).to include(*foreign_keys_columns)
          end
        end

        context 'columns ending with _id' do
          let(:column_names) { columns.map(&:name) }
          let(:column_names_with_id) { column_names.select { |column_name| column_name.ends_with?('_id') } }
          let(:foreign_keys_columns) { foreign_keys.map(&:column) }
Kamil Trzciński's avatar
Kamil Trzciński committed
109
          let(:ignored_columns) { ignored_fk_columns(table) }
110 111 112 113 114 115 116 117

          it 'do have the foreign keys' do
            expect(column_names_with_id - ignored_columns).to contain_exactly(*foreign_keys_columns)
          end
        end
      end
    end
  end
Kamil Trzciński's avatar
Kamil Trzciński committed
118 119 120 121 122 123

  private

  def ignored_fk_columns(column)
    IGNORED_FK_COLUMNS.fetch(column, [])
  end
124
end