File: internal_metadata.rb

package info (click to toggle)
rails 2%3A7.2.2.1%2Bdfsg-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 43,352 kB
  • sloc: ruby: 349,799; javascript: 30,703; yacc: 46; sql: 43; sh: 29; makefile: 27
file content (164 lines) | stat: -rw-r--r-- 4,426 bytes parent folder | download
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
# frozen_string_literal: true

require "active_record/scoping/default"
require "active_record/scoping/named"

module ActiveRecord
  # This class is used to create a table that keeps track of values and keys such
  # as which environment migrations were run in.
  #
  # This is enabled by default. To disable this functionality set
  # `use_metadata_table` to false in your database configuration.
  class InternalMetadata # :nodoc:
    class NullInternalMetadata # :nodoc:
    end

    attr_reader :arel_table

    def initialize(pool)
      @pool = pool
      @arel_table = Arel::Table.new(table_name)
    end

    def primary_key
      "key"
    end

    def value_key
      "value"
    end

    def table_name
      "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
    end

    def enabled?
      @pool.db_config.use_metadata_table?
    end

    def []=(key, value)
      return unless enabled?

      @pool.with_connection do |connection|
        update_or_create_entry(connection, key, value)
      end
    end

    def [](key)
      return unless enabled?

      @pool.with_connection do |connection|
        if entry = select_entry(connection, key)
          entry[value_key]
        end
      end
    end

    def delete_all_entries
      dm = Arel::DeleteManager.new(arel_table)

      @pool.with_connection do |connection|
        connection.delete(dm, "#{self.class} Destroy")
      end
    end

    def count
      sm = Arel::SelectManager.new(arel_table)
      sm.project(*Arel::Nodes::Count.new([Arel.star]))

      @pool.with_connection do |connection|
        connection.select_values(sm, "#{self.class} Count").first
      end
    end

    def create_table_and_set_flags(environment, schema_sha1 = nil)
      return unless enabled?

      @pool.with_connection do |connection|
        create_table
        update_or_create_entry(connection, :environment, environment)
        update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1
      end
    end

    # Creates an internal metadata table with columns +key+ and +value+
    def create_table
      return unless enabled?

      @pool.with_connection do |connection|
        unless connection.table_exists?(table_name)
          connection.create_table(table_name, id: false) do |t|
            t.string :key, **connection.internal_string_options_for_primary_key
            t.string :value
            t.timestamps
          end
        end
      end
    end

    def drop_table
      return unless enabled?

      @pool.with_connection do |connection|
        connection.drop_table table_name, if_exists: true
      end
    end

    def table_exists?
      @pool.schema_cache.data_source_exists?(table_name)
    end

    private
      def update_or_create_entry(connection, key, value)
        entry = select_entry(connection, key)

        if entry
          if entry[value_key] != value
            update_entry(connection, key, value)
          else
            entry[value_key]
          end
        else
          create_entry(connection, key, value)
        end
      end

      def current_time(connection)
        connection.default_timezone == :utc ? Time.now.utc : Time.now
      end

      def create_entry(connection, key, value)
        im = Arel::InsertManager.new(arel_table)
        im.insert [
          [arel_table[primary_key], key],
          [arel_table[value_key], value],
          [arel_table[:created_at], current_time(connection)],
          [arel_table[:updated_at], current_time(connection)]
        ]

        connection.insert(im, "#{self.class} Create", primary_key, key)
      end

      def update_entry(connection, key, new_value)
        um = Arel::UpdateManager.new(arel_table)
        um.set [
          [arel_table[value_key], new_value],
          [arel_table[:updated_at], current_time(connection)]
        ]

        um.where(arel_table[primary_key].eq(key))

        connection.update(um, "#{self.class} Update")
      end

      def select_entry(connection, key)
        sm = Arel::SelectManager.new(arel_table)
        sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true))
        sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
        sm.order(arel_table[primary_key].asc)
        sm.limit = 1

        connection.select_all(sm, "#{self.class} Load").first
      end
  end
end