Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab_schema_info.rb « database « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 36e586313a59639d46985ea57cf71b43352a87dd (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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# frozen_string_literal: true

module Gitlab
  module Database
    GitlabSchemaInfo = Struct.new(
      :name,
      :description,
      :allow_cross_joins,
      :allow_cross_transactions,
      :allow_cross_foreign_keys,
      :file_path,
      :cell_local,
      keyword_init: true
    ) do
      def initialize(*)
        super
        self.name = name.to_sym
        self.allow_cross_joins = add_table_specific_allows(
          :joins, convert_array_to_hash(allow_cross_joins))
        self.allow_cross_transactions = add_table_specific_allows(
          :transactions, convert_array_to_hash(allow_cross_transactions))
        self.allow_cross_foreign_keys = add_table_specific_allows(
          :foreign_keys, convert_array_to_hash(allow_cross_foreign_keys))
      end

      def self.load_file(yaml_file)
        content = YAML.load_file(yaml_file)
        new(**content.deep_symbolize_keys.merge(file_path: yaml_file))
      end

      def allow_cross_joins?(table_schemas, all_tables)
        allowed_for?(allow_cross_joins, table_schemas, all_tables)
      end

      def allow_cross_transactions?(table_schemas, all_tables)
        allowed_for?(allow_cross_transactions, table_schemas, all_tables)
      end

      def allow_cross_foreign_keys?(table_schemas, all_tables)
        allowed_for?(allow_cross_foreign_keys, table_schemas, all_tables)
      end

      private

      def allowed_for?(allowed_schemas, table_schemas, all_tables)
        # Take all the schemas in the query and remove the current schema and all the allowed schemas. If there is
        # anything left then it's not allowed. Then we even if there is nothing left we continue to verify
        # `specific_tables` used in the allowed schemas.
        denied_schemas = table_schemas - [name]
        denied_schemas -= allowed_schemas.keys
        return false unless denied_schemas.empty?

        # Additional validation for specific_tables. We should validate that if `specific_tables` is set then we will
        # need all the tables to be in the the allowed specific_tables
        all_tables.all? do |table|
          table_schema = ::Gitlab::Database::GitlabSchema.table_schema!(table)
          allowed_tables = allowed_schemas[table_schema]

          # If specific tables key is nil? (not present) then we assume all tables are allowed and return true Otherwise
          # we check every table in the current query is in specific_tables list
          allowed_tables.nil? ||
            allowed_tables[:specific_tables].include?(table)
        end
      end

      # Convert from:
      # - schema_a
      # - schema_b:
      #     specific_tables:
      #     - table_b_of_schema_b
      #     - table_c_of_schema_b
      #
      # To:
      # { :schema_a => nil,
      #   :schema_b => { specific_tables : ['table_b_of_schema_b', 'table_c_of_schema_b'] }
      # }
      #
      def convert_array_to_hash(subject)
        result = {}

        subject&.each do |item|
          if item.is_a?(Hash)
            item.each do |key, value|
              result[key.to_sym] = { specific_tables: value[:specific_tables].to_set }
            end
          else
            result[item.to_sym] = nil
          end
        end

        result
      end

      # This method loops over all the `db/docs` files for every table and injects any
      # allow_cross_joins/allow_cross_transactions/allow_cross_foreign_keys into the specific_tables lists for the
      # current schema.
      def add_table_specific_allows(type, schema_allows)
        result = schema_allows
        all_table_allows(type).each do |schema_from, tables|
          # Preserve the meaning of `nil` as defined in convert_array_to_hash as a nil value means that we allow all
          # tables
          next if result.key?(schema_from) && result[schema_from].nil?

          # Now we add the table to the specific_tables list because this table specifies it is allowed in this schema
          result[schema_from] ||= { specific_tables: Set.new }
          result[schema_from][:specific_tables] += tables
        end
        result.freeze
      end

      # For the given type we iterate over all db/docs files build a Hash like:
      #
      # {
      #   gitlab_main_cell: ['table_a', 'table_b']
      # }
      #
      # This specifies that in the `gitlab_main_cell` schema the 'table_a` and `table_b` tables are allowing cross
      # queries with the current schema
      def all_table_allows(type)
        @all_table_allows ||= {}
        @all_table_allows[type] ||= begin
          result = {}
          ::Gitlab::Database::Dictionary.entries.each do |entry|
            allowed_schemas = entry.allow_cross_to_schemas(type)
            allowed_schemas.each do |schema|
              # In the context of this GitlabSchemaInfo we only need the tables that have allowed this schema
              next unless schema == name

              result[entry.gitlab_schema.to_sym] ||= []
              result[entry.gitlab_schema.to_sym] << entry.key_name
            end
          end
          result
        end
      end
    end
  end
end