Class: StrongMigrations::Adapters::PostgreSQLAdapter

Inherits:
AbstractAdapter show all
Defined in:
lib/strong_migrations/adapters/postgresql_adapter.rb

Instance Method Summary collapse

Methods inherited from AbstractAdapter

#initialize, #rewrite_blocks

Constructor Details

This class inherits a constructor from StrongMigrations::Adapters::AbstractAdapter

Instance Method Details

#add_column_default_safe?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 44

def add_column_default_safe?
  server_version >= Gem::Version.new("11")
end

#analyze_table(table) ⇒ Object



40
41
42
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 40

def analyze_table(table)
  connection.execute "ANALYZE #{connection.quote_table_name(table.to_s)}"
end

#auto_incrementing_typesObject



172
173
174
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 172

def auto_incrementing_types
  ["primary_key", "serial", "bigserial"]
end

#change_type_safe?(table, column, type, options, existing_column, existing_type) ⇒ Boolean

Returns:

  • (Boolean)


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
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 48

def change_type_safe?(table, column, type, options, existing_column, existing_type)
  safe = false

  case type.to_s
  when "string"
    # safe to increase limit or remove it
    # not safe to decrease limit or add a limit
    case existing_type
    when "character varying"
      safe = !options[:limit] || (existing_column.limit && options[:limit] >= existing_column.limit)
    when "text"
      safe = !options[:limit]
    when "citext"
      safe = !options[:limit] && !indexed?(table, column)
    end
  when "text"
    # safe to change varchar to text (and text to text)
    safe =
      ["character varying", "text"].include?(existing_type) ||
      (existing_type == "citext" && !indexed?(table, column))
  when "citext"
    safe = ["character varying", "text"].include?(existing_type) && !indexed?(table, column)
  when "varbit"
    # increasing length limit or removing the limit is safe
    # but there doesn't seem to be a way to set/modify it
    # https://wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.2#Reduce_ALTER_TABLE_rewrites
  when "numeric", "decimal"
    # numeric and decimal are equivalent and can be used interchangeably
    safe = ["numeric", "decimal"].include?(existing_type) &&
      (
        (
          # unconstrained
          !options[:precision] && !options[:scale]
        ) || (
          # increased precision, same scale
          options[:precision] && existing_column.precision &&
          options[:precision] >= existing_column.precision &&
          options[:scale] == existing_column.scale
        )
      )
  when "datetime", "timestamp", "timestamptz"
    # precision for datetime
    # limit for timestamp, timestamptz
    precision = (type.to_s == "datetime" ? options[:precision] : options[:limit]) || 6
    existing_precision = existing_column.limit || existing_column.precision || 6

    type_map = {
      "timestamp" => "timestamp without time zone",
      "timestamptz" => "timestamp with time zone"
    }
    maybe_safe = type_map.values.include?(existing_type) && precision >= existing_precision

    if maybe_safe
      new_type = type.to_s == "datetime" ? datetime_type : type.to_s

      # resolve with fallback
      new_type = type_map[new_type] || new_type

      safe = new_type == existing_type || (server_version >= Gem::Version.new("12") && time_zone == "UTC")
    end
  when "time"
    precision = options[:precision] || options[:limit] || 6
    existing_precision = existing_column.precision || existing_column.limit || 6

    safe = existing_type == "time without time zone" && precision >= existing_precision
  when "timetz"
    # increasing precision is safe
    # but there doesn't seem to be a way to set/modify it
  when "interval"
    # https://wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.2#Reduce_ALTER_TABLE_rewrites
    # Active Record uses precision before limit
    precision = options[:precision] || options[:limit] || 6
    existing_precision = existing_column.precision || existing_column.limit || 6

    safe = existing_type == "interval" && precision >= existing_precision
  when "inet"
    safe = existing_type == "cidr"
  end

  safe
end

#check_lock_timeout(limit) ⇒ Object



30
31
32
33
34
35
36
37
38
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 30

def check_lock_timeout(limit)
  lock_timeout = connection.select_all("SHOW lock_timeout").first["lock_timeout"]
  lock_timeout_sec = timeout_to_sec(lock_timeout)
  if lock_timeout_sec == 0
    warn "[strong_migrations] DANGER: No lock timeout set"
  elsif lock_timeout_sec > limit
    warn "[strong_migrations] DANGER: Lock timeout is longer than #{limit} seconds: #{lock_timeout}"
  end
end

#constraints(table_name) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 130

def constraints(table_name)
  query = <<~SQL
    SELECT
      conname AS name,
      pg_get_constraintdef(oid) AS def
    FROM
      pg_constraint
    WHERE
      contype = 'c' AND
      convalidated AND
      conrelid = #{connection.quote(connection.quote_table_name(table_name))}::regclass
  SQL
  select_all(query.squish).to_a
end

#default_volatile?(default) ⇒ Boolean

default to true if unsure

Returns:

  • (Boolean)


166
167
168
169
170
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 166

def default_volatile?(default)
  name = default.to_s.delete_suffix("()")
  rows = select_all("SELECT provolatile FROM pg_proc WHERE proname = #{connection.quote(name)}").to_a
  rows.empty? || rows.any? { |r| r["provolatile"] == "v" }
end

#index_corruption?Boolean

only check in non-developer environments (where actual server version is used)

Returns:

  • (Boolean)


159
160
161
162
163
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 159

def index_corruption?
  server_version >= Gem::Version.new("14.0") &&
    server_version < Gem::Version.new("14.4") &&
    !StrongMigrations.developer_env?
end

#min_versionObject



8
9
10
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 8

def min_version
  "10"
end

#nameObject



4
5
6
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 4

def name
  "PostgreSQL"
end

#server_versionObject



12
13
14
15
16
17
18
19
20
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 12

def server_version
  @version ||= begin
    target_version(StrongMigrations.target_postgresql_version) do
      version = select_all("SHOW server_version_num").first["server_version_num"].to_i
      # major and minor version
      "#{version / 10000}.#{(version % 10000)}"
    end
  end
end

#set_lock_timeout(timeout) ⇒ Object



26
27
28
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 26

def set_lock_timeout(timeout)
  set_timeout("lock_timeout", timeout)
end

#set_statement_timeout(timeout) ⇒ Object



22
23
24
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 22

def set_statement_timeout(timeout)
  set_timeout("statement_timeout", timeout)
end

#writes_blocked?Boolean

Returns:

  • (Boolean)


145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/strong_migrations/adapters/postgresql_adapter.rb', line 145

def writes_blocked?
  query = <<~SQL
    SELECT
      relation::regclass::text
    FROM
      pg_locks
    WHERE
      mode IN ('ShareRowExclusiveLock', 'AccessExclusiveLock') AND
      pid = pg_backend_pid()
  SQL
  select_all(query.squish).any?
end