Skip to content

Commit

Permalink
Make jdbc/postgres adapter convert array type elements (e.g. date[] a…
Browse files Browse the repository at this point in the history
…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
jeremyevans committed Jun 9, 2012
1 parent 4d5988a commit 938299f
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,7 @@
=== HEAD === 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_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) * Make the pg_json extension handle json[] type when used with the pg_array extension (jeremyevans)
Expand Down
34 changes: 26 additions & 8 deletions lib/sequel/adapters/jdbc/postgresql.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -45,32 +45,50 @@ class Dataset < JDBC::Dataset
APOS = Dataset::APOS APOS = Dataset::APOS


class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR 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 # Convert Java::OrgPostgresqlUtil::PGobject to ruby strings
def pg_object(v) def pg_object(v)
v.to_string v.to_string
end 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 private


# Handle multi-dimensional Java arrays by recursively mapping them # Handle multi-dimensional Java arrays by recursively mapping them
# to ruby arrays. # to ruby arrays of ruby values.
def _pg_array(v) def _pg_array(v)
v.to_ary.map do |i| v.to_ary.map do |i|
if i.respond_to?(:to_ary) if i.respond_to?(:to_ary)
_pg_array(i) _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 else
i i
end end
end end
end end
end end


PG_ARRAY_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_array)
PG_OBJECT_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_object) PG_OBJECT_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_object)


# Add the shared PostgreSQL prepared statement methods # Add the shared PostgreSQL prepared statement methods
Expand All @@ -88,7 +106,7 @@ def prepare(*args)
def convert_type_proc(v) def convert_type_proc(v)
case v case v
when Java::OrgPostgresqlJdbc4::Jdbc4Array when Java::OrgPostgresqlJdbc4::Jdbc4Array
PG_ARRAY_METHOD PGArrayConverter.new(method(:convert_type_proc))
when Java::OrgPostgresqlUtil::PGobject when Java::OrgPostgresqlUtil::PGobject
PG_OBJECT_METHOD PG_OBJECT_METHOD
else else
Expand Down
79 changes: 58 additions & 21 deletions spec/adapters/postgres_spec.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1354,6 +1354,7 @@ def @ds.check_return
@db.extend Sequel::Postgres::PGArray::DatabaseMethods @db.extend Sequel::Postgres::PGArray::DatabaseMethods
@ds = @db[:items] @ds = @db[:items]
@native = POSTGRES_DB.adapter_scheme == :postgres @native = POSTGRES_DB.adapter_scheme == :postgres
@jdbc = POSTGRES_DB.adapter_scheme == :jdbc
@tp = lambda{@db.schema(:items).map{|a| a.last[:type]}} @tp = lambda{@db.schema(:items).map{|a| a.last[:type]}}
end end
after do after do
Expand All @@ -1371,19 +1372,26 @@ def @ds.check_return
@tp.call.should == [:integer_array, :integer_array, :bigint_array, :float_array, :float_array] @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.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 @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]}] 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.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)} rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete @ds.delete
@ds.insert(rs.first) @ds.insert(rs.first)
@ds.all.should == rs @ds.all.should == rs
end


@ds.delete @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")) @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
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]]}] 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.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)} rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete @ds.delete
Expand All @@ -1399,19 +1407,25 @@ def @ds.check_return
@tp.call.should == [:decimal_array] @tp.call.should == [:decimal_array]
@ds.insert([BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')].pg_array(:numeric)) @ds.insert([BigDecimal.new('1.000000000000000000001'), nil, BigDecimal.new('1')].pg_array(:numeric))
@ds.count.should == 1 @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')]}] 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.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)} rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete @ds.delete
@ds.insert(rs.first) @ds.insert(rs.first)
@ds.all.should == rs @ds.all.should == rs
end


@ds.delete @ds.delete
@ds.insert([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]].pg_array(:numeric)) @ds.insert([[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]].pg_array(:numeric))
rs = @ds.all rs = @ds.all
if @jdbc || @native
rs.should == [{:n=>[[BigDecimal.new('1.0000000000000000000000000000001'), nil], [nil, BigDecimal.new('1')]]}] 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.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)} rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete @ds.delete
Expand All @@ -1429,19 +1443,25 @@ def @ds.check_return
@tp.call.should == [:string_array, :string_array, :string_array] @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.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 @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']}] 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.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)} rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete @ds.delete
@ds.insert(rs.first) @ds.insert(rs.first)
@ds.all.should == rs @ds.all.should == rs
end


@ds.delete @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)) @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 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']]]}] 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.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)} rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete @ds.delete
Expand All @@ -1453,25 +1473,42 @@ def @ds.check_return
specify 'insert and retrieve arrays of other types' do specify 'insert and retrieve arrays of other types' do
@db.create_table!(:items) do @db.create_table!(:items) do
column :b, 'bool[]' column :b, 'bool[]'
column :ba, 'bytea[]'
column :d, 'date[]' column :d, 'date[]'
column :t, 'time[]' column :t, 'time[]'
column :tz, 'timetz[]'
column :ts, 'timestamp[]' column :ts, 'timestamp[]'
column :tstz, 'timestamptz[]' column :tstz, 'timestamptz[]'
column :o, 'oid[]'
end 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 d = Date.today
t = Sequel::SQLTime.create(10, 20, 30) t = Sequel::SQLTime.create(10, 20, 30)
ts = Time.local(2011, 1, 2, 3, 4, 5) 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 @ds.count.should == 1
if @native if @native
rs = @ds.all 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.should_not be_a_kind_of(Array)}
rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)} rs.first.values.each{|v| v.to_a.should be_a_kind_of(Array)}
@ds.delete @ds.delete
Expand Down

0 comments on commit 938299f

Please sign in to comment.