Skip to content

Commit 9329d0b

Browse files
committed
[postgres] hstore values should be returned as Hash instances (fixes #454)
1 parent 85d815d commit 9329d0b

File tree

2 files changed

+60
-5
lines changed

2 files changed

+60
-5
lines changed

src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@
3636
import java.sql.Timestamp;
3737
import java.sql.Types;
3838
import java.util.List;
39+
import java.util.Map;
3940
import java.util.UUID;
4041

4142
import org.jruby.Ruby;
4243
import org.jruby.RubyArray;
4344
import org.jruby.RubyBoolean;
4445
import org.jruby.RubyClass;
4546
import org.jruby.RubyFloat;
47+
import org.jruby.RubyHash;
4648
import org.jruby.RubyIO;
4749
import org.jruby.RubyString;
4850
import org.jruby.anno.JRubyMethod;
@@ -388,13 +390,23 @@ protected String resolveArrayBaseTypeName(final ThreadContext context,
388390
return sqlType;
389391
}
390392

393+
private static final int HSTORE_TYPE = 100000 + 1111;
394+
391395
@Override
392396
protected int jdbcTypeFor(final ThreadContext context, final Ruby runtime,
393397
final IRubyObject column, final Object value) throws SQLException {
394398
// NOTE: likely wrong but native adapters handles this thus we should
395399
// too - used from #table_exists? `binds << [ nil, schema ] if schema`
396400
if ( column == null || column.isNil() ) return Types.VARCHAR; // assume type == :string
397-
return super.jdbcTypeFor(context, runtime, column, value);
401+
final int type = super.jdbcTypeFor(context, runtime, column, value);
402+
/*
403+
if ( type == Types.OTHER ) {
404+
final IRubyObject columnType = column.callMethod(context, "type");
405+
if ( "hstore" == (Object) columnType.asJavaString() ) {
406+
return HSTORE_TYPE;
407+
}
408+
} */
409+
return type;
398410
}
399411

400412
/**
@@ -467,6 +479,7 @@ protected IRubyObject arrayToRuby(final ThreadContext context,
467479
protected IRubyObject objectToRuby(final ThreadContext context,
468480
final Ruby runtime, final ResultSet resultSet, final int column)
469481
throws SQLException {
482+
470483
final Object object = resultSet.getObject(column);
471484

472485
if ( object == null && resultSet.wasNull() ) return runtime.getNil();
@@ -485,6 +498,16 @@ protected IRubyObject objectToRuby(final ThreadContext context,
485498
return runtime.newString( object.toString() );
486499
}
487500

501+
if ( object instanceof Map ) { // hstore
502+
if ( rawHstoreType ) {
503+
return runtime.newString( resultSet.getString(column) );
504+
}
505+
// by default we avoid double parsing by driver and than column :
506+
final RubyHash rubyObject = RubyHash.newHash(runtime);
507+
rubyObject.putAll((Map) object); // converts keys/values to ruby
508+
return rubyObject;
509+
}
510+
488511
return JavaUtil.convertJavaToRuby(runtime, object);
489512
}
490513

@@ -529,6 +552,24 @@ private String formatInterval(final Object object) {
529552
return str.toString();
530553
}
531554

555+
protected static boolean rawHstoreType = Boolean.getBoolean("arjdbc.postgresql.hstore.raw");
556+
557+
@JRubyMethod(name = "raw_hstore_type?")
558+
public static IRubyObject useRawHstoreType(final ThreadContext context, final IRubyObject self) {
559+
return context.getRuntime().newBoolean(rawHstoreType);
560+
}
561+
562+
@JRubyMethod(name = "raw_hstore_type=")
563+
public static IRubyObject setRawHstoreType(final IRubyObject self, final IRubyObject value) {
564+
if ( value instanceof RubyBoolean ) {
565+
rawHstoreType = ((RubyBoolean) value).isTrue();
566+
}
567+
else {
568+
rawHstoreType = ! value.isNil();
569+
}
570+
return value;
571+
}
572+
532573
// whether to use "raw" interval values off by default - due native adapter compatibilty :
533574
// RAW values :
534575
// - 2 years 0 mons 0 days 0 hours 3 mins 0.00 secs
@@ -549,7 +590,7 @@ public static IRubyObject setRawIntervalType(final IRubyObject self, final IRuby
549590
rawIntervalType = ((RubyBoolean) value).isTrue();
550591
}
551592
else {
552-
rawIntervalType = value.isNil();
593+
rawIntervalType = ! value.isNil();
553594
}
554595
return value;
555596
}

test/db/postgresql/hstore_test.rb

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'db/postgres'
44

55
class PostgresqlHstoreTest < Test::Unit::TestCase
6-
6+
77
class Hstore < ActiveRecord::Base
88
self.table_name = 'hstores'
99
end
@@ -133,17 +133,31 @@ def test_rewrite
133133
assert x.save!
134134
end
135135

136-
137136
def test_select
138137
@connection.execute "insert into hstores (tags) VALUES ('1=>2')"
139138
x = Hstore.first
140139
assert_equal({'1' => '2'}, x.tags)
140+
assert_instance_of Hash, x.tags
141141
end
142142

143143
def test_select_multikey
144144
@connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
145145
x = Hstore.first
146146
assert_equal({'1' => '2', '2' => '3'}, x.tags)
147+
assert_instance_of Hash, x.tags
148+
end
149+
150+
class Hstore2 < ActiveRecord::Base
151+
self.table_name = 'hstores'
152+
store :tags, :accessors => [ :name ]
153+
end
154+
155+
def test_store_select
156+
@connection.execute "insert into hstores (tags) VALUES ('name=>ferko,type=>suska')"
157+
x = Hstore2.first
158+
assert_equal 'ferko', x.name
159+
assert_equal 'suska', x.tags[:type]
160+
assert_instance_of ActiveSupport::HashWithIndifferentAccess, x.tags
147161
end
148162

149163
def test_create
@@ -196,5 +210,5 @@ def assert_cycle hash
196210
x.reload
197211
assert_equal(hash, x.tags)
198212
end
199-
213+
200214
end if Test::Unit::TestCase.ar_version('4.0')

0 commit comments

Comments
 (0)