Skip to content

Commit 87b6e5b

Browse files
committed
[postgres] remove the PgResultSetMetaDataWrapper
* AR uses the OID to map to named types for which handlers are registered * JDBC can return the named types with getColumnTypeName() * For normal (non-array, non-rage, etc.) this already works * by adding handler based on the name as well as the OID, getColumnTypeName() can be used for everything. OID based mapping is still required for SchemaStatements. * loading additional types needs to be adapted to handle OIDs as well as type names, with or without namespace * pgjdbc returns two additional type: 'serial' and 'bigserial'. They're just int4 and int8 with auto-increment on top * only trick thing is "fmod". luckily this is only used for "numeric" and can be re-constructed by using getPrecision() and getScale() * one test needs to be excluded because it checks for a message containing an OID (number) instead of name (string) we get now
1 parent 4a3a605 commit 87b6e5b

File tree

4 files changed

+102
-40
lines changed

4 files changed

+102
-40
lines changed

lib/arjdbc/postgresql/oid_types.rb

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,61 @@ module PostgreSQL
99
# @private
1010
OID = ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID
1111

12+
# this version makes sure to register the types by name as well
13+
# we still need to version with OID since it's used from SchemaStatements as well
14+
class ArjdbcTypeMapInitializer < OID::TypeMapInitializer
15+
private
16+
17+
def name_with_ns(row)
18+
if row['in_ns']
19+
row['typname']
20+
else
21+
%Q("#{row['nspname']}"."#{row['typname']}")
22+
end
23+
end
24+
25+
def register_enum_type(row)
26+
super
27+
register name_with_ns(row), OID::Enum.new
28+
end
29+
30+
def register_array_type(row)
31+
super
32+
register_with_subtype(name_with_ns(row), row['typelem'].to_i) do |subtype|
33+
OID::Array.new(subtype, row['typdelim'])
34+
end
35+
end
36+
37+
def register_range_type(row)
38+
super
39+
name = name_with_ns(row)
40+
register_with_subtype(name, row['rngsubtype'].to_i) do |subtype|
41+
OID::Range.new(subtype, name.to_sym)
42+
end
43+
end
44+
45+
def register_domain_type(row)
46+
if base_type = @store.lookup(row['typbasetype'].to_i)
47+
register row['oid'], base_type
48+
register name_with_ns(row), base_type
49+
else
50+
warn "unknown base type (OID: #{row['typbasetype']}) for domain #{row['typname']}."
51+
end
52+
end
53+
54+
def register_composite_type(row)
55+
if subtype = @store.lookup(row['typelem'].to_i)
56+
register row['oid'], OID::Vector.new(row['typdelim'], subtype)
57+
register name_with_ns(row), OID::Vector.new(row['typdelim'], subtype)
58+
end
59+
end
60+
61+
def assert_valid_registration(oid, oid_type)
62+
ret = super
63+
ret == 0 ? oid : ret
64+
end
65+
end
66+
1267
# @private
1368
module OIDTypes
1469

@@ -41,7 +96,7 @@ def lookup_cast_type(sql_type)
4196

4297
def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
4398
if !type_map.key?(oid)
44-
load_additional_types(type_map, [oid])
99+
load_additional_types(type_map, oid)
45100
end
46101

47102
type_map.fetch(oid, fmod, sql_type) {
@@ -138,26 +193,45 @@ def initialize_type_map(m)
138193
end
139194

140195
load_additional_types(m)
196+
197+
# pgjdbc returns these if the column is auto-incrmenting
198+
m.alias_type 'serial', 'int4'
199+
m.alias_type 'bigserial', 'int8'
141200
end
142201

143-
def load_additional_types(type_map, oids = nil) # :nodoc:
144-
initializer = OID::TypeMapInitializer.new(type_map)
202+
def load_additional_types(type_map, oid = nil) # :nodoc:
203+
initializer = ArjdbcTypeMapInitializer.new(type_map)
145204

146205
if supports_ranges?
147206
query = <<-SQL
148-
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
207+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype,
208+
ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
149209
FROM pg_type as t
150210
LEFT JOIN pg_range as r ON oid = rngtypid
211+
JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
151212
SQL
152213
else
153214
query = <<-SQL
154-
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
215+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype,
216+
ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
155217
FROM pg_type as t
218+
JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
156219
SQL
157220
end
158221

159-
if oids
160-
query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
222+
if oid
223+
if oid.is_a? Numeric || oid.match(/^\d+$/)
224+
# numeric OID
225+
query += "WHERE t.oid::integer = %s" % oid
226+
227+
elsif m = oid.match(/"?(\w+)"?\."?(\w+)"?/)
228+
# namespace and type name
229+
query += "WHERE ns.nspname = '%s' AND t.typname = '%s'" % [m[1], m[2]]
230+
231+
else
232+
# only type name
233+
query += "WHERE t.typname = '%s' AND ns.nspname = ANY(current_schemas(true))" % oid
234+
end
161235
else
162236
query += initializer.query_conditions_for_initial_load(type_map)
163237
end

src/java/arjdbc/postgresql/PgResultSetMetaDataWrapper.java

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/java/arjdbc/postgresql/PostgreSQLResult.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import arjdbc.jdbc.RubyJdbcConnection;
55

66
import java.sql.ResultSet;
7+
import java.sql.ResultSetMetaData;
78
import java.sql.SQLException;
89
import java.sql.Types;
910

@@ -20,17 +21,13 @@
2021
import org.jruby.runtime.ThreadContext;
2122
import org.jruby.runtime.builtin.IRubyObject;
2223

23-
import org.postgresql.core.Field;
24-
import org.postgresql.jdbc.PgResultSetMetaData;
25-
import org.postgresql.jdbc.PgResultSetMetaDataWrapper; // This is a hack unfortunately to get around method scoping
26-
2724
/*
2825
* This class mimics the PG:Result class enough to get by
2926
*/
3027
public class PostgreSQLResult extends JdbcResult {
3128

3229
// These are needed when generating an AR::Result
33-
private final PgResultSetMetaData resultSetMetaData;
30+
private final ResultSetMetaData resultSetMetaData;
3431

3532
/********* JRuby compat methods ***********/
3633

@@ -60,7 +57,7 @@ private PostgreSQLResult(ThreadContext context, RubyClass clazz, RubyJdbcConnect
6057
ResultSet resultSet) throws SQLException {
6158
super(context, clazz, connection, resultSet);
6259

63-
resultSetMetaData = (PgResultSetMetaData) resultSet.getMetaData();
60+
resultSetMetaData = resultSet.getMetaData();
6461
}
6562

6663
/**
@@ -73,16 +70,28 @@ private PostgreSQLResult(ThreadContext context, RubyClass clazz, RubyJdbcConnect
7370
protected IRubyObject columnTypeMap(final ThreadContext context) throws SQLException {
7471
Ruby runtime = context.runtime;
7572
RubyHash types = RubyHash.newHash(runtime);
76-
PgResultSetMetaDataWrapper mdWrapper = new PgResultSetMetaDataWrapper(resultSetMetaData);
7773
int columnCount = columnNames.length;
7874

7975
IRubyObject adapter = connection.adapter(context);
8076
for (int i = 0; i < columnCount; i++) {
81-
final Field field = mdWrapper.getField(i + 1);
77+
int col = i + 1;
78+
String typeName = resultSetMetaData.getColumnTypeName(col);
79+
80+
int mod = 0;
81+
if ("numeric".equals(typeName)) {
82+
// this field is only relevant for "numeric" type in AR
83+
// AR checks (fmod - 4 & 0xffff).zero?
84+
// pgjdbc:
85+
// - for typmod == -1, getScale() and getPrecision() return 0
86+
// - for typmod != -1, getScale() returns "(typmod - 4) & 0xFFFF;"
87+
mod = resultSetMetaData.getScale(col);
88+
mod = mod == 0 && resultSetMetaData.getPrecision(col) == 0 ? -1 : mod + 4;
89+
}
90+
8291
final RubyString name = columnNames[i];
8392
final IRubyObject type = Helpers.invoke(context, adapter, "get_oid_type",
84-
runtime.newFixnum(field.getOID()),
85-
runtime.newFixnum(field.getMod()),
93+
runtime.newString(typeName),
94+
runtime.newFixnum(mod),
8695
name);
8796

8897
if (!type.isNil()) types.fastASet(name, type);

test/rails/excludes/postgresql/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterTest.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
exclude :test_exec_typecasts_bind_vals, 'it uses $1 for parameter mapping which is not currently supported'
44
end
55

6+
exclude :test_only_warn_on_first_encounter_of_unrecognized_oid, 'expects warning with OID, ARJBC has name instead'
7+
exclude :test_only_warn_on_first_encounter_of_unknown_oid, 'expects warning with OID, ARJBC has name instead'
68
exclude :test_default_sequence_name_bad_table, "ARJDBC does more quoting (which is not wrong)"

0 commit comments

Comments
 (0)