require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper") describe "pg_hstore extension" do before do Sequel.extension :pg_array, :pg_hstore @db = Sequel.connect('mock://postgres', :quote_identifiers=>false) @m = Sequel::Postgres @c = @m::HStore @db.extension :pg_hstore end it "should parse hstore strings correctly" do @c.parse('').to_hash.should == {} @c.parse('"a"=>"b"').to_hash.should == {'a'=>'b'} @c.parse('"a"=>"b", "c"=>NULL').to_hash.should == {'a'=>'b', 'c'=>nil} @c.parse('"a"=>"b", "c"=>"NULL"').to_hash.should == {'a'=>'b', 'c'=>'NULL'} @c.parse('"a"=>"b", "c"=>"\\\\ \\"\'=>"').to_hash.should == {'a'=>'b', 'c'=>'\ "\'=>'} end it "should cache parse results" do r = @c::Parser.new('') o = r.parse o.should == {} r.parse.should equal(o) end it "should literalize HStores to strings correctly" do @db.literal(Sequel.hstore({})).should == '\'\'::hstore' @db.literal(Sequel.hstore("a"=>"b")).should == '\'"a"=>"b"\'::hstore' @db.literal(Sequel.hstore("c"=>nil)).should == '\'"c"=>NULL\'::hstore' @db.literal(Sequel.hstore("c"=>'NULL')).should == '\'"c"=>"NULL"\'::hstore' @db.literal(Sequel.hstore('c'=>'\ "\'=>')).should == '\'"c"=>"\\\\ \\"\'\'=>"\'::hstore' ['\'"a"=>"b","c"=>"d"\'::hstore', '\'"c"=>"d","a"=>"b"\'::hstore'].should include(@db.literal(Sequel.hstore("a"=>"b","c"=>"d"))) end it "should have Sequel.hstore method for creating HStore instances" do Sequel.hstore({}).should be_a_kind_of(@c) end it "should have Sequel.hstore return HStores as-is" do a = Sequel.hstore({}) Sequel.hstore(a).should equal(a) end it "should HStore#to_hash method for getting underlying hash" do Sequel.hstore({}).to_hash.should be_a_kind_of(Hash) end it "should convert keys and values to strings on creation" do Sequel.hstore(1=>2).to_hash.should == {"1"=>"2"} end it "should convert keys and values to strings on assignment" do v = Sequel.hstore({}) v[1] = 2 v.to_hash.should == {"1"=>"2"} v.store(:'1', 3) v.to_hash.should == {"1"=>"3"} end it "should not convert nil values to strings on creation" do Sequel.hstore(:foo=>nil).to_hash.should == {"foo"=>nil} end it "should not convert nil values to strings on assignment" do v = Sequel.hstore({}) v[:foo] = nil v.to_hash.should == {"foo"=>nil} end it "should convert lookups by key to string" do Sequel.hstore('foo'=>'bar')[:foo].should == 'bar' Sequel.hstore('1'=>'bar')[1].should == 'bar' Sequel.hstore('foo'=>'bar').fetch(:foo).should == 'bar' Sequel.hstore('foo'=>'bar').fetch(:foo2, 2).should == 2 k = nil Sequel.hstore('foo2'=>'bar').fetch(:foo){|key| k = key }.should == 'foo' k.should == 'foo' Sequel.hstore('foo'=>'bar').has_key?(:foo).should be_true Sequel.hstore('foo'=>'bar').has_key?(:bar).should be_false Sequel.hstore('foo'=>'bar').key?(:foo).should be_true Sequel.hstore('foo'=>'bar').key?(:bar).should be_false Sequel.hstore('foo'=>'bar').member?(:foo).should be_true Sequel.hstore('foo'=>'bar').member?(:bar).should be_false Sequel.hstore('foo'=>'bar').include?(:foo).should be_true Sequel.hstore('foo'=>'bar').include?(:bar).should be_false Sequel.hstore('foo'=>'bar', '1'=>'2').values_at(:foo3, :foo, :foo2, 1).should == [nil, 'bar', nil, '2'] if RUBY_VERSION >= '1.9.0' Sequel.hstore('foo'=>'bar').assoc(:foo).should == ['foo', 'bar'] Sequel.hstore('foo'=>'bar').assoc(:foo2).should == nil end end it "should convert has_value?/value? lookups to string" do Sequel.hstore('foo'=>'bar').has_value?(:bar).should be_true Sequel.hstore('foo'=>'bar').has_value?(:foo).should be_false Sequel.hstore('foo'=>'bar').value?(:bar).should be_true Sequel.hstore('foo'=>'bar').value?(:foo).should be_false end it "should handle nil values in has_value?/value? lookups" do Sequel.hstore('foo'=>'').has_value?('').should be_true Sequel.hstore('foo'=>'').has_value?(nil).should be_false Sequel.hstore('foo'=>nil).has_value?(nil).should be_true end it "should have underlying hash convert lookups by key to string" do Sequel.hstore('foo'=>'bar').to_hash[:foo].should == 'bar' Sequel.hstore('1'=>'bar').to_hash[1].should == 'bar' end if RUBY_VERSION >= '1.9.0' it "should convert key lookups to string" do Sequel.hstore('foo'=>'bar').key(:bar).should == 'foo' Sequel.hstore('foo'=>'bar').key(:bar2).should be_nil end it "should handle nil values in key lookups" do Sequel.hstore('foo'=>'').key('').should == 'foo' Sequel.hstore('foo'=>'').key(nil).should == nil Sequel.hstore('foo'=>nil).key(nil).should == 'foo' end it "should convert rassoc lookups to string" do Sequel.hstore('foo'=>'bar').rassoc(:bar).should == ['foo', 'bar'] Sequel.hstore('foo'=>'bar').rassoc(:bar2).should be_nil end it "should handle nil values in rassoc lookups" do Sequel.hstore('foo'=>'').rassoc('').should == ['foo', ''] Sequel.hstore('foo'=>'').rassoc(nil).should == nil Sequel.hstore('foo'=>nil).rassoc(nil).should == ['foo', nil] end end it "should have delete convert key to string" do v = Sequel.hstore('foo'=>'bar') v.delete(:foo).should == 'bar' v.to_hash.should == {} end it "should handle #replace with hashes that do not use strings" do v = Sequel.hstore('foo'=>'bar') v.replace(:bar=>1) v.should be_a_kind_of(@c) v.should == {'bar'=>'1'} v.to_hash[:bar].should == '1' end it "should handle #merge with hashes that do not use strings" do v = Sequel.hstore('foo'=>'bar').merge(:bar=>1) v.should be_a_kind_of(@c) v.should == {'foo'=>'bar', 'bar'=>'1'} end it "should handle #merge/#update with hashes that do not use strings" do v = Sequel.hstore('foo'=>'bar') v.merge!(:bar=>1) v.should be_a_kind_of(@c) v.should == {'foo'=>'bar', 'bar'=>'1'} v = Sequel.hstore('foo'=>'bar') v.update(:bar=>1) v.should be_a_kind_of(@c) v.should == {'foo'=>'bar', 'bar'=>'1'} end it "should support using hstores as bound variables" do @db.bound_variable_arg(1, nil).should == 1 @db.bound_variable_arg({'1'=>'2'}, nil).should == '"1"=>"2"' @db.bound_variable_arg(Sequel.hstore('1'=>'2'), nil).should == '"1"=>"2"' @db.bound_variable_arg(Sequel.hstore('1'=>nil), nil).should == '"1"=>NULL' @db.bound_variable_arg(Sequel.hstore('1'=>"NULL"), nil).should == '"1"=>"NULL"' @db.bound_variable_arg(Sequel.hstore('1'=>"'\\ \"=>"), nil).should == '"1"=>"\'\\\\ \\"=>"' ['"a"=>"b","c"=>"d"', '"c"=>"d","a"=>"b"'].should include(@db.bound_variable_arg(Sequel.hstore("a"=>"b","c"=>"d"), nil)) end it "should parse hstore type from the schema correctly" do @db.fetch = [{:name=>'id', :db_type=>'integer'}, {:name=>'i', :db_type=>'hstore'}] @db.schema(:items).map{|e| e[1][:type]}.should == [:integer, :hstore] end it "should support typecasting for the hstore type" do h = Sequel.hstore(1=>2) @db.typecast_value(:hstore, h).should equal(h) @db.typecast_value(:hstore, {}).should be_a_kind_of(@c) @db.typecast_value(:hstore, {}).should == Sequel.hstore({}) @db.typecast_value(:hstore, {'a'=>'b'}).should == Sequel.hstore("a"=>"b") proc{@db.typecast_value(:hstore, [])}.should raise_error(Sequel::InvalidValue) end it "should be serializable" do v = Sequel.hstore('foo'=>'bar') dump = Marshal.dump(v) Marshal.load(dump).should == v end it "should return correct results for Database#schema_type_class" do @db.schema_type_class(:hstore).should == Sequel::Postgres::HStore @db.schema_type_class(:integer).should == Integer end end