Permalink
Browse files

Make jdbc/postgres adapter convert array type elements (e.g. date[] a…

…rrays are returned as arrays of Date instances)

Before, the conversion proc would recursively map the array so that
all subarrays where turned into ruby arrays.  However, it would
not do any value conversion, so date[] arrays would be returned
as ruby arrays with Java::JavaSQL::Date instances.  Refactor this
to use a separate converter instance per array column, that uses
the standard scalar type conversion to convert elements of the
array.

Refactor specs so that some basic tests of this feature are run
on jdbc.  Unfortunately, it looks like not all types are handled,
so change the tests so that only the cases the jdbc/postgres
adapter currently handles are run on jdbc/postgres.

This is not backwards compatible if you were relying on the
jdbc/postgres array typecasting internals.
  • Loading branch information...
1 parent 4d5988a commit 938299f1cbda55562a1f80eb0ba4b7f0009f6b1f @jeremyevans committed Jun 9, 2012
Showing with 86 additions and 29 deletions.
  1. +2 −0 CHANGELOG
  2. +26 −8 lib/sequel/adapters/jdbc/postgresql.rb
  3. +58 −21 spec/adapters/postgres_spec.rb
View
@@ -1,5 +1,7 @@
=== HEAD
+* Make jdbc/postgres adapter convert array type elements (e.g. date[] arrays are returned as arrays of Date instances) (jeremyevans)
+
* Make the pg_inet extension handle inet[]/cidr[]/macaddr[] types when used with the pg_array extension (jeremyevans)
* Make the pg_json extension handle json[] type when used with the pg_array extension (jeremyevans)
@@ -45,32 +45,50 @@ class Dataset < JDBC::Dataset
APOS = Dataset::APOS
class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
- # Convert Java::OrgPostgresqlJdbc4::Jdbc4Array to ruby arrays
- def pg_array(v)
- _pg_array(v.array)
- end
-
# Convert Java::OrgPostgresqlUtil::PGobject to ruby strings
def pg_object(v)
v.to_string
end
+ end
+
+ # Handle conversions of PostgreSQL array instances
+ class PGArrayConverter
+ # Set the method that will return the correct conversion
+ # proc for elements of this array.
+ def initialize(meth)
+ @conversion_proc_method = meth
+ @conversion_proc = nil
+ end
+
+ # Convert Java::OrgPostgresqlJdbc4::Jdbc4Array to ruby arrays
+ def call(v)
+ _pg_array(v.array)
+ end
private
# Handle multi-dimensional Java arrays by recursively mapping them
- # to ruby arrays.
+ # to ruby arrays of ruby values.
def _pg_array(v)
v.to_ary.map do |i|
if i.respond_to?(:to_ary)
_pg_array(i)
+ elsif i
+ if @conversion_proc.nil?
+ @conversion_proc = @conversion_proc_method.call(i)
+ end
+ if @conversion_proc
+ @conversion_proc.call(i)
+ else
+ i
+ end
else
i
end
end
end
end
- PG_ARRAY_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_array)
PG_OBJECT_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_object)
# Add the shared PostgreSQL prepared statement methods
@@ -88,7 +106,7 @@ def prepare(*args)
def convert_type_proc(v)
case v
when Java::OrgPostgresqlJdbc4::Jdbc4Array
- PG_ARRAY_METHOD
+ PGArrayConverter.new(method(:convert_type_proc))
when Java::OrgPostgresqlUtil::PGobject
PG_OBJECT_METHOD
else
@@ -1354,6 +1354,7 @@ def @ds.check_return
@db.extend Sequel::Postgres::PGArray::DatabaseMethods
@ds = @db[:items]
@native = POSTGRES_DB.adapter_scheme == :postgres
+ @jdbc = POSTGRES_DB.adapter_scheme == :jdbc
@tp = lambda{@db.schema(:items).map{|a| a.last[:type]}}
end
after do
@@ -1371,19 +1372,26 @@ def @ds.check_return
@tp.call.should == [:integer_array, :integer_array, :bigint_array, :float_array, :float_array]
@ds.insert([1].pg_array(:int2), [nil, 2].pg_array(:int4), [3, nil].pg_array(:int8), [4, nil, 4.5].pg_array(:real), [5, nil, 5.5].pg_array("double precision"))
@ds.count.should == 1
- if @native
- rs = @ds.all
+ rs = @ds.all
+ if @jdbc || @native
rs.should == [{:i2=>[1], :i4=>[nil, 2], :i8=>[3, nil], :r=>[4.0, nil, 4.5], :dp=>[5.0, nil, 5.5]}]
+ end
+ if @native
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete
@ds.insert(rs.first)
@ds.all.should == rs
+ end
- @ds.delete
- @ds.insert([[1], [2]].pg_array(:int2), [[nil, 2], [3, 4]].pg_array(:int4), [[3, nil], [nil, nil]].pg_array(:int8), [[4, nil], [nil, 4.5]].pg_array(:real), [[5, nil], [nil, 5.5]].pg_array("double precision"))
- rs = @ds.all
+ @ds.delete
+ @ds.insert([[1], [2]].pg_array(:int2), [[nil, 2], [3, 4]].pg_array(:int4), [[3, nil], [nil, nil]].pg_array(:int8), [[4, nil], [nil, 4.5]].pg_array(:real), [[5, nil], [nil, 5.5]].pg_array("double precision"))
+
+ rs = @ds.all
+ if @jdbc || @native
rs.should == [{:i2=>[[1], [2]], :i4=>[[nil, 2], [3, 4]], :i8=>[[3, nil], [nil, nil]], :r=>[[4, nil], [nil, 4.5]], :dp=>[[5, nil], [nil, 5.5]]}]
+ end
+ if @native
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete
@@ -1399,19 +1407,25 @@ def @ds.check_return
@tp.call.should == [:decimal_array]
@ds.insert([BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')].pg_array(:numeric))
@ds.count.should == 1
- if @native
- rs = @ds.all
+ rs = @ds.all
+ if @jdbc || @native
rs.should == [{:n=>[BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')]}]
+ end
+ if @native
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete
@ds.insert(rs.first)
@ds.all.should == rs
+ end
- @ds.delete
- @ds.insert([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]].pg_array(:numeric))
- rs = @ds.all
+ @ds.delete
+ @ds.insert([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]].pg_array(:numeric))
+ rs = @ds.all
+ if @jdbc || @native
rs.should == [{:n=>[[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]]}]
+ end
+ if @native
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete
@@ -1429,19 +1443,25 @@ def @ds.check_return
@tp.call.should == [:string_array, :string_array, :string_array]
@ds.insert(['a', nil, 'NULL', 'b"\'c'].pg_array('char(4)'), ['a', nil, 'NULL', 'b"\'c'].pg_array(:varchar), ['a', nil, 'NULL', 'b"\'c'].pg_array(:text))
@ds.count.should == 1
- if @native
- rs = @ds.all
+ rs = @ds.all
+ if @jdbc || @native
rs.should == [{:c=>['a ', nil, 'NULL', 'b"\'c'], :vc=>['a', nil, 'NULL', 'b"\'c'], :t=>['a', nil, 'NULL', 'b"\'c']}]
+ end
+ if @native
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete
@ds.insert(rs.first)
@ds.all.should == rs
+ end
- @ds.delete
- @ds.insert([[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array('char(4)'), [[['a'], ['']], [['NULL'], ['b"\'c']]].pg_array(:varchar), [[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array(:text))
- rs = @ds.all
+ @ds.delete
+ @ds.insert([[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array('char(4)'), [[['a'], ['']], [['NULL'], ['b"\'c']]].pg_array(:varchar), [[['a'], [nil]], [['NULL'], ['b"\'c']]].pg_array(:text))
+ rs = @ds.all
+ if @jdbc || @native
rs.should == [{:c=>[[['a '], [nil]], [['NULL'], ['b"\'c']]], :vc=>[[['a'], ['']], [['NULL'], ['b"\'c']]], :t=>[[['a'], [nil]], [['NULL'], ['b"\'c']]]}]
+ end
+ if @native
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete
@@ -1453,25 +1473,42 @@ def @ds.check_return
specify 'insert and retrieve arrays of other types' do
@db.create_table!(:items) do
column :b, 'bool[]'
- column :ba, 'bytea[]'
column :d, 'date[]'
column :t, 'time[]'
- column :tz, 'timetz[]'
column :ts, 'timestamp[]'
column :tstz, 'timestamptz[]'
- column :o, 'oid[]'
end
- @tp.call.should == [:boolean_array, :blob_array, :date_array, :time_array, :time_timezone_array, :datetime_array, :datetime_timezone_array, :integer_array]
+ @tp.call.should == [:boolean_array, :date_array, :time_array, :datetime_array, :datetime_timezone_array]
d = Date.today
t = Sequel::SQLTime.create(10, 20, 30)
ts = Time.local(2011, 1, 2, 3, 4, 5)
- @ds.insert([true, false].pg_array(:bool), [Sequel.blob("a\0"), nil].pg_array(:bytea), [d, nil].pg_array(:date), [t, nil].pg_array(:time), [t, nil].pg_array(:timetz), [ts, nil].pg_array(:timestamp), [ts, nil].pg_array(:timestamptz), [1, 2, 3].pg_array(:oid))
+ @ds.insert([true, false].pg_array(:bool), [d, nil].pg_array(:date), [t, nil].pg_array(:time), [ts, nil].pg_array(:timestamp), [ts, nil].pg_array(:timestamptz))
+ @ds.count.should == 1
+ rs = @ds.all
+ if @jdbc || @native
+ rs.should == [{:b=>[true, false], :d=>[d, nil], :t=>[t, nil], :ts=>[ts, nil], :tstz=>[ts, nil]}]
+ end
+ if @native
+ rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
+ rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
+ @ds.delete
+ @ds.insert(rs.first)
+ @ds.all.should == rs
+ end
+
+ @db.create_table!(:items) do
+ column :ba, 'bytea[]'
+ column :tz, 'timetz[]'
+ column :o, 'oid[]'
+ end
+ @tp.call.should == [:blob_array, :time_timezone_array, :integer_array]
+ @ds.insert( [Sequel.blob("a\0"), nil].pg_array(:bytea), [t, nil].pg_array(:timetz), [1, 2, 3].pg_array(:oid))
@ds.count.should == 1
if @native
rs = @ds.all
- rs.should == [{:b=>[true, false], :ba=>[Sequel.blob("a\0"), nil], :d=>[d, nil], :t=>[t, nil], :tz=>[t, nil], :ts=>[ts, nil], :tstz=>[ts, nil], :o=>[1, 2, 3]}]
+ rs.should == [{:ba=>[Sequel.blob("a\0"), nil], :tz=>[t, nil], :o=>[1, 2, 3]}]
rs.first.values.each{|v| v.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete

0 comments on commit 938299f

Please sign in to comment.