diff options
Diffstat (limited to 'activerecord/test/cases/adapters/postgresql/hstore_test.rb')
-rw-r--r-- | activerecord/test/cases/adapters/postgresql/hstore_test.rb | 427 |
1 files changed, 311 insertions, 116 deletions
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 23bafde17b..00ff456e16 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -1,157 +1,352 @@ # encoding: utf-8 - require "cases/helper" -require 'active_record/base' -require 'active_record/connection_adapters/postgresql_adapter' +require 'support/schema_dumping_helper' -class PostgresqlHstoreTest < ActiveRecord::TestCase - class Hstore < ActiveRecord::Base - self.table_name = 'hstores' - end +if ActiveRecord::Base.connection.supports_extensions? + class PostgresqlHstoreTest < ActiveRecord::TestCase + include SchemaDumpingHelper + class Hstore < ActiveRecord::Base + self.table_name = 'hstores' + + store_accessor :settings, :language, :timezone + end + + def setup + @connection = ActiveRecord::Base.connection + + unless @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + @connection.commit_db_transaction + end + + @connection.reconnect! - def setup - @connection = ActiveRecord::Base.connection - begin @connection.transaction do @connection.create_table('hstores') do |t| t.hstore 'tags', :default => '' + t.hstore 'payload', array: true + t.hstore 'settings' end end - rescue ActiveRecord::StatementInvalid - return skip "do not test on PG without hstore" + @column = Hstore.columns_hash['tags'] end - @column = Hstore.columns.find { |c| c.name == 'tags' } - end - def teardown - @connection.execute 'drop table if exists hstores' - end + teardown do + @connection.execute 'drop table if exists hstores' + end - def test_column - assert_equal :hstore, @column.type - end + def test_hstore_included_in_extensions + assert @connection.respond_to?(:extensions), "connection should have a list of extensions" + assert @connection.extensions.include?('hstore'), "extension list should include hstore" + end - def test_type_cast_hstore - assert @column + def test_disable_enable_hstore + assert @connection.extension_enabled?('hstore') + @connection.disable_extension 'hstore' + assert_not @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + assert @connection.extension_enabled?('hstore') + ensure + # Restore column(s) dropped by `drop extension hstore cascade;` + load_schema + end - data = "\"1\"=>\"2\"" - hash = @column.class.string_to_hstore data - assert_equal({'1' => '2'}, hash) - assert_equal({'1' => '2'}, @column.type_cast(data)) + def test_column + assert_equal :hstore, @column.type + assert_equal "hstore", @column.sql_type + assert_not @column.number? + assert_not @column.binary? + assert_not @column.array + end - assert_equal({}, @column.type_cast("")) - assert_equal({'key'=>nil}, @column.type_cast('key => NULL')) - assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b"))) - end + def test_default + @connection.add_column 'hstores', 'permissions', :hstore, default: '"users"=>"read", "articles"=>"write"' + Hstore.reset_column_information - def test_gen1 - assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''})) - end + assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.column_defaults['permissions']) + assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.new.permissions) + ensure + Hstore.reset_column_information + end - def test_gen2 - assert_equal(%q(","=>""), @column.class.hstore_to_string({','=>''})) - end + def test_change_table_supports_hstore + @connection.transaction do + @connection.change_table('hstores') do |t| + t.hstore 'users', default: '' + end + Hstore.reset_column_information + column = Hstore.columns_hash['users'] + assert_equal :hstore, column.type - def test_gen3 - assert_equal(%q("="=>""), @column.class.hstore_to_string({'='=>''})) - end + raise ActiveRecord::Rollback # reset the schema change + end + ensure + Hstore.reset_column_information + end - def test_gen4 - assert_equal(%q(">"=>""), @column.class.hstore_to_string({'>'=>''})) - end + def test_hstore_migration + hstore_migration = Class.new(ActiveRecord::Migration) do + def change + change_table("hstores") do |t| + t.hstore :keys + end + end + end - def test_parse1 - assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c')) - end + hstore_migration.new.suppress_messages do + hstore_migration.migrate(:up) + assert_includes @connection.columns(:hstores).map(&:name), "keys" + hstore_migration.migrate(:down) + assert_not_includes @connection.columns(:hstores).map(&:name), "keys" + end + end - def test_parse2 - assert_equal({" " => " "}, @column.type_cast("\\ =>\\ ")) - end + def test_cast_value_on_write + x = Hstore.new tags: {"bool" => true, "number" => 5} + assert_equal({"bool" => true, "number" => 5}, x.tags_before_type_cast) + assert_equal({"bool" => "true", "number" => "5"}, x.tags) + x.save + assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags) + end - def test_parse3 - assert_equal({"=" => ">"}, @column.type_cast("==>>")) - end + def test_type_cast_hstore + assert_equal({'1' => '2'}, @column.type_cast_from_database("\"1\"=>\"2\"")) + assert_equal({}, @column.type_cast_from_database("")) + assert_equal({'key'=>nil}, @column.type_cast_from_database('key => NULL')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast_from_database(%q(c=>"}", "\"a\""=>"b \"a b"))) + end - def test_parse4 - assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w')) - end + def test_with_store_accessors + x = Hstore.new(language: "fr", timezone: "GMT") + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - def test_parse5 - assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w')) - end + x.save! + x = Hstore.first + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - def test_parse6 - assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w')) - end + x.language = "de" + x.save! - def test_parse7 - assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w')) - end + x = Hstore.first + assert_equal "de", x.language + assert_equal "GMT", x.timezone + end - def test_rewrite - @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.first - x.tags = { '"a\'' => 'b' } - assert x.save! - end + def test_duplication_with_store_accessors + x = Hstore.new(language: "fr", timezone: "GMT") + assert_equal "fr", x.language + assert_equal "GMT", x.timezone + y = x.dup + assert_equal "fr", y.language + assert_equal "GMT", y.timezone + end - def test_select - @connection.execute "insert into hstores (tags) VALUES ('1=>2')" - x = Hstore.first - assert_equal({'1' => '2'}, x.tags) - end + def test_yaml_round_trip_with_store_accessors + x = Hstore.new(language: "fr", timezone: "GMT") + assert_equal "fr", x.language + assert_equal "GMT", x.timezone - def test_select_multikey - @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" - x = Hstore.first - assert_equal({'1' => '2', '2' => '3'}, x.tags) - end + y = YAML.load(YAML.dump(x)) + assert_equal "fr", y.language + assert_equal "GMT", y.timezone + end - def test_create - assert_cycle('a' => 'b', '1' => '2') - end + def test_changes_in_place + hstore = Hstore.create!(settings: { 'one' => 'two' }) + hstore.settings['three'] = 'four' + hstore.save! + hstore.reload - def test_nil - assert_cycle('a' => nil) - end + assert_equal 'four', hstore.settings['three'] + assert_not hstore.changed? + end - def test_quotes - assert_cycle('a' => 'b"ar', '1"foo' => '2') - end + def test_gen1 + assert_equal(%q(" "=>""), @column.cast_type.type_cast_for_database({' '=>''})) + end - def test_whitespace - assert_cycle('a b' => 'b ar', '1"foo' => '2') - end + def test_gen2 + assert_equal(%q(","=>""), @column.cast_type.type_cast_for_database({','=>''})) + end - def test_backslash - assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') - end + def test_gen3 + assert_equal(%q("="=>""), @column.cast_type.type_cast_for_database({'='=>''})) + end - def test_comma - assert_cycle('a, b' => 'bar', '1"foo' => '2') - end + def test_gen4 + assert_equal(%q(">"=>""), @column.cast_type.type_cast_for_database({'>'=>''})) + end - def test_arrow - assert_cycle('a=>b' => 'bar', '1"foo' => '2') - end + def test_parse1 + assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast_from_database('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + end - def test_quoting_special_characters - assert_cycle('ca' => 'cà', 'ac' => 'àc') - end + def test_parse2 + assert_equal({" " => " "}, @column.type_cast_from_database("\\ =>\\ ")) + end - private - def assert_cycle hash - # test creation - x = Hstore.create!(:tags => hash) - x.reload - assert_equal(hash, x.tags) - - # test updating - x = Hstore.create!(:tags => {}) - x.tags = hash - x.save! - x.reload - assert_equal(hash, x.tags) + def test_parse3 + assert_equal({"=" => ">"}, @column.type_cast_from_database("==>>")) + end + + def test_parse4 + assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('\=a=>q=w')) + end + + def test_parse5 + assert_equal({"=a"=>"q=w"}, @column.type_cast_from_database('"=a"=>q\=w')) + end + + def test_parse6 + assert_equal({"\"a"=>"q>w"}, @column.type_cast_from_database('"\"a"=>q>w')) + end + + def test_parse7 + assert_equal({"\"a"=>"q\"w"}, @column.type_cast_from_database('\"a=>q"w')) + end + + def test_rewrite + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + x.tags = { '"a\'' => 'b' } + assert x.save! + end + + def test_select + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.first + assert_equal({'1' => '2'}, x.tags) + end + + def test_array_cycle + assert_array_cycle([{"AA" => "BB", "CC" => "DD"}, {"AA" => nil}]) + end + + def test_array_strings_with_quotes + assert_array_cycle([{'this has' => 'some "s that need to be escaped"'}]) + end + + def test_array_strings_with_commas + assert_array_cycle([{'this,has' => 'many,values'}]) + end + + def test_array_strings_with_array_delimiters + assert_array_cycle(['{' => '}']) + end + + def test_array_strings_with_null_strings + assert_array_cycle([{'NULL' => 'NULL'}]) + end + + def test_contains_nils + assert_array_cycle([{'NULL' => nil}]) + end + + def test_select_multikey + @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" + x = Hstore.first + assert_equal({'1' => '2', '2' => '3'}, x.tags) + end + + def test_create + assert_cycle('a' => 'b', '1' => '2') + end + + def test_nil + assert_cycle('a' => nil) + end + + def test_quotes + assert_cycle('a' => 'b"ar', '1"foo' => '2') + end + + def test_whitespace + assert_cycle('a b' => 'b ar', '1"foo' => '2') + end + + def test_backslash + assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') + end + + def test_comma + assert_cycle('a, b' => 'bar', '1"foo' => '2') + end + + def test_arrow + assert_cycle('a=>b' => 'bar', '1"foo' => '2') + end + + def test_quoting_special_characters + assert_cycle('ca' => 'cà', 'ac' => 'àc') + end + + def test_multiline + assert_cycle("a\nb" => "c\nd") + end + + class TagCollection + def initialize(hash); @hash = hash end + def to_hash; @hash end + def self.load(hash); new(hash) end + def self.dump(object); object.to_hash end + end + + class HstoreWithSerialize < Hstore + serialize :tags, TagCollection + end + + def test_hstore_with_serialized_attributes + HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) + record = HstoreWithSerialize.first + assert_instance_of TagCollection, record.tags + assert_equal({"one" => "two"}, record.tags.to_hash) + record.tags = TagCollection.new("three" => "four") + record.save! + assert_equal({"three" => "four"}, HstoreWithSerialize.first.tags.to_hash) + end + + def test_clone_hstore_with_serialized_attributes + HstoreWithSerialize.create! tags: TagCollection.new({"one" => "two"}) + record = HstoreWithSerialize.first + dupe = record.dup + assert_equal({"one" => "two"}, dupe.tags.to_hash) + end + + def test_schema_dump_with_shorthand + output = dump_table_schema("hstores") + assert_match %r[t.hstore "tags",\s+default: {}], output + end + + private + def assert_array_cycle(array) + # test creation + x = Hstore.create!(payload: array) + x.reload + assert_equal(array, x.payload) + + # test updating + x = Hstore.create!(payload: []) + x.payload = array + x.save! + x.reload + assert_equal(array, x.payload) + end + + def assert_cycle(hash) + # test creation + x = Hstore.create!(:tags => hash) + x.reload + assert_equal(hash, x.tags) + + # test updating + x = Hstore.create!(:tags => {}) + x.tags = hash + x.save! + x.reload + assert_equal(hash, x.tags) + end end end |