diff options
Diffstat (limited to 'activerecord')
13 files changed, 119 insertions, 15 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 62a6b856f2..16460ec36d 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 4.0.0 (unreleased) ## +* PostgreSQL geometric type point is supported by ActiveRecord. Fixes #7324. + + *Martin Schuerrer* + * Add an `add_index` override in Postgresql adapter and MySQL adapter to allow custom index type support. Fixes #6101. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 1f111a0976..71f5c8fd29 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -599,6 +599,10 @@ module ActiveRecord self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end + def valid_type?(type) + !native_database_types[type].nil? + end + protected # MySQL is too stupid to create a temporary table for use subquery, so we have diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index fcbd8fd88a..14ef07a75e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -2,6 +2,17 @@ module ActiveRecord module ConnectionAdapters class PostgreSQLColumn < Column module Cast + def point_to_string(point) + "(#{point[0]},#{point[1]})" + end + + def string_to_point(string) + if string[0] == '(' && string[-1] == ')' + string = string[1...-1] + end + string.split(',').map{ |v| Float(v) } + end + def string_to_time(string) return string unless String === string diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 68f2f2ca7b..51f377dfd7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -63,6 +63,16 @@ module ActiveRecord end end + class Point < Type + def type_cast(value) + if String === value + ConnectionAdapters::PostgreSQLColumn.string_to_point value + else + value + end + end + end + class Array < Type attr_reader :subtype def initialize(subtype) @@ -330,6 +340,7 @@ module ActiveRecord register_type 'time', OID::Time.new register_type 'path', OID::Identity.new + register_type 'point', OID::Point.new register_type 'polygon', OID::Identity.new register_type 'circle', OID::Identity.new register_type 'hstore', OID::Hstore.new diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 49b93e5626..61e5e76f83 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -28,10 +28,14 @@ module ActiveRecord super end when Array - if column.array - "'#{PostgreSQLColumn.array_to_string(value, column, self)}'" + case sql_type + when 'point' then super(PostgreSQLColumn.point_to_string(value)) else - super + if column.array + "'#{PostgreSQLColumn.array_to_string(value, column, self)}'" + else + super + end end when Hash case sql_type @@ -79,9 +83,10 @@ module ActiveRecord def type_cast(value, column, array_member = false) return super(value, column) unless column + sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale) case value when Range - return super(value, column) unless /range$/ =~ column.sql_type + return super(value, column) unless /range$/ =~ sql_type PostgreSQLColumn.range_to_string(value) when NilClass if column.array && array_member @@ -92,19 +97,23 @@ module ActiveRecord super(value, column) end when Array - return super(value, column) unless column.array - PostgreSQLColumn.array_to_string(value, column, self) + case sql_type + when 'point' then PostgreSQLColumn.point_to_string(value) + else + return super(value, column) unless column.array + PostgreSQLColumn.array_to_string(value, column, self) + end when String - return super(value, column) unless 'bytea' == column.sql_type + return super(value, column) unless 'bytea' == sql_type { :value => value, :format => 1 } when Hash - case column.sql_type + case sql_type when 'hstore' then PostgreSQLColumn.hstore_to_string(value) when 'json' then PostgreSQLColumn.json_to_string(value) else super(value, column) end when IPAddr - return super(value, column) unless ['inet','cidr'].include? column.sql_type + return super(value, column) unless ['inet','cidr'].include? sql_type PostgreSQLColumn.cidr_to_string(value) else super(value, column) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 6f1eb25990..d26b676335 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -663,6 +663,10 @@ module ActiveRecord @use_insert_returning end + def valid_type?(type) + !native_database_types[type].nil? + end + protected # Returns the version of the connected PostgreSQL server. @@ -707,7 +711,14 @@ module ActiveRecord # populate composite types nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row| - vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i] + if OID.registered_type? row['typname'] + # this composite type is explicitly registered + vector = OID::NAMES[row['typname']] + else + # use the default for composite types + vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i] + end + OID::TYPE_MAP[row['oid'].to_i] = vector end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 50d189d27a..c3bebf7a5e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -499,6 +499,10 @@ module ActiveRecord rename_column_indexes(table_name, column_name, new_column_name) end + def valid_type?(type) + true + end + protected def select(sql, name = nil, binds = []) #:nodoc: exec_query(sql, name, binds) diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index df090b972d..fa9de926c5 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -118,7 +118,7 @@ HEADER # then dump all non-primary key columns column_specs = columns.map do |column| - raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil? + raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type) next if column.name == pk @connection.column_spec(column, @types) end.compact diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 0eb1cc511e..a75883cd3a 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -16,6 +16,15 @@ module ActiveRecord eosql end + def test_valid_column + column = @conn.columns('ex').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end + + def test_invalid_column + assert_not @conn.valid_type?(:foobar) + end + def test_client_encoding assert_equal Encoding::UTF_8, @conn.client_encoding end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 05e0f0e192..17d77c5454 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -10,6 +10,15 @@ module ActiveRecord @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end + def test_valid_column + column = @connection.columns('ex').find { |col| col.name == 'id' } + assert @connection.valid_type?(column.type) + end + + def test_invalid_column + assert_not @connection.valid_type?(:foobar) + end + def test_primary_key assert_equal 'id', @connection.primary_key('ex') end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 003052bac4..dcbec24acf 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -25,6 +25,15 @@ module ActiveRecord @conn.intercepted = true end + def test_valid_column + column = @conn.columns('items').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end + + def test_invalid_column + assert @conn.valid_type?(:foobar) + end + def teardown @conn.intercepted = false @conn.logged = [] diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 7fb50e9617..f9524bc6bd 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -840,7 +840,7 @@ class BasicsTest < ActiveRecord::TestCase # Reload and check that we have all the geometric attributes. h = Geometric.find(g.id) - assert_equal '(5,6.1)', h.a_point + assert_equal [5.0, 6.1], h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path @@ -869,7 +869,7 @@ class BasicsTest < ActiveRecord::TestCase # Reload and check that we have all the geometric attributes. h = Geometric.find(g.id) - assert_equal '(5,6.1)', h.a_point + assert_equal [5.0, 6.1], h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path @@ -880,6 +880,29 @@ class BasicsTest < ActiveRecord::TestCase objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id] assert_equal true, objs[0].isclosed + + # test native ruby formats when defining the geometric types + g = Geometric.new( + :a_point => [5.0, 6.1], + #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql + :a_line_segment => '((2.0, 3), (5.5, 7.0))', + :a_box => '(2.0, 3), (5.5, 7.0)', + :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path + :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0', + :a_circle => '((5.3, 10.4), 2)' + ) + + assert g.save + + # Reload and check that we have all the geometric attributes. + h = Geometric.find(g.id) + + assert_equal [5.0, 6.1], h.a_point + assert_equal '[(2,3),(5.5,7)]', h.a_line_segment + assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner + assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path + assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon + assert_equal '<(5.3,10.4),2>', h.a_circle end end @@ -1024,7 +1047,7 @@ class BasicsTest < ActiveRecord::TestCase Joke.reset_sequence_name end - def test_dont_clear_inheritnce_column_when_setting_explicitly + def test_dont_clear_inheritance_column_when_setting_explicitly Joke.inheritance_column = "my_type" before_inherit = Joke.inheritance_column diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 7042d7f4b6..b6e140b912 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -806,7 +806,7 @@ module NestedAttributesOnACollectionAssociationTests assert_nothing_raised(NoMethodError) { @pirate.save! } end - def test_numeric_colum_changes_from_zero_to_no_empty_string + def test_numeric_column_changes_from_zero_to_no_empty_string Man.accepts_nested_attributes_for(:interests) repair_validations(Interest) do |