提交 d91117f2 编写于 作者: Y Yves Senn

Merge pull request #14692 from senny/pg/reload_type_map_for_unknown_types

PostgreSQL automatic type map reload
* PostgreSQL adapter only warns once for every missing OID per connection.
Fixes #14275.
*Matthew Draper*, *Yves Senn*
* PostgreSQL adapter automatically reloads it's type map when encountering
unknown OIDs.
Fixes #14678.
*Matthew Draper*, *Yves Senn*
* Fix insertion of records via `has_many :through` association with scope.
Fixes #3548.
......
......@@ -142,10 +142,7 @@ def exec_query(sql, name = 'SQL', binds = [])
fields.each_with_index do |fname, i|
ftype = result.ftype i
fmod = result.fmod i
types[fname] = type_map.fetch(ftype, fmod) { |oid, mod|
warn "unknown OID: #{fname}(#{oid}) (#{sql})"
OID::Identity.new
}
types[fname] = get_oid_type(ftype, fmod, fname)
end
ret = ActiveRecord::Result.new(fields, result.values, types)
......
......@@ -182,9 +182,7 @@ def indexes(table_name, name = nil)
def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
oid = type_map.fetch(oid.to_i, fmod.to_i) {
OID::Identity.new
}
oid = get_oid_type(oid.to_i, fmod.to_i, column_name)
PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
end
end
......
......@@ -559,6 +559,17 @@ def type_map
@type_map
end
def get_oid_type(oid, fmod, column_name)
if !type_map.key?(oid)
initialize_type_map(type_map, [oid])
end
type_map.fetch(oid, fmod) {
warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
type_map[oid] = OID::Identity.new
}
end
def reload_type_map
type_map.clear
initialize_type_map(type_map)
......@@ -583,19 +594,25 @@ def add_oid(row, records_by_oid, type_map)
type_map
end
def initialize_type_map(type_map)
def initialize_type_map(type_map, oids = nil)
if supports_ranges?
result = execute(<<-SQL, 'SCHEMA')
query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
FROM pg_type as t
LEFT JOIN pg_range as r ON oid = rngtypid
SQL
else
result = execute(<<-SQL, 'SCHEMA')
query = <<-SQL
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
FROM pg_type as t
SQL
end
if oids
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
end
result = execute(query, 'SCHEMA')
ranges, nodes = result.partition { |row| row['typtype'] == 'r' }
enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
......
......@@ -19,9 +19,6 @@ def setup
t.column :price, :custom_money
end
end
# reload type map after creating the enum type
@connection.send(:reload_type_map)
end
teardown do
......
......@@ -21,8 +21,6 @@ def setup
t.column :current_mood, :mood
end
end
# reload type map after creating the enum type
@connection.send(:reload_type_map)
end
teardown do
......
# encoding: utf-8
require "cases/helper"
require 'support/ddl_helper'
require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::TestCase
include DdlHelper
include ConnectionHelper
def setup
@connection = ActiveRecord::Base.connection
......@@ -357,6 +359,43 @@ def test_raise_error_when_cannot_translate_exception
end
end
def test_reload_type_map_for_newly_defined_types
@connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')"
result = @connection.select_all "SELECT 'good'::feeling"
assert_instance_of(PostgreSQLAdapter::OID::Enum,
result.column_types["feeling"])
ensure
@connection.execute "DROP TYPE IF EXISTS feeling"
reset_connection
end
def test_only_reload_type_map_once_for_every_unknown_type
silence_warnings do
assert_queries 2, ignore_none: true do
@connection.select_all "SELECT NULL::anyelement"
end
assert_queries 1, ignore_none: true do
@connection.select_all "SELECT NULL::anyelement"
end
assert_queries 2, ignore_none: true do
@connection.select_all "SELECT NULL::anyarray"
end
end
ensure
reset_connection
end
def test_only_warn_on_first_encounter_of_unknown_oid
warning = capture(:stderr) {
@connection.select_all "SELECT NULL::anyelement"
@connection.select_all "SELECT NULL::anyelement"
@connection.select_all "SELECT NULL::anyelement"
}
assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning)
ensure
reset_connection
end
private
def insert(ctx, data)
binds = data.map { |name, value|
......
......@@ -34,7 +34,6 @@ def setup
@connection.add_column 'postgresql_ranges', 'float_range', 'floatrange'
end
@connection.send :reload_type_map
PostgresqlRange.reset_column_information
rescue ActiveRecord::StatementInvalid
skip "do not test on PG without range"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册