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

database.rb « schema_validation « database « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 858bf618f448feb4b415c6d5e314bb82b934e746 (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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# frozen_string_literal: true

module Gitlab
  module Database
    module SchemaValidation
      class Database
        STATIC_PARTITIONS_SCHEMA = 'gitlab_partitions_static'

        def initialize(connection)
          @connection = connection
        end

        def fetch_index_by_name(index_name)
          index_map[index_name]
        end

        def fetch_trigger_by_name(trigger_name)
          trigger_map[trigger_name]
        end

        def fetch_foreign_key_by_name(foreign_key_name)
          foreign_key_map[foreign_key_name]
        end

        def fetch_table_by_name(table_name)
          table_map[table_name]
        end

        def index_exists?(index_name)
          index_map[index_name].present?
        end

        def trigger_exists?(trigger_name)
          trigger_map[trigger_name].present?
        end

        def foreign_key_exists?(foreign_key_name)
          fetch_foreign_key_by_name(foreign_key_name).present?
        end

        def table_exists?(table_name)
          fetch_table_by_name(table_name).present?
        end

        def indexes
          index_map.values
        end

        def triggers
          trigger_map.values
        end

        def foreign_keys
          foreign_key_map.values
        end

        def tables
          table_map.values
        end

        private

        attr_reader :connection

        def schemas
          @schemas ||= [STATIC_PARTITIONS_SCHEMA, connection.current_schema]
        end

        def index_map
          @index_map ||=
            fetch_indexes.transform_values! do |index_stmt|
              SchemaObjects::Index.new(PgQuery.parse(index_stmt).tree.stmts.first.stmt.index_stmt)
            end
        end

        def trigger_map
          @trigger_map ||=
            fetch_triggers.transform_values! do |trigger_stmt|
              SchemaObjects::Trigger.new(PgQuery.parse(trigger_stmt).tree.stmts.first.stmt.create_trig_stmt)
            end
        end

        def foreign_key_map
          @foreign_key_map ||= fetch_fks.each_with_object({}) do |stmt, result|
            adapter = Adapters::ForeignKeyDatabaseAdapter.new(stmt)

            result[adapter.name] = SchemaObjects::ForeignKey.new(adapter)
          end
        end

        def table_map
          @table_map ||= fetch_tables.transform_values! do |stmt|
            columns = stmt.map { |column| SchemaObjects::Column.new(Adapters::ColumnDatabaseAdapter.new(column)) }

            SchemaObjects::Table.new(stmt.first['table_name'], columns)
          end
        end

        def fetch_indexes
          sql = <<~SQL
            SELECT indexname, indexdef
            FROM pg_indexes
            WHERE indexname NOT LIKE '%_pkey' AND schemaname IN ($1, $2);
          SQL

          connection.select_rows(sql, nil, schemas).to_h
        end

        def fetch_triggers
          sql = <<~SQL
            SELECT triggers.tgname, pg_get_triggerdef(triggers.oid)
            FROM pg_catalog.pg_trigger triggers
            INNER JOIN pg_catalog.pg_class rel ON triggers.tgrelid = rel.oid
            INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = rel.relnamespace
            WHERE triggers.tgisinternal IS FALSE
            AND nsp.nspname IN ($1, $2)
          SQL

          connection.select_rows(sql, nil, schemas).to_h
        end

        def fetch_tables
          sql = <<~SQL
            SELECT
              table_information.relname  AS table_name,
              col_information.attname AS column_name,
              col_information.attnotnull AS not_null,
              col_information.attnum = ANY(pg_partitioned_table.partattrs) as partition_key,
              format_type(col_information.atttypid, col_information.atttypmod) AS data_type,
              pg_get_expr(col_default_information.adbin, col_default_information.adrelid) AS column_default
            FROM pg_attribute AS col_information
            JOIN pg_class     AS table_information  ON col_information.attrelid = table_information.oid
            JOIN pg_namespace AS schema_information ON table_information.relnamespace = schema_information.oid
            LEFT JOIN pg_partitioned_table ON pg_partitioned_table.partrelid = table_information.oid
            LEFT JOIN pg_attrdef AS col_default_information ON col_information.attrelid = col_default_information.adrelid
              AND col_information.attnum = col_default_information.adnum
            WHERE NOT col_information.attisdropped
            AND col_information.attnum > 0
            AND table_information.relkind IN ('r', 'p')
            AND schema_information.nspname IN ($1, $2)
          SQL

          connection.exec_query(sql, nil, schemas).group_by { |row| row['table_name'] }
        end

        def fetch_fks
          sql = <<~SQL
            SELECT
              pg_namespace.nspname::text AS schema,
              pg_class.relname::text AS table_name,
              pg_constraint.conname AS foreign_key_name,
              pg_get_constraintdef(pg_constraint.oid) AS foreign_key_definition
            FROM pg_constraint
            INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid
            INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
            WHERE contype = 'f'
            AND pg_namespace.nspname = $1
            AND pg_constraint.conparentid = 0
          SQL

          connection.exec_query(sql, nil, [connection.current_schema])
        end
      end
    end
  end
end