From 32c194aa327f08b48e4abc476a14a9fb1dad22db Mon Sep 17 00:00:00 2001 From: Pan Thomakos Date: Fri, 23 Sep 2011 11:01:52 -0700 Subject: [PATCH 1/4] Added JDBCMysql Adapter and other improvements. - Upgraded the gem structure to use a more generic rubygems gemspec. - Added a Gemfile to install development dependencies for easier testing. - Added bundler require to simplify code in specs and lib. - Updated the README.rdoc file to include testing information. - Added support for the jdbcmysql adapter. - Consolidated common mysql code and tests to conform to DRY principles. There was a lot of replicated code in tests and mysql related files that is now shared in modules. - Upgraded tests to be compliant with the new rspec format. - Upgraded tests to be compliant with the new SchemaDumper format. - Removed jeweler related gemspec that was hacked into the Rakefile. - Added a .gitignore file. --- .gitignore | 3 + Gemfile | 3 + README.rdoc | 49 +- Rakefile | 43 +- lib/spatial_adapter.rb | 10 +- lib/spatial_adapter/base/mysql.rb | 9 + lib/spatial_adapter/base/mysql/adapter.rb | 54 ++ .../base/mysql/spatial_column.rb | 12 + lib/spatial_adapter/common.rb | 10 + lib/spatial_adapter/common/schema_dumper.rb | 24 +- lib/spatial_adapter/common/spatial_column.rb | 8 +- .../common/table_definition.rb | 18 +- lib/spatial_adapter/jdbcmysql.rb | 50 ++ lib/spatial_adapter/mysql.rb | 128 ++-- lib/spatial_adapter/mysql2.rb | 125 ++-- lib/spatial_adapter/postgresql.rb | 639 +++++++++--------- lib/spatial_adapter/version.rb | 3 + spatial_adapter.gemspec | 89 +-- spec/README.txt | 22 - spec/db/jdbcmysql_raw.rb | 70 ++ spec/jdbcmysql_spec.rb | 25 + spec/mysql/connection_adapter_spec.rb | 106 --- spec/mysql/models_spec.rb | 65 -- spec/mysql2/connection_adapter_spec.rb | 106 --- spec/mysql2/migration_spec.rb | 64 -- spec/mysql2/models_spec.rb | 65 -- spec/mysql2/schema_dumper_spec.rb | 56 -- spec/mysql2_spec.rb | 31 + spec/mysql_spec.rb | 17 + spec/postgresql/connection_adapter_spec.rb | 73 +- spec/postgresql/migration_spec.rb | 102 +-- spec/postgresql/models_spec.rb | 74 +- spec/postgresql/schema_dumper_spec.rb | 24 +- spec/postgresql_spec.rb | 5 + .../common_model_actions_spec.rb} | 4 +- spec/shared/mysql_connection_adapter_spec.rb | 110 +++ .../mysql_migration_spec.rb} | 34 +- spec/shared/mysql_models_spec.rb | 58 ++ .../mysql_schema_dumper_spec.rb} | 51 +- spec/spec_helper.rb | 46 +- 40 files changed, 1151 insertions(+), 1334 deletions(-) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 lib/spatial_adapter/base/mysql.rb create mode 100644 lib/spatial_adapter/base/mysql/adapter.rb create mode 100644 lib/spatial_adapter/base/mysql/spatial_column.rb create mode 100644 lib/spatial_adapter/common.rb create mode 100644 lib/spatial_adapter/jdbcmysql.rb create mode 100644 lib/spatial_adapter/version.rb delete mode 100644 spec/README.txt create mode 100644 spec/db/jdbcmysql_raw.rb create mode 100644 spec/jdbcmysql_spec.rb delete mode 100644 spec/mysql/connection_adapter_spec.rb delete mode 100644 spec/mysql/models_spec.rb delete mode 100644 spec/mysql2/connection_adapter_spec.rb delete mode 100644 spec/mysql2/migration_spec.rb delete mode 100644 spec/mysql2/models_spec.rb delete mode 100644 spec/mysql2/schema_dumper_spec.rb create mode 100644 spec/mysql2_spec.rb create mode 100644 spec/mysql_spec.rb create mode 100644 spec/postgresql_spec.rb rename spec/{shared_examples.rb => shared/common_model_actions_spec.rb} (96%) create mode 100644 spec/shared/mysql_connection_adapter_spec.rb rename spec/{mysql/migration_spec.rb => shared/mysql_migration_spec.rb} (69%) create mode 100644 spec/shared/mysql_models_spec.rb rename spec/{mysql/schema_dumper_spec.rb => shared/mysql_schema_dumper_spec.rb} (56%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f2d714 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.rvmrc +Gemfile.lock +pkg/ diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c80ee36 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "http://rubygems.org" + +gemspec diff --git a/README.rdoc b/README.rdoc index 118beff..0523cd9 100644 --- a/README.rdoc +++ b/README.rdoc @@ -16,7 +16,7 @@ The following gems are required: For PostgreSQL: - PostGIS version 1.4.0 or higher should be installed in your database - + == Installation Choose ONE of the following installation methods. You shouldn't have to do both. @@ -27,15 +27,15 @@ This is the preferred method of installation, and will pull in the required dependencies as well. gem install spatial_adapter - + In a Rails 2.x app, you can add a gem dependency in environment.rb: config.gem 'spatial_adapter' - + In a Rails 3 app, add a gem dependency to Gemfile: gem 'spatial_adapter' - + === As a Rails Plugin In your Rails project, run the following: @@ -50,18 +50,20 @@ Choose the database type for which you would like to use spatial_adapter, and load each with require 'spatial_adapter/[database]' - + where [database] should be replaced with one of the following: - postgresql - mysql - +- mysql2 +- jdbcmysql + For example to use the PostgreSQL spatial adapter: require 'spatial_adapter/postgresql' - -In a Rails app, spatial_adapter will automatically load the adapter for the database -specified in your database.yml configuration. + +In a Rails app, spatial_adapter will automatically load the adapter for the +database specified in your database.yml configuration. == Operations @@ -73,13 +75,13 @@ and loaded in migrations the same way as columns of basic types. Here is an example of code for the creation of a table with a geometric column in PostGIS, along with the addition of a spatial index on the column: - + ActiveRecord::Schema.define do create_table :table_points, :force => true do |t| t.string :data t.point :geom, :null => false, :srid => 123, :with_z => true end - + add_index :table_points, :geom, :spatial => true end @@ -90,15 +92,15 @@ Here is a related statement valid for MySql version <= 5.0.16: t.string :data t.point :geom, :null => false end - + add_index :table_points, :geom, :spatial => true end - + === Differences Between Databases -- On all versions of MySQL, the :srid, :with_z, and :with_m options are ignored, since - they are not supported. - +- On all versions of MySQL, the :srid, :with_z, and :with_m options are ignored, + since they are not supported. + - On MySQL versions <= 5.0.16, you have to add :options => "ENGINE=MyISAM" to the create_table statement, since only MyISAM tables can have spatial columns. In addition, only MyISAM tables may have spatial @@ -161,7 +163,7 @@ library (http://georuby.rubyforge.org/): This is where the place = Place.find_first place.the_geom.y=123456.7 # this doesn't work - + Since the translation to a geometry is performed every time the_geom is read, the change to y will not be saved! You would have to do something like this: @@ -195,3 +197,16 @@ These will hopefully be added back in the near future. Any questions, enhancement proposals, bug notifications or corrections can be made via the project page at http://github.com/fragility/spatial_adapter + +== Running Tests + +The gem depdencencies can be installed with bundler. There are different +development dependencies for jRuby and Ruby. To ensure you have the proper +gem dependencies installed, run `bundle install` after switching ruby platforms. + +You will need to set up an empty database named `spatial_adapter` for each +adapter you want to test. + +Tests are partitioned by adapter and can be run using separate rake task. + + bundle exec rake spec:[adapter] diff --git a/Rakefile b/Rakefile index d904afa..62072ea 100644 --- a/Rakefile +++ b/Rakefile @@ -1,40 +1,11 @@ -require 'rubygems' -require 'spec/rake/spectask' +require 'bundler' +Bundler::GemHelper.install_tasks -[:mysql, :mysql2, :postgresql].each do |adapter| - desc "Run specs for #{adapter} adapter" - Spec::Rake::SpecTask.new("spec:#{adapter.to_s}") do |t| - t.spec_files = FileList["spec/#{adapter}/*_spec.rb"] - end -end +require 'rspec/core/rake_task' -begin - require 'jeweler' - Jeweler::Tasks.new do |gem| - gem.name = "spatial_adapter" - gem.summary = "Spatial Adapter for ActiveRecord" - gem.description = "Provides enhancements to ActiveRecord to handle spatial datatypes in PostgreSQL and MySQL." - gem.authors = ["Pete Deffendol", "Guilhem Vellut"] - gem.email = "pete@fragility.us" - gem.homepage = "http://github.com/fragility/spatial_adapter" - - gem.files = FileList[ - "rails/*.rb", - "lib/**/*.rb", - "MIT-LICENSE", - "README.rdoc", - "VERSION" - ] - gem.test_files = FileList[ - "spec/**/*.rb", - "spec/README.txt" - ] - - gem.add_dependency 'activerecord', '>= 2.2.2' - gem.add_dependency 'GeoRuby', '>= 1.3.0' +[:mysql, :mysql2, :jdbcmysql, :postgresql].each do |adapter| + desc "Run specs for #{adapter} adapter" + RSpec::Core::RakeTask.new("spec:#{adapter.to_s}") do |t| + t.pattern = "spec/#{adapter}_spec.rb" end - - Jeweler::GemcutterTasks.new -rescue LoadError - puts "WARNING: Jeweler is not available for building packages. Install it with: gem install jeweler" end diff --git a/lib/spatial_adapter.rb b/lib/spatial_adapter.rb index 01a8c31..1ce8282 100644 --- a/lib/spatial_adapter.rb +++ b/lib/spatial_adapter.rb @@ -16,7 +16,7 @@ module SpatialAdapter # Translation of geometric data types - def geometry_data_types + def self.geometry_data_types { :point => { :name => "POINT" }, :line_string => { :name => "LINESTRING" }, @@ -28,14 +28,10 @@ def geometry_data_types :geometry => { :name => "GEOMETRY"} } end - + class NotCompatibleError < ::StandardError end end -require 'spatial_adapter/common/raw_geom_info' -require 'spatial_adapter/common/spatial_column' -require 'spatial_adapter/common/schema_definitions' -require 'spatial_adapter/common/schema_dumper' -require 'spatial_adapter/common/table_definition' +require 'spatial_adapter/common' require 'spatial_adapter/railtie' if defined?(Rails::Railtie) diff --git a/lib/spatial_adapter/base/mysql.rb b/lib/spatial_adapter/base/mysql.rb new file mode 100644 index 0000000..f570aa8 --- /dev/null +++ b/lib/spatial_adapter/base/mysql.rb @@ -0,0 +1,9 @@ +module SpatialAdapter + module Base + module Mysql + end + end +end + +require 'spatial_adapter/base/mysql/adapter' +require 'spatial_adapter/base/mysql/spatial_column' diff --git a/lib/spatial_adapter/base/mysql/adapter.rb b/lib/spatial_adapter/base/mysql/adapter.rb new file mode 100644 index 0000000..262c3ff --- /dev/null +++ b/lib/spatial_adapter/base/mysql/adapter.rb @@ -0,0 +1,54 @@ +module SpatialAdapter::Base::Mysql + module Adapter + GEOMETRY_REGEXP = /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i + + def supports_geographic? + false + end + + def self.included klass + klass.class_eval do + def native_database_types + (defined?(NATIVE_DATABASE_TYPES) ? NATIVE_DATABASE_TYPES : super()) \ + .merge(SpatialAdapter.geometry_data_types) + end + + # Redefines the quote method to add behaviour for when a Geometry is + # encountered ; used when binding variables in find_by methods + def quote(value, column = nil) + if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) + "GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})" + else + super(value,column) + end + end + + #Redefines add_index to support the case where the index is spatial + #If the :spatial key in the options table is true, then the sql string for a spatial index is created + def add_index(table_name,column_name,options = {}) + index_name = options[:name] || index_name(table_name,:column => Array(column_name)) + + if options[:spatial] + execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})" + else + super + end + end + end + end + + private + + def show_table_status_like(table) + execute("SHOW TABLE STATUS LIKE '#{table}'") + end + + def show_fields_from(table, name = nil) + execute("SHOW FIELDS FROM #{quote_table_name(table)}", name) + end + + def show_keys_from(table, name = nil) + execute("SHOW KEYS FROM #{quote_table_name(table)}", name) || [] + end + end +end diff --git a/lib/spatial_adapter/base/mysql/spatial_column.rb b/lib/spatial_adapter/base/mysql/spatial_column.rb new file mode 100644 index 0000000..ef31b9f --- /dev/null +++ b/lib/spatial_adapter/base/mysql/spatial_column.rb @@ -0,0 +1,12 @@ +module SpatialAdapter::Base::Mysql + module SpatialColumn + def string_to_geometry(string) + return string unless string.is_a?(String) + begin + GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1]) + rescue Exception => exception + nil + end + end + end +end diff --git a/lib/spatial_adapter/common.rb b/lib/spatial_adapter/common.rb new file mode 100644 index 0000000..6804aa2 --- /dev/null +++ b/lib/spatial_adapter/common.rb @@ -0,0 +1,10 @@ +module SpatialAdapter + module Common + end +end + +require 'spatial_adapter/common/raw_geom_info' +require 'spatial_adapter/common/spatial_column' +require 'spatial_adapter/common/schema_definitions' +require 'spatial_adapter/common/schema_dumper' +require 'spatial_adapter/common/table_definition' diff --git a/lib/spatial_adapter/common/schema_dumper.rb b/lib/spatial_adapter/common/schema_dumper.rb index 854e333..3374cd7 100644 --- a/lib/spatial_adapter/common/schema_dumper.rb +++ b/lib/spatial_adapter/common/schema_dumper.rb @@ -3,7 +3,7 @@ ActiveRecord::SchemaDumper.class_eval do # These are the valid options for a column specification (spatial options added) VALID_COLUMN_SPEC_KEYS = [:name, :limit, :precision, :scale, :default, :null, :srid, :with_z, :with_m, :geographic] - + def table(table, stream) columns = @connection.columns(table) begin @@ -15,7 +15,7 @@ def table(table, stream) elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end - + tbl.print " create_table #{table.inspect}" if columns.detect { |c| c.name == pk } if pk != 'id' @@ -24,13 +24,13 @@ def table(table, stream) else tbl.print ", :id => false" end - + # Added by Spatial Adapter to ensure correct MySQL table engine if @connection.respond_to?(:options_for) res = @connection.options_for(table) tbl.print ", :options=>'#{res}'" if res end - + tbl.print ", :force => true" tbl.puts " do |t|" @@ -69,7 +69,7 @@ def table(table, stream) tbl.puts " end" tbl.puts - + indexes(table, tbl) tbl.rewind @@ -79,10 +79,10 @@ def table(table, stream) stream.puts "# #{e.message}" stream.puts end - + stream end - + def indexes(table, stream) if (indexes = @connection.indexes(table)).any? @@ -101,14 +101,14 @@ def indexes(table, stream) stream.puts end end - + private - + # Build specification for a table column def column_spec(column) spec = {} spec[:name] = column.name.inspect - + # AR has an optimisation which handles zero-scale decimals as integers. This # code ensures that the dumper still dumps the column as a decimal. spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) } @@ -121,9 +121,9 @@ def column_spec(column) spec[:scale] = column.scale.inspect if !column.scale.nil? spec[:null] = 'false' if !column.null spec[:default] = default_string(column.default) if column.has_default? - + # Additions for spatial columns - if column.is_a?(SpatialColumn) + if column.is_a?(::SpatialAdapter::SpatialColumn) # Override with specific geometry type spec[:type] = column.geometry_type.to_s spec[:srid] = column.srid.inspect if column.srid != -1 diff --git a/lib/spatial_adapter/common/spatial_column.rb b/lib/spatial_adapter/common/spatial_column.rb index dbedb37..84ef57b 100644 --- a/lib/spatial_adapter/common/spatial_column.rb +++ b/lib/spatial_adapter/common/spatial_column.rb @@ -9,11 +9,11 @@ def initialize(name, default, sql_type = nil, null = true, srid=-1, with_z=false @with_z = with_z @with_m = with_m end - + def spatial? !@geometry_type.nil? end - + def geographic? false end @@ -25,9 +25,9 @@ def type_cast(value) spatial? ? self.class.string_to_geometry(value) : super end - #Redefines type_cast_code to add support for geometries. + #Redefines type_cast_code to add support for geometries. # - #WARNING : Since ActiveRecord keeps only the string values directly returned from the database, it translates from these to the correct types everytime an attribute is read (using the code returned by this method), which is probably ok for simple types, but might be less than efficient for geometries. Also you cannot modify the geometry object returned directly or your change will not be saved. + #WARNING : Since ActiveRecord keeps only the string values directly returned from the database, it translates from these to the correct types everytime an attribute is read (using the code returned by this method), which is probably ok for simple types, but might be less than efficient for geometries. Also you cannot modify the geometry object returned directly or your change will not be saved. # alias_method :type_cast_code_without_spatial, :type_cast_code def type_cast_code(var_name) spatial? ? "#{self.class.name}.string_to_geometry(#{var_name})" : super diff --git a/lib/spatial_adapter/common/table_definition.rb b/lib/spatial_adapter/common/table_definition.rb index f2320eb..ad9dab1 100644 --- a/lib/spatial_adapter/common/table_definition.rb +++ b/lib/spatial_adapter/common/table_definition.rb @@ -1,14 +1,12 @@ -include SpatialAdapter +require 'spatial_adapter' -ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do - SpatialAdapter.geometry_data_types.keys.each do |column_type| - class_eval <<-EOV - def #{column_type}(*args) - options = args.extract_options! - column_names = args - - column_names.each { |name| column(name, '#{column_type}', options) } +class ActiveRecord::ConnectionAdapters::TableDefinition + SpatialAdapter.geometry_data_types.keys.each do |column_name| + define_method(column_name) do |*args| + options = args.extract_options! + args.each do |name| + column(name, column_name, options) end - EOV + end end end diff --git a/lib/spatial_adapter/jdbcmysql.rb b/lib/spatial_adapter/jdbcmysql.rb new file mode 100644 index 0000000..ee39b6a --- /dev/null +++ b/lib/spatial_adapter/jdbcmysql.rb @@ -0,0 +1,50 @@ +require 'spatial_adapter' +require 'spatial_adapter/base/mysql' +require 'active_record/connection_adapters/jdbcmysql_adapter' + +module ActiveRecord::ConnectionAdapters + class MysqlAdapter + include SpatialAdapter::Base::Mysql::Adapter + + #Redefinition of columns to add the information that a column is geometric + def columns(table_name, name = nil)#:nodoc: + show_fields_from(table_name, name).map do |field| + klass = \ + if field["Type"] =~ GEOMETRY_REGEXP + ActiveRecord::ConnectionAdapters::SpatialMysqlColumn + else + ActiveRecord::ConnectionAdapters::MysqlColumn + end + klass.new(field['Field'], field['Default'], field['Type'], field['Null'] == "YES") + end + end + + # Check the nature of the index : If it is SPATIAL, it is indicated in the + # IndexDefinition object (redefined to add the spatial flag in + # spatial_adapter_common.rb) + def indexes(table_name, name = nil)#:nodoc: + indexes = [] + current_index = nil + show_keys_from(table_name, name).each do |row| + if current_index != row['Key_name'] + next if row['Key_name'] == "PRIMARY" # skip the primary key + current_index = row['Key_name'] + indexes << ActiveRecord::ConnectionAdapters::IndexDefinition \ + .new(row['Table'], row['Key_name'], row['Non_unique'] == "0", [], row['Index_type'] == "SPATIAL") + end + indexes.last.columns << row['Column_name'] + end + indexes + end + + def options_for(table) + engine = show_table_status_like(table).first['Engine'] + engine !~ /inno/i ? "ENGINE=#{engine}" : nil + end + end + + class SpatialMysqlColumn < MysqlColumn + include SpatialAdapter::SpatialColumn + extend SpatialAdapter::Base::Mysql::SpatialColumn + end +end diff --git a/lib/spatial_adapter/mysql.rb b/lib/spatial_adapter/mysql.rb index 8a3b6ed..5eb22b0 100644 --- a/lib/spatial_adapter/mysql.rb +++ b/lib/spatial_adapter/mysql.rb @@ -1,98 +1,56 @@ require 'spatial_adapter' +require 'spatial_adapter/base/mysql' require 'active_record/connection_adapters/mysql_adapter' -ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do - include SpatialAdapter +module ActiveRecord::ConnectionAdapters + class MysqlAdapter + include SpatialAdapter::Base::Mysql::Adapter + + #Redefinition of columns to add the information that a column is geometric + def columns(table_name, name = nil)#:nodoc: + result = show_fields_from(table_name, name) + + columns = [] + result.each do |field| + klass = \ + if field[1] =~ GEOMETRY_REGEXP + ActiveRecord::ConnectionAdapters::SpatialMysqlColumn + else + ActiveRecord::ConnectionAdapters::MysqlColumn + end + columns << klass.new(field[0], field[4], field[1], field[2] == "YES") + end - def supports_geographic? - false - end - - alias :original_native_database_types :native_database_types - def native_database_types - original_native_database_types.merge!(geometry_data_types) - end - - alias :original_quote :quote - #Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods - def quote(value, column = nil) - if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) - "GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})" - else - original_quote(value,column) - end - end - - #Redefinition of columns to add the information that a column is geometric - def columns(table_name, name = nil)#:nodoc: - sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" - columns = [] - result = execute(sql, name) - result.each do |field| - klass = field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i ? ActiveRecord::ConnectionAdapters::SpatialMysqlColumn : ActiveRecord::ConnectionAdapters::MysqlColumn - columns << klass.new(field[0], field[4], field[1], field[2] == "YES") + result.free + columns end - result.free - columns - end - - - #operations relative to migrations - #Redefines add_index to support the case where the index is spatial - #If the :spatial key in the options table is true, then the sql string for a spatial index is created - def add_index(table_name,column_name,options = {}) - index_name = options[:name] || index_name(table_name,:column => Array(column_name)) - - if options[:spatial] - execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})" - else - super - end - end - - #Check the nature of the index : If it is SPATIAL, it is indicated in the IndexDefinition object (redefined to add the spatial flag in spatial_adapter_common.rb) - def indexes(table_name, name = nil)#:nodoc: - indexes = [] - current_index = nil - execute("SHOW KEYS FROM #{table_name}", name).each do |row| - if current_index != row[2] - next if row[2] == "PRIMARY" # skip the primary key - current_index = row[2] - indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL") + # Check the nature of the index : If it is SPATIAL, it is indicated in the + # IndexDefinition object (redefined to add the spatial flag in + # spatial_adapter_common.rb) + def indexes(table_name, name = nil)#:nodoc: + indexes = [] + current_index = nil + show_keys_from(table_name, name).each do |row| + if current_index != row[2] + next if row[2] == "PRIMARY" # skip the primary key + current_index = row[2] + indexes << ActiveRecord::ConnectionAdapters::IndexDefinition \ + .new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL") + end + indexes.last.columns << row[4] end - indexes.last.columns << row[4] + indexes end - indexes - end - - #Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here. - def options_for(table) - result = execute("show table status like '#{table}'") - engine = result.fetch_row[1] - if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration - "ENGINE=#{engine}" - else - nil + + def options_for(table) + engine = show_table_status_like(table).fetch_row[1] + engine !~ /inno/i ? "ENGINE=#{engine}" : nil end end -end - -module ActiveRecord - module ConnectionAdapters - class SpatialMysqlColumn < MysqlColumn - include SpatialAdapter::SpatialColumn - - #MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions. - def self.string_to_geometry(string) - return string unless string.is_a?(String) - begin - GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1]) - rescue Exception => exception - nil - end - end - end + class SpatialMysqlColumn < MysqlColumn + include SpatialAdapter::SpatialColumn + extend SpatialAdapter::Base::Mysql::SpatialColumn end end diff --git a/lib/spatial_adapter/mysql2.rb b/lib/spatial_adapter/mysql2.rb index 1636483..0c0b8cc 100644 --- a/lib/spatial_adapter/mysql2.rb +++ b/lib/spatial_adapter/mysql2.rb @@ -1,97 +1,50 @@ require 'spatial_adapter' +require 'spatial_adapter/base/mysql' require 'active_record/connection_adapters/mysql2_adapter' -ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do - include SpatialAdapter - - def supports_geographic? - false - end - - alias :original_native_database_types :native_database_types - def native_database_types - original_native_database_types.merge!(geometry_data_types) - end - - alias :original_quote :quote - #Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods - def quote(value, column = nil) - if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) - "GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})" - else - original_quote(value,column) - end - end - - #Redefinition of columns to add the information that a column is geometric - def columns(table_name, name = nil)#:nodoc: - sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" - columns = [] - result = execute(sql, name) - result.each do |field| - klass = field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i ? ActiveRecord::ConnectionAdapters::SpatialMysql2Column : ActiveRecord::ConnectionAdapters::Mysql2Column - columns << klass.new(field[0], field[4], field[1], field[2] == "YES") - end - columns - end - - - #operations relative to migrations - - #Redefines add_index to support the case where the index is spatial - #If the :spatial key in the options table is true, then the sql string for a spatial index is created - def add_index(table_name,column_name,options = {}) - index_name = options[:name] || index_name(table_name,:column => Array(column_name)) - - if options[:spatial] - execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})" - else - super - end - end - - #Check the nature of the index : If it is SPATIAL, it is indicated in the IndexDefinition object (redefined to add the spatial flag in spatial_adapter_common.rb) - def indexes(table_name, name = nil)#:nodoc: - indexes = [] - current_index = nil - (execute("SHOW KEYS FROM #{table_name}", name) || []).each do |row| - if current_index != row[2] - next if row[2] == "PRIMARY" # skip the primary key - current_index = row[2] - indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL") +module ActiveRecord::ConnectionAdapters + class SpatialMysql2Column < Mysql2Column + include SpatialAdapter::SpatialColumn + extend SpatialAdapter::Base::Mysql::SpatialColumn + end + + class Mysql2Adapter + include SpatialAdapter::Base::Mysql::Adapter + + #Redefinition of columns to add the information that a column is geometric + def columns(table_name, name = nil)#:nodoc: + show_fields_from(table_name, name).map do |field| + klass = \ + if field[1] =~ GEOMETRY_REGEXP + ActiveRecord::ConnectionAdapters::SpatialMysql2Column + else + ActiveRecord::ConnectionAdapters::Mysql2Column + end + klass.new(field[0], field[4], field[1], field[2] == "YES") end - indexes.last.columns << row[4] - end - indexes - end - - #Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here. - def options_for(table) - result = execute("show table status like '#{table}'") - engine = result.first[1] - if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration - "ENGINE=#{engine}" - else - nil end - end -end - -module ActiveRecord - module ConnectionAdapters - class SpatialMysql2Column < Mysql2Column - include SpatialAdapter::SpatialColumn - - #MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions. - def self.string_to_geometry(string) - return string unless string.is_a?(String) - begin - GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1]) - rescue Exception => exception - nil + # Check the nature of the index : If it is SPATIAL, it is indicated in the + # IndexDefinition object (redefined to add the spatial flag in + # spatial_adapter_common.rb) + def indexes(table_name, name = nil)#:nodoc: + indexes = [] + current_index = nil + show_keys_from(table_name, name).each do |row| + if current_index != row[2] + next if row[2] == "PRIMARY" # skip the primary key + current_index = row[2] + indexes << ActiveRecord::ConnectionAdapters::IndexDefinition \ + .new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL") end + indexes.last.columns << row[4] end + indexes + end + + def options_for(table) + engine = show_table_status_like(table).first[1] + engine !~ /inno/i ? "ENGINE=#{engine}" : nil end end end diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index d0da480..f797b87 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -1,388 +1,379 @@ require 'spatial_adapter' require 'active_record/connection_adapters/postgresql_adapter' -ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do - include SpatialAdapter - - def postgis_version - begin - select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] - rescue ActiveRecord::StatementInvalid - nil +module ActiveRecord::ConnectionAdapters + class PostgreSQLAdapter + def postgis_version + begin + select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] + rescue ActiveRecord::StatementInvalid + nil + end end - end - - def postgis_major_version - version = postgis_version - version ? version.scan(/^(\d)\.\d\.\d$/)[0][0].to_i : nil - end - - def postgis_minor_version - version = postgis_version - version ? version.scan(/^\d\.(\d)\.\d$/)[0][0].to_i : nil - end - - def spatial? - !postgis_version.nil? - end - - def supports_geographic? - postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5) - end - - alias :original_native_database_types :native_database_types - def native_database_types - original_native_database_types.merge!(geometry_data_types) - end - alias :original_quote :quote - #Redefines the quote method to add behaviour for when a Geometry is encountered - def quote(value, column = nil) - if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) - "'#{value.as_hex_ewkb}'" - else - original_quote(value,column) + def postgis_major_version + version = postgis_version + version ? version.scan(/^(\d)\.\d\.\d$/)[0][0].to_i : nil + end + + def postgis_minor_version + version = postgis_version + version ? version.scan(/^\d\.(\d)\.\d$/)[0][0].to_i : nil + end + + def spatial? + !postgis_version.nil? + end + + def supports_geographic? + postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5) + end + + alias :original_native_database_types :native_database_types + def native_database_types + original_native_database_types.merge!(SpatialAdapter.geometry_data_types) + end + + alias :original_quote :quote + #Redefines the quote method to add behaviour for when a Geometry is encountered + def quote(value, column = nil) + if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) + "'#{value.as_hex_ewkb}'" + else + original_quote(value,column) + end end - end - def columns(table_name, name = nil) #:nodoc: - raw_geom_infos = column_spatial_info(table_name) - - column_definitions(table_name).collect do |name, type, default, notnull| - case type - when /geography/i - ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_from_geography(name, default, type, notnull == 'f') - when /geometry/i - raw_geom_info = raw_geom_infos[name] - if raw_geom_info.nil? - # This column isn't in the geometry_columns table, so we don't know anything else about it - ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_simplified(name, default, notnull == "f") + def columns(table_name, name = nil) #:nodoc: + raw_geom_infos = column_spatial_info(table_name) + + column_definitions(table_name).collect do |name, type, default, notnull| + case type + when /geography/i + ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_from_geography(name, default, type, notnull == 'f') + when /geometry/i + raw_geom_info = raw_geom_infos[name] + if raw_geom_info.nil? + # This column isn't in the geometry_columns table, so we don't know anything else about it + ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_simplified(name, default, notnull == "f") + else + ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.new(name, default, raw_geom_info.type, notnull == "f", raw_geom_info.srid, raw_geom_info.with_z, raw_geom_info.with_m) + end else - ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.new(name, default, raw_geom_info.type, notnull == "f", raw_geom_info.srid, raw_geom_info.with_z, raw_geom_info.with_m) + ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(name, default, type, notnull == "f") end - else - ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(name, default, type, notnull == "f") end end - end - def create_table(table_name, options = {}) - # Using the subclassed table definition - table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self) - table_definition.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false + def create_table(table_name, options = {}) + # Using the subclassed table definition + table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self) + table_definition.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false - yield table_definition if block_given? + yield table_definition if block_given? - if options[:force] && table_exists?(table_name) - drop_table(table_name, options) - end + if options[:force] && table_exists?(table_name) + drop_table(table_name, options) + end - create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " - create_sql << "#{quote_table_name(table_name)} (" - create_sql << table_definition.to_sql - create_sql << ") #{options[:options]}" + create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " + create_sql << "#{quote_table_name(table_name)} (" + create_sql << table_definition.to_sql + create_sql << ") #{options[:options]}" - # This is the additional portion for PostGIS - unless table_definition.geom_columns.nil? - table_definition.geom_columns.each do |geom_column| - geom_column.table_name = table_name - create_sql << "; " + geom_column.to_sql + # This is the additional portion for PostGIS + unless table_definition.geom_columns.nil? + table_definition.geom_columns.each do |geom_column| + geom_column.table_name = table_name + create_sql << "; " + geom_column.to_sql + end end - end - execute create_sql - end + execute create_sql + end - alias :original_remove_column :remove_column - def remove_column(table_name, *column_names) - column_names = column_names.flatten - columns(table_name).each do |col| - if column_names.include?(col.name.to_sym) - # Geometry columns have to be removed using DropGeometryColumn - if col.is_a?(SpatialColumn) && col.spatial? && !col.geographic? - execute "SELECT DropGeometryColumn('#{table_name}','#{col.name}')" - else - original_remove_column(table_name, col.name) + alias :original_remove_column :remove_column + def remove_column(table_name, *column_names) + column_names = column_names.flatten + columns(table_name).each do |col| + if column_names.include?(col.name.to_sym) + # Geometry columns have to be removed using DropGeometryColumn + if col.is_a?(::SpatialAdapter::SpatialColumn) && col.spatial? && !col.geographic? + execute "SELECT DropGeometryColumn('#{table_name}','#{col.name}')" + else + original_remove_column(table_name, col.name) + end end end end - end - - alias :original_add_column :add_column - def add_column(table_name, column_name, type, options = {}) - unless geometry_data_types[type].nil? - geom_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumnDefinition.new(self, column_name, type, nil, nil, options[:null], options[:srid] || -1 , options[:with_z] || false , options[:with_m] || false, options[:geographic] || false) - if geom_column.geographic - default = options[:default] - notnull = options[:null] == false - - execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{geom_column.to_sql}") - - change_column_default(table_name, column_name, default) if options_include_default?(options) - change_column_null(table_name, column_name, false, default) if notnull + + alias :original_add_column :add_column + def add_column(table_name, column_name, type, options = {}) + unless SpatialAdapter.geometry_data_types[type].nil? + geom_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumnDefinition.new(self, column_name, type, nil, nil, options[:null], options[:srid] || -1 , options[:with_z] || false , options[:with_m] || false, options[:geographic] || false) + if geom_column.geographic + default = options[:default] + notnull = options[:null] == false + + execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{geom_column.to_sql}") + + change_column_default(table_name, column_name, default) if options_include_default?(options) + change_column_null(table_name, column_name, false, default) if notnull + else + geom_column.table_name = table_name + execute geom_column.to_sql + end else - geom_column.table_name = table_name - execute geom_column.to_sql + original_add_column(table_name, column_name, type, options) end - else - original_add_column(table_name, column_name, type, options) end - end - # Adds an index to a column. - def add_index(table_name, column_name, options = {}) - column_names = Array(column_name) - index_name = index_name(table_name, :column => column_names) - - if Hash === options # legacy support, since this param was a string - index_type = options[:unique] ? "UNIQUE" : "" - index_name = options[:name] || index_name - index_method = options[:spatial] ? 'USING GIST' : "" - else - index_type = options + # Adds an index to a column. + def add_index(table_name, column_name, options = {}) + column_names = Array(column_name) + index_name = index_name(table_name, :column => column_names) + + if Hash === options # legacy support, since this param was a string + index_type = options[:unique] ? "UNIQUE" : "" + index_name = options[:name] || index_name + index_method = options[:spatial] ? 'USING GIST' : "" + else + index_type = options + end + quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ") + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_method} (#{quoted_column_names})" end - quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ") - execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_method} (#{quoted_column_names})" - end - # Returns the list of all indexes for a table. - # - # This is a full replacement for the ActiveRecord method and as a result - # has a higher probability of breaking in future releases. - def indexes(table_name, name = nil) - schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') - - # Changed from upstread: link to pg_am to grab the index type (e.g. "gist") - result = query(<<-SQL, name) - SELECT distinct i.relname, d.indisunique, d.indkey, t.oid, am.amname - FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am - WHERE i.relkind = 'i' - AND d.indexrelid = i.oid - AND d.indisprimary = 'f' - AND t.oid = d.indrelid - AND t.relname = '#{table_name}' - AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) ) - AND i.relam = am.oid - AND a.attrelid = t.oid - ORDER BY i.relname - SQL - - - indexes = [] - - indexes = result.map do |row| - index_name = row[0] - unique = row[1] == 't' - indkey = row[2].split(" ") - oid = row[3] - indtype = row[4] - - # Changed from upstream: need to get the column types to test for spatial indexes - columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = [r[0], r[2]]; attlist} - SELECT a.attname, a.attnum, t.typname - FROM pg_attribute a, pg_type t - WHERE a.attrelid = #{oid} - AND a.attnum IN (#{indkey.join(",")}) - AND a.atttypid = t.oid + # Returns the list of all indexes for a table. + # + # This is a full replacement for the ActiveRecord method and as a result + # has a higher probability of breaking in future releases. + def indexes(table_name, name = nil) + schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') + + # Changed from upstread: link to pg_am to grab the index type (e.g. "gist") + result = query(<<-SQL, name) + SELECT distinct i.relname, d.indisunique, d.indkey, t.oid, am.amname + FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am + WHERE i.relkind = 'i' + AND d.indexrelid = i.oid + AND d.indisprimary = 'f' + AND t.oid = d.indrelid + AND t.relname = '#{table_name}' + AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) ) + AND i.relam = am.oid + AND a.attrelid = t.oid + ORDER BY i.relname SQL - # Only GiST indexes on spatial columns denote a spatial index - spatial = indtype == 'gist' && columns.size == 1 && (columns.values.first[1] == 'geometry' || columns.values.first[1] == 'geography') - column_names = indkey.map {|attnum| columns[attnum] ? columns[attnum][0] : nil } - ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, spatial) - end + indexes = [] - indexes - end + indexes = result.map do |row| + index_name = row[0] + unique = row[1] == 't' + indkey = row[2].split(" ") + oid = row[3] + indtype = row[4] - def disable_referential_integrity(&block) #:nodoc: - if supports_disable_referential_integrity?() then - execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) - end - yield - ensure - if supports_disable_referential_integrity?() then - execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + # Changed from upstream: need to get the column types to test for spatial indexes + columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = [r[0], r[2]]; attlist} + SELECT a.attname, a.attnum, t.typname + FROM pg_attribute a, pg_type t + WHERE a.attrelid = #{oid} + AND a.attnum IN (#{indkey.join(",")}) + AND a.atttypid = t.oid + SQL + + # Only GiST indexes on spatial columns denote a spatial index + spatial = indtype == 'gist' && columns.size == 1 && (columns.values.first[1] == 'geometry' || columns.values.first[1] == 'geography') + + column_names = indkey.map {|attnum| columns[attnum] ? columns[attnum][0] : nil } + ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, spatial) + end + + indexes end - end - private - - def tables_without_postgis - tables - %w{ geometry_columns spatial_ref_sys } - end - - def column_spatial_info(table_name) - constr = query("SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'") - - raw_geom_infos = {} - constr.each do |constr_def_a| - raw_geom_infos[constr_def_a[3]] ||= SpatialAdapter::RawGeomInfo.new - raw_geom_infos[constr_def_a[3]].type = constr_def_a[6] - raw_geom_infos[constr_def_a[3]].dimension = constr_def_a[4].to_i - raw_geom_infos[constr_def_a[3]].srid = constr_def_a[5].to_i - - if raw_geom_infos[constr_def_a[3]].type[-1] == ?M - raw_geom_infos[constr_def_a[3]].with_m = true - raw_geom_infos[constr_def_a[3]].type.chop! - else - raw_geom_infos[constr_def_a[3]].with_m = false + def disable_referential_integrity(&block) #:nodoc: + if supports_disable_referential_integrity?() then + execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + end + yield + ensure + if supports_disable_referential_integrity?() then + execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) end end - raw_geom_infos.each_value do |raw_geom_info| - #check the presence of z and m - raw_geom_info.convert! + private + + def tables_without_postgis + tables - %w{ geometry_columns spatial_ref_sys } end - raw_geom_infos + def column_spatial_info(table_name) + constr = query("SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'") - end -end + raw_geom_infos = {} + constr.each do |constr_def_a| + raw_geom_infos[constr_def_a[3]] ||= SpatialAdapter::RawGeomInfo.new + raw_geom_infos[constr_def_a[3]].type = constr_def_a[6] + raw_geom_infos[constr_def_a[3]].dimension = constr_def_a[4].to_i + raw_geom_infos[constr_def_a[3]].srid = constr_def_a[5].to_i -module ActiveRecord - module ConnectionAdapters - class PostgreSQLTableDefinition < TableDefinition - attr_reader :geom_columns - - def column(name, type, options = {}) - unless (@base.geometry_data_types[type.to_sym].nil? or - (options[:create_using_addgeometrycolumn] == false)) - - column = self[name] || PostgreSQLColumnDefinition.new(@base, name, type) - column.null = options[:null] - column.srid = options[:srid] || -1 - column.with_z = options[:with_z] || false - column.with_m = options[:with_m] || false - column.geographic = options[:geographic] || false - - if column.geographic - @columns << column unless @columns.include? column - else - # Hold this column for later - @geom_columns ||= [] - @geom_columns << column - end - self + if raw_geom_infos[constr_def_a[3]].type[-1] == ?M + raw_geom_infos[constr_def_a[3]].with_m = true + raw_geom_infos[constr_def_a[3]].type.chop! else - super(name, type, options) + raw_geom_infos[constr_def_a[3]].with_m = false end - end - end + end - class PostgreSQLColumnDefinition < ColumnDefinition - attr_accessor :table_name - attr_accessor :srid, :with_z, :with_m, :geographic - attr_reader :spatial - - def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil, null=nil, srid=-1, with_z=false, with_m=false, geographic=false) - super(base, name, type, limit, default, null) - @table_name = nil - @spatial = true - @srid = srid - @with_z = with_z - @with_m = with_m - @geographic = geographic + raw_geom_infos.each_value do |raw_geom_info| + #check the presence of z and m + raw_geom_info.convert! end - - def sql_type - if geographic - type_sql = base.geometry_data_types[type.to_sym][:name] - type_sql += "Z" if with_z - type_sql += "M" if with_m - # SRID is not yet supported (defaults to 4326) - #type_sql += ", #{srid}" if (srid && srid != -1) - type_sql = "geography(#{type_sql})" - type_sql + + raw_geom_infos + end + end + + class PostgreSQLTableDefinition < TableDefinition + attr_reader :geom_columns + + def column(name, type, options = {}) + unless (SpatialAdapter.geometry_data_types[type.to_sym].nil? or + (options[:create_using_addgeometrycolumn] == false)) + + column = self[name] || PostgreSQLColumnDefinition.new(@base, name, type) + column.null = options[:null] + column.srid = options[:srid] || -1 + column.with_z = options[:with_z] || false + column.with_m = options[:with_m] || false + column.geographic = options[:geographic] || false + + if column.geographic + @columns << column unless @columns.include? column else - super + # Hold this column for later + @geom_columns ||= [] + @geom_columns << column end + self + else + super(name, type, options) end - - def to_sql - if spatial && !geographic - type_sql = base.geometry_data_types[type.to_sym][:name] - type_sql += "M" if with_m and !with_z - if with_m and with_z - dimension = 4 - elsif with_m or with_z - dimension = 3 - else - dimension = 2 - end - - column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})" - column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false - column_sql + end + end + + class PostgreSQLColumnDefinition < ColumnDefinition + attr_accessor :table_name + attr_accessor :srid, :with_z, :with_m, :geographic + attr_reader :spatial + + def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil, null=nil, srid=-1, with_z=false, with_m=false, geographic=false) + super(base, name, type, limit, default, null) + @table_name = nil + @spatial = true + @srid = srid + @with_z = with_z + @with_m = with_m + @geographic = geographic + end + + def sql_type + if geographic + type_sql = SpatialAdapter.geometry_data_types[type.to_sym][:name] + type_sql += "Z" if with_z + type_sql += "M" if with_m + # SRID is not yet supported (defaults to 4326) + #type_sql += ", #{srid}" if (srid && srid != -1) + type_sql = "geography(#{type_sql})" + type_sql + else + super + end + end + + def to_sql + if spatial && !geographic + type_sql = SpatialAdapter.geometry_data_types[type.to_sym][:name] + type_sql += "M" if with_m and !with_z + if with_m and with_z + dimension = 4 + elsif with_m or with_z + dimension = 3 else - super + dimension = 2 end + + column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})" + column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false + column_sql + else + super end end end -end -module ActiveRecord - module ConnectionAdapters - class SpatialPostgreSQLColumn < PostgreSQLColumn - include SpatialAdapter::SpatialColumn + class SpatialPostgreSQLColumn < PostgreSQLColumn + include SpatialAdapter::SpatialColumn - def initialize(name, default, sql_type = nil, null = true, srid=-1, with_z=false, with_m=false, geographic = false) - super(name, default, sql_type, null, srid, with_z, with_m) - @geographic = geographic - end + def initialize(name, default, sql_type = nil, null = true, srid=-1, with_z=false, with_m=false, geographic = false) + super(name, default, sql_type, null, srid, with_z, with_m) + @geographic = geographic + end - def geographic? - @geographic - end - - #Transforms a string to a geometry. PostGIS returns a HewEWKB string. - def self.string_to_geometry(string) - return string unless string.is_a?(String) - GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil - end + def geographic? + @geographic + end - def self.create_simplified(name, default, null = true) - new(name, default, "geometry", null) - end - - def self.create_from_geography(name, default, sql_type, null = true) - params = extract_geography_params(sql_type) - new(name, default, sql_type, null, params[:srid], params[:with_z], params[:with_m], true) - end - - private - - # Add detection of PostGIS-specific geography columns - def geometry_simplified_type(sql_type) - case sql_type - when /geography\(point/i then :point - when /geography\(linestring/i then :line_string - when /geography\(polygon/i then :polygon - when /geography\(multipoint/i then :multi_point - when /geography\(multilinestring/i then :multi_line_string - when /geography\(multipolygon/i then :multi_polygon - when /geography\(geometrycollection/i then :geometry_collection - when /geography/i then :geometry - else - super - end + #Transforms a string to a geometry. PostGIS returns a HewEWKB string. + def self.string_to_geometry(string) + return string unless string.is_a?(String) + GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil + end + + def self.create_simplified(name, default, null = true) + new(name, default, "geometry", null) + end + + def self.create_from_geography(name, default, sql_type, null = true) + params = extract_geography_params(sql_type) + new(name, default, sql_type, null, params[:srid], params[:with_z], params[:with_m], true) + end + + private + + # Add detection of PostGIS-specific geography columns + def geometry_simplified_type(sql_type) + case sql_type + when /geography\(point/i then :point + when /geography\(linestring/i then :line_string + when /geography\(polygon/i then :polygon + when /geography\(multipoint/i then :multi_point + when /geography\(multilinestring/i then :multi_line_string + when /geography\(multipolygon/i then :multi_polygon + when /geography\(geometrycollection/i then :geometry_collection + when /geography/i then :geometry + else + super end + end - def self.extract_geography_params(sql_type) - params = { - :srid => 0, - :with_z => false, - :with_m => false - } - if sql_type =~ /geography(?:\((?:\w+?)(Z)?(M)?(?:,(\d+))?\))?/i - params[:with_z] = $1 == 'Z' - params[:with_m] = $2 == 'M' - params[:srid] = $3.to_i - end - params + def self.extract_geography_params(sql_type) + params = { + :srid => 0, + :with_z => false, + :with_m => false + } + if sql_type =~ /geography(?:\((?:\w+?)(Z)?(M)?(?:,(\d+))?\))?/i + params[:with_z] = $1 == 'Z' + params[:with_m] = $2 == 'M' + params[:srid] = $3.to_i end + params end end end diff --git a/lib/spatial_adapter/version.rb b/lib/spatial_adapter/version.rb new file mode 100644 index 0000000..8806583 --- /dev/null +++ b/lib/spatial_adapter/version.rb @@ -0,0 +1,3 @@ +module SpatialAdapter + VERSION = '1.2.0' +end diff --git a/spatial_adapter.gemspec b/spatial_adapter.gemspec index 485b308..07804c3 100644 --- a/spatial_adapter.gemspec +++ b/spatial_adapter.gemspec @@ -1,76 +1,39 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require 'spatial_adapter/version' Gem::Specification.new do |s| s.name = %q{spatial_adapter} - s.version = "1.2.0" + s.version = SpatialAdapter::VERSION + + s.platform = $platform || RUBY_PLATFORM[/java/] || Gem::Platform::RUBY - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Pete Deffendol", "Guilhem Vellut"] - s.date = %q{2011-01-08} - s.description = %q{Provides enhancements to ActiveRecord to handle spatial datatypes in PostgreSQL and MySQL.} s.email = %q{pete@fragility.us} - s.extra_rdoc_files = [ - "README.rdoc" - ] - s.files = [ - "MIT-LICENSE", - "README.rdoc", - "VERSION", - "lib/spatial_adapter.rb", - "lib/spatial_adapter/common/raw_geom_info.rb", - "lib/spatial_adapter/common/schema_definitions.rb", - "lib/spatial_adapter/common/schema_dumper.rb", - "lib/spatial_adapter/common/spatial_column.rb", - "lib/spatial_adapter/common/table_definition.rb", - "lib/spatial_adapter/mysql.rb", - "lib/spatial_adapter/mysql2.rb", - "lib/spatial_adapter/postgresql.rb", - "lib/spatial_adapter/railtie.rb", - "rails/init.rb" - ] s.homepage = %q{http://github.com/fragility/spatial_adapter} + s.summary = "spatial_adapter-#{SpatialAdapter::VERSION}" + s.description = %q{Provides enhancements to ActiveRecord to handle spatial + datatypes in PostgreSQL and MySQL.} + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.extra_rdoc_files = ["README.rdoc", "MIT-LICENSE"] + s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.7} - s.summary = %q{Spatial Adapter for ActiveRecord} - s.test_files = [ - "spec/README.txt", - "spec/db/mysql2_raw.rb", - "spec/db/mysql_raw.rb", - "spec/db/postgis_raw.rb", - "spec/models/common.rb", - "spec/mysql/connection_adapter_spec.rb", - "spec/mysql/migration_spec.rb", - "spec/mysql/models_spec.rb", - "spec/mysql/schema_dumper_spec.rb", - "spec/mysql2/connection_adapter_spec.rb", - "spec/mysql2/migration_spec.rb", - "spec/mysql2/models_spec.rb", - "spec/mysql2/schema_dumper_spec.rb", - "spec/postgresql/connection_adapter_spec.rb", - "spec/postgresql/migration_spec.rb", - "spec/postgresql/models_spec.rb", - "spec/postgresql/schema_dumper_spec.rb", - "spec/shared_examples.rb", - "spec/spec_helper.rb" - ] - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 3 + s.add_development_dependency 'rake' + s.add_development_dependency 'rspec' + if s.platform.to_s == 'ruby' + s.add_development_dependency 'pg' + s.add_development_dependency 'mysql' + s.add_development_dependency 'mysql2', '<= 0.2.13' + end - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q, [">= 2.2.2"]) - s.add_runtime_dependency(%q, [">= 1.3.0"]) - else - s.add_dependency(%q, [">= 2.2.2"]) - s.add_dependency(%q, [">= 1.3.0"]) - end - else - s.add_dependency(%q, [">= 2.2.2"]) - s.add_dependency(%q, [">= 1.3.0"]) + if s.platform.to_s == 'java' + s.add_development_dependency 'activerecord-jdbcmysql-adapter' end -end + s.add_dependency 'activerecord', '>= 2.2.2', '< 3.1.0' + s.add_dependency 'GeoRuby', '>= 1.3.0' +end diff --git a/spec/README.txt b/spec/README.txt deleted file mode 100644 index cb2662a..0000000 --- a/spec/README.txt +++ /dev/null @@ -1,22 +0,0 @@ -= Running Tests - -You will need to set up empty databases for each adapter you want to test. - -== PostgreSQL - -Create an empty database "spatial_adapter" and ensure that the PostGIS extensions are loaded. - -run "rake spec:postgresql" to run the specs - -== MySQL - -Create an empty database "spatial_adapter" - the spatial extensions are already available. - -run "rake spec:mysql" to run the specs - -== MySQL2 - -Create an empty database "spatial_adapter" - the spatial extensions are already available. - -run "rake spec:mysql2" to run the specs - diff --git a/spec/db/jdbcmysql_raw.rb b/spec/db/jdbcmysql_raw.rb new file mode 100644 index 0000000..64c04bf --- /dev/null +++ b/spec/db/jdbcmysql_raw.rb @@ -0,0 +1,70 @@ +jdbcmysql_connection + +ActiveRecord::Schema.define() do + execute "drop table if exists point_models" + execute "create table point_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + more_extra varchar(255), + geom point not null + ) ENGINE=MyISAM" + execute "create spatial index index_point_models_on_geom on point_models (geom)" + execute "create index index_point_models_on_extra on point_models (extra, more_extra)" + + execute "drop table if exists line_string_models" + execute "create table line_string_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + geom linestring + ) ENGINE=MyISAM" + + execute "drop table if exists polygon_models" + execute "create table polygon_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + geom polygon + ) ENGINE=MyISAM" + + execute "drop table if exists multi_point_models" + execute "create table multi_point_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + geom multipoint + ) ENGINE=MyISAM" + + execute "drop table if exists multi_line_string_models" + execute "create table multi_line_string_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + geom multilinestring + ) ENGINE=MyISAM" + + execute "drop table if exists multi_polygon_models" + execute "create table multi_polygon_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + geom multipolygon + ) ENGINE=MyISAM" + + execute "drop table if exists geometry_collection_models" + execute "create table geometry_collection_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + geom geometrycollection + ) ENGINE=MyISAM" + + execute "drop table if exists geometry_models" + execute "create table geometry_models + ( + id int(11) DEFAULT NULL auto_increment PRIMARY KEY, + extra varchar(255), + geom geometry + ) ENGINE=MyISAM" +end diff --git a/spec/jdbcmysql_spec.rb b/spec/jdbcmysql_spec.rb new file mode 100644 index 0000000..b307df2 --- /dev/null +++ b/spec/jdbcmysql_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' +require 'shared/mysql_connection_adapter_spec' +require 'shared/mysql_migration_spec' +require 'shared/mysql_schema_dumper_spec' +require 'shared/mysql_models_spec' +require 'shared/common_model_actions_spec' +require 'spatial_adapter/jdbcmysql' +require 'db/jdbcmysql_raw' +require 'models/common' + +describe ActiveRecord::ConnectionAdapters::MysqlAdapter do + it_should_behave_like 'common model actions' + it_should_behave_like 'a modified mysql adapter' do + let(:establish){ jdbcmysql_connection } + end + it_should_behave_like 'spatially enabled migrations' do + let(:establish){ jdbcmysql_connection } + end + it_should_behave_like 'spatially enabled schema dump' do + let(:establish){ jdbcmysql_connection } + end + it_should_behave_like 'spatially enabled models' do + let(:establish){ jdbcmysql_connection } + end +end diff --git a/spec/mysql/connection_adapter_spec.rb b/spec/mysql/connection_adapter_spec.rb deleted file mode 100644 index f7bcccd..0000000 --- a/spec/mysql/connection_adapter_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'spec_helper' -require 'spatial_adapter/mysql' -require 'db/mysql_raw' -require 'models/common' - -describe "Modified MysqlAdapter" do - before :each do - mysql_connection - @connection = ActiveRecord::Base.connection - end - - describe '#supports_geographic?' do - it "should be false" do - @connection.supports_geographic?.should == false - end - end - - describe "#columns" do - describe "type" do - it "should be SpatialMysqlColumn if column is a spatial data type" do - PointModel.columns.select{|c| c.name == 'geom'}.first.should be_a(ActiveRecord::ConnectionAdapters::SpatialMysqlColumn) - end - - it "should be SpatialMysqlColumn if column is not a spatial data type" do - PointModel.columns.select{|c| c.name == 'extra'}.first.should be_a(ActiveRecord::ConnectionAdapters::MysqlColumn) - end - end - - describe "@geometry_type" do - it "should be :point for columns restricted to POINT types" do - PointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :point - end - - it "should be :line_string for columns restricted to LINESTRING types" do - LineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :line_string - end - - it "should be :polygon for columns restricted to POLYGON types" do - PolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :polygon - end - - it "should be :multi_point for columns restricted to MULTIPOINT types" do - MultiPointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_point - end - - it "should be :multi_line_string for columns restricted to MULTILINESTRING types" do - MultiLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_line_string - end - - it "should be :multi_polygon for columns restricted to MULTIPOLYGON types" do - MultiPolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_polygon - end - - it "should be :geometry_collection for columns restricted to GEOMETRYCOLLECTION types" do - GeometryCollectionModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry_collection - end - - it "should be :geometry for columns not restricted to a type" do - GeometryModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry - end - end - end - - describe "#indexes" do - before :each do - @indexes = @connection.indexes('point_models') - end - - it "should return an IndexDefinition for each index on the table" do - @indexes.should have(2).items - @indexes.each do |i| - i.should be_a(ActiveRecord::ConnectionAdapters::IndexDefinition) - end - end - - it "should indicate the correct columns in the index" do - @indexes.select{|i| i.name == 'index_point_models_on_geom'}.first.columns.should == ['geom'] - @indexes.select{|i| i.name == 'index_point_models_on_extra'}.first.columns.should == ['extra', 'more_extra'] - end - - it "should be marked as spatial if a spatial index" do - @indexes.select{|i| i.columns.include?('geom')}.first.spatial.should == true - end - - it "should not be marked as spatial if not a spatial index" do - @indexes.select{|i| i.columns.include?('extra')}.first.spatial.should == false - end - end - - describe "#add_index" do - after :each do - @connection.should_receive(:execute).with(any_args()) - @connection.remove_index('geometry_models', 'geom') - end - - it "should create a spatial index given :spatial => true" do - @connection.should_receive(:execute).with(/create spatial index/i) - @connection.add_index('geometry_models', 'geom', :spatial => true) - end - - it "should not create a spatial index unless specified" do - @connection.should_not_receive(:execute).with(/create spatial index/i) - @connection.add_index('geometry_models', 'extra') - end - end -end \ No newline at end of file diff --git a/spec/mysql/models_spec.rb b/spec/mysql/models_spec.rb deleted file mode 100644 index 8681392..0000000 --- a/spec/mysql/models_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'spec_helper' -require 'shared_examples' -require 'spatial_adapter/mysql' -require 'db/mysql_raw' -require 'models/common' - -describe "Spatially-enabled Models" do - before :each do - mysql_connection - @connection = ActiveRecord::Base.connection - end - - describe "inserting records" do - it 'should save Point objects' do - model = PointModel.new(:extra => 'test', :geom => GeometryFactory.point) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.point.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save LineString objects' do - model = LineStringModel.new(:extra => 'test', :geom => GeometryFactory.line_string) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.line_string.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save Polygon objects' do - model = PolygonModel.new(:extra => 'test', :geom => GeometryFactory.polygon) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.polygon.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save MultiPoint objects' do - model = MultiPointModel.new(:extra => 'test', :geom => GeometryFactory.multi_point) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_point.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save MultiLineString objects' do - model = MultiLineStringModel.new(:extra => 'test', :geom => GeometryFactory.multi_line_string) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_line_string.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save MultiPolygon objects' do - model = MultiPolygonModel.new(:extra => 'test', :geom => GeometryFactory.multi_polygon) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_polygon.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save GeometryCollection objects' do - model = GeometryCollectionModel.new(:extra => 'test', :geom => GeometryFactory.geometry_collection) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.geometry_collection.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save Geometry objects' do - model = GeometryModel.new(:extra => 'test', :geom => GeometryFactory.point) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.point.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - end - - include CommonModelActions -end - diff --git a/spec/mysql2/connection_adapter_spec.rb b/spec/mysql2/connection_adapter_spec.rb deleted file mode 100644 index 31f7f09..0000000 --- a/spec/mysql2/connection_adapter_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'spec_helper' -require 'spatial_adapter/mysql2' -require 'db/mysql2_raw' -require 'models/common' - -describe "Modified Mysql2Adapter" do - before :each do - mysql2_connection - @connection = ActiveRecord::Base.connection - end - - describe '#supports_geographic?' do - it "should be false" do - @connection.supports_geographic?.should == false - end - end - - describe "#columns" do - describe "type" do - it "should be SpatialMysql2Column if column is a spatial data type" do - PointModel.columns.select{|c| c.name == 'geom'}.first.should be_a(ActiveRecord::ConnectionAdapters::SpatialMysql2Column) - end - - it "should be Mysql2Column if column is not a spatial data type" do - PointModel.columns.select{|c| c.name == 'extra'}.first.should be_a(ActiveRecord::ConnectionAdapters::Mysql2Column) - end - end - - describe "@geometry_type" do - it "should be :point for columns restricted to POINT types" do - PointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :point - end - - it "should be :line_string for columns restricted to LINESTRING types" do - LineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :line_string - end - - it "should be :polygon for columns restricted to POLYGON types" do - PolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :polygon - end - - it "should be :multi_point for columns restricted to MULTIPOINT types" do - MultiPointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_point - end - - it "should be :multi_line_string for columns restricted to MULTILINESTRING types" do - MultiLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_line_string - end - - it "should be :multi_polygon for columns restricted to MULTIPOLYGON types" do - MultiPolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_polygon - end - - it "should be :geometry_collection for columns restricted to GEOMETRYCOLLECTION types" do - GeometryCollectionModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry_collection - end - - it "should be :geometry for columns not restricted to a type" do - GeometryModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry - end - end - end - - describe "#indexes" do - before :each do - @indexes = @connection.indexes('point_models') - end - - it "should return an IndexDefinition for each index on the table" do - @indexes.should have(2).items - @indexes.each do |i| - i.should be_a(ActiveRecord::ConnectionAdapters::IndexDefinition) - end - end - - it "should indicate the correct columns in the index" do - @indexes.select{|i| i.name == 'index_point_models_on_geom'}.first.columns.should == ['geom'] - @indexes.select{|i| i.name == 'index_point_models_on_extra'}.first.columns.should == ['extra', 'more_extra'] - end - - it "should be marked as spatial if a spatial index" do - @indexes.select{|i| i.columns.include?('geom')}.first.spatial.should == true - end - - it "should not be marked as spatial if not a spatial index" do - @indexes.select{|i| i.columns.include?('extra')}.first.spatial.should == false - end - end - - describe "#add_index" do - after :each do - @connection.should_receive(:execute).with(any_args()) - @connection.remove_index('geometry_models', 'geom') - end - - it "should create a spatial index given :spatial => true" do - @connection.should_receive(:execute).with(/create spatial index/i) - @connection.add_index('geometry_models', 'geom', :spatial => true) - end - - it "should not create a spatial index unless specified" do - @connection.should_not_receive(:execute).with(/create spatial index/i) - @connection.add_index('geometry_models', 'extra') - end - end -end \ No newline at end of file diff --git a/spec/mysql2/migration_spec.rb b/spec/mysql2/migration_spec.rb deleted file mode 100644 index 01bf6eb..0000000 --- a/spec/mysql2/migration_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'spec_helper' -require 'spatial_adapter/mysql2' - -class MigratedGeometryModel < ActiveRecord::Base -end - -describe "Spatially-enabled Migrations" do - before :each do - mysql2_connection - @connection = ActiveRecord::Base.connection - end - - describe "creating tables" do - after :each do - @connection.drop_table "migrated_geometry_models" - end - - SpatialAdapter.geometry_data_types.keys.each do |type| - it "should create #{type.to_s} columns" do - ActiveRecord::Schema.define do - create_table :migrated_geometry_models, :force => true do |t| - t.integer :extra - t.send(type, :geom) - end - end - - geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first - geom_column.should be_a(SpatialAdapter::SpatialColumn) - geom_column.geometry_type.should == type - geom_column.type.should == :string - end - end - end - - describe "adding columns" do - before :each do - ActiveRecord::Schema.define do - create_table :migrated_geometry_models, :force => true do |t| - t.integer :extra - end - end - end - - after :each do - @connection.drop_table "migrated_geometry_models" - end - - SpatialAdapter.geometry_data_types.keys.each do |type| - it "should add #{type.to_s} columns" do - ActiveRecord::Schema.define do - add_column :migrated_geometry_models, :geom, type - end - - geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first - geom_column.should be_a(SpatialAdapter::SpatialColumn) - geom_column.geometry_type.should == type - geom_column.type.should == :string - geom_column.with_z.should == false - geom_column.with_m.should == false - geom_column.srid.should == -1 - end - end - end -end \ No newline at end of file diff --git a/spec/mysql2/models_spec.rb b/spec/mysql2/models_spec.rb deleted file mode 100644 index 0a28172..0000000 --- a/spec/mysql2/models_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'spec_helper' -require 'shared_examples' -require 'spatial_adapter/mysql2' -require 'db/mysql2_raw' -require 'models/common' - -describe "Spatially-enabled Models" do - before :each do - mysql2_connection - @connection = ActiveRecord::Base.connection - end - - describe "inserting records" do - it 'should save Point objects' do - model = PointModel.new(:extra => 'test', :geom => GeometryFactory.point) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.point.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save LineString objects' do - model = LineStringModel.new(:extra => 'test', :geom => GeometryFactory.line_string) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.line_string.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save Polygon objects' do - model = PolygonModel.new(:extra => 'test', :geom => GeometryFactory.polygon) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.polygon.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save MultiPoint objects' do - model = MultiPointModel.new(:extra => 'test', :geom => GeometryFactory.multi_point) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_point.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save MultiLineString objects' do - model = MultiLineStringModel.new(:extra => 'test', :geom => GeometryFactory.multi_line_string) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_line_string.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save MultiPolygon objects' do - model = MultiPolygonModel.new(:extra => 'test', :geom => GeometryFactory.multi_polygon) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_polygon.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save GeometryCollection objects' do - model = GeometryCollectionModel.new(:extra => 'test', :geom => GeometryFactory.geometry_collection) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.geometry_collection.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - - it 'should save Geometry objects' do - model = GeometryModel.new(:extra => 'test', :geom => GeometryFactory.point) - @connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.point.as_hex_wkb), anything(), anything(), anything(), anything()) - model.save.should == true - end - end - - include CommonModelActions -end - diff --git a/spec/mysql2/schema_dumper_spec.rb b/spec/mysql2/schema_dumper_spec.rb deleted file mode 100644 index dee7cf3..0000000 --- a/spec/mysql2/schema_dumper_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' -require 'spatial_adapter/mysql2' - -describe "Spatially-enabled Schema Dumps" do - before :all do - mysql2_connection - @connection = ActiveRecord::Base.connection - - # Create a new table - ActiveRecord::Schema.define do - create_table :migrated_geometry_models, :options=> "ENGINE=MyISAM", :force => true do |t| - t.integer :extra - t.point :geom, :null => false - end - add_index :migrated_geometry_models, :geom, :spatial => true, :name => 'test_spatial_index' - end - - File.open('schema.rb', "w") do |file| - ActiveRecord::SchemaDumper.dump(@connection, file) - end - - # Drop the original table - @connection.drop_table "migrated_geometry_models" - - # Load the dumped schema - load('schema.rb') - end - - after :all do - # delete the schema file - File.delete('schema.rb') - - # Drop the new table - @connection.drop_table "migrated_geometry_models" - end - - it "should preserve spatial attributes of tables" do - columns = @connection.columns("migrated_geometry_models") - - columns.should have(3).items - geom_column = columns.select{|c| c.name == 'geom'}.first - geom_column.should be_a(SpatialAdapter::SpatialColumn) - geom_column.geometry_type.should == :point - geom_column.type.should == :string - end - - it "should preserve spatial indexes" do - indexes = @connection.indexes("migrated_geometry_models") - - indexes.should have(1).item - - indexes.first.name.should == 'test_spatial_index' - indexes.first.columns.should == ["geom"] - indexes.first.spatial.should == true - end -end \ No newline at end of file diff --git a/spec/mysql2_spec.rb b/spec/mysql2_spec.rb new file mode 100644 index 0000000..bf0dbda --- /dev/null +++ b/spec/mysql2_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' +require 'shared/mysql_connection_adapter_spec' +require 'shared/mysql_migration_spec' +require 'shared/mysql_schema_dumper_spec' +require 'shared/mysql_models_spec' +require 'shared/common_model_actions_spec' +require 'spatial_adapter/mysql2' +require 'db/mysql2_raw' +require 'models/common' + +describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do + it_should_behave_like 'common model actions' + it_should_behave_like 'a modified mysql adapter' do + let(:establish){ mysql2_connection } + let(:column) do + ActiveRecord::ConnectionAdapters::Mysql2Column + end + let(:spatial_column) do + ActiveRecord::ConnectionAdapters::SpatialMysql2Column + end + end + it_should_behave_like 'spatially enabled migrations' do + let(:establish){ mysql2_connection } + end + it_should_behave_like 'spatially enabled schema dump' do + let(:establish){ mysql2_connection } + end + it_should_behave_like 'spatially enabled models' do + let(:establish){ mysql2_connection } + end +end diff --git a/spec/mysql_spec.rb b/spec/mysql_spec.rb new file mode 100644 index 0000000..46e2f8b --- /dev/null +++ b/spec/mysql_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' +require 'shared/mysql_connection_adapter_spec' +require 'shared/mysql_migration_spec' +require 'shared/mysql_schema_dumper_spec' +require 'shared/mysql_models_spec' +require 'shared/common_model_actions_spec' +require 'spatial_adapter/mysql' +require 'db/mysql_raw' +require 'models/common' + +describe ActiveRecord::ConnectionAdapters::MysqlAdapter do + it_should_behave_like 'common model actions' + it_should_behave_like 'a modified mysql adapter' + it_should_behave_like 'spatially enabled migrations' + it_should_behave_like 'spatially enabled schema dump' + it_should_behave_like 'spatially enabled models' +end diff --git a/spec/postgresql/connection_adapter_spec.rb b/spec/postgresql/connection_adapter_spec.rb index 1c6727c..715d185 100644 --- a/spec/postgresql/connection_adapter_spec.rb +++ b/spec/postgresql/connection_adapter_spec.rb @@ -8,66 +8,66 @@ postgis_connection @connection = ActiveRecord::Base.connection end - + describe '#postgis_version' do it 'should report a version number if PostGIS is installed' do @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_return('POSTGIS="1.5.0" GEOS="3.2.0-CAPI-1.6.0" PROJ="Rel. 4.7.1, 23 September 2009" LIBXML="2.7.6" USE_STATS') @connection.postgis_version.should_not be_nil end - + it 'should report nil if PostGIS is not installed' do @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_raise(ActiveRecord::StatementInvalid) @connection.postgis_version.should be_nil end end - + describe '#postgis_major_version' do it 'should be the first component of the version number' do @connection.stub!(:postgis_version).and_return('1.5.0') @connection.postgis_major_version.should == 1 end - + it 'should be nil if PostGIS is not installed' do @connection.stub!(:postgis_version).and_return(nil) @connection.postgis_major_version.should be_nil end end - + describe '#postgis_minor_version' do it 'should be the second component of the version number' do @connection.stub!(:postgis_version).and_return('1.5.0') @connection.postgis_minor_version.should == 5 end - + it 'should be nil if PostGIS is not installed' do @connection.stub!(:postgis_version).and_return(nil) @connection.postgis_minor_version.should be_nil end end - + describe '#spatial?' do it 'should be true if PostGIS is installed' do @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_return('POSTGIS="1.5.0" GEOS="3.2.0-CAPI-1.6.0" PROJ="Rel. 4.7.1, 23 September 2009" LIBXML="2.7.6" USE_STATS') @connection.should be_spatial end - + it 'should be false if PostGIS is not installed' do @connection.should_receive(:select_value).with('SELECT postgis_full_version()').and_raise(ActiveRecord::StatementInvalid) @connection.should_not be_spatial end end - + describe '#supports_geographic?' do it "should be true for PostGIS version 1.5.0" do @connection.stub!(:postgis_version).and_return('1.5.0') @connection.supports_geographic?.should == true end - + it "should be true for PostGIS newer than 1.5.0" do @connection.stub!(:postgis_version).and_return('1.5.1') @connection.supports_geographic?.should == true end - + it "should be true for PostGIS older than 1.5.0" do @connection.stub!(:postgis_version).and_return('1.4.0') @connection.supports_geographic?.should == false @@ -82,24 +82,24 @@ column.geometry_type.should == :point column.should_not be_geographic end - + it "should be a geographic SpatialPostgreSQLColumn if column is a geography data type" do column = GeographyPointModel.columns.select{|c| c.name == 'geom'}.first column.should be_a(ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn) column.geometry_type.should == :point column.should be_geographic end - + it "should be PostgreSQLColumn if column is not a spatial data type" do PointModel.columns.select{|c| c.name == 'extra'}.first.should be_a(ActiveRecord::ConnectionAdapters::PostgreSQLColumn) end end - + describe "@geometry_type" do it "should be :point for geometry columns restricted to POINT types" do PointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :point end - + it "should be :line_string for geometry columns restricted to LINESTRING types" do LineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :line_string end @@ -115,23 +115,23 @@ it "should be :multi_line_string for geometry columns restricted to MULTILINESTRING types" do MultiLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_line_string end - + it "should be :multi_polygon for geometry columns restricted to MULTIPOLYGON types" do MultiPolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_polygon end - + it "should be :geometry_collection for geometry columns restricted to GEOMETRYCOLLECTION types" do GeometryCollectionModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry_collection end - + it "should be :geometry for geometry columns not restricted to a type" do GeometryModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry end - + it "should be :point for geography columns restricted to POINT types" do GeographyPointModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :point end - + it "should be :line_string for geography columns restricted to LINESTRING types" do GeographyLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :line_string end @@ -147,51 +147,51 @@ it "should be :multi_line_string for geography columns restricted to MULTILINESTRING types" do GeographyMultiLineStringModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_line_string end - + it "should be :multi_polygon for geography columns restricted to MULTIPOLYGON types" do GeographyMultiPolygonModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :multi_polygon end - + it "should be :geometry_collection for geography columns restricted to GEOMETRYCOLLECTION types" do GeographyGeometryCollectionModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry_collection end - + it "should be :geometry for geography columns not restricted to a type" do GeographyModel.columns.select{|c| c.name == 'geom'}.first.geometry_type.should == :geometry end end end - + describe "#indexes" do before :each do @indexes = @connection.indexes('point_models') end - + it "should return an IndexDefinition for each index on the table" do @indexes.should have(2).items @indexes.each do |i| i.should be_a(ActiveRecord::ConnectionAdapters::IndexDefinition) end end - + it "should indicate the correct columns in the index" do @indexes.select{|i| i.name == 'index_point_models_on_geom'}.first.columns.should == ['geom'] @indexes.select{|i| i.name == 'index_point_models_on_extra'}.first.columns.should == ['extra', 'more_extra'] end - + it "should be marked as spatial if a GiST index on a geometry column" do @indexes.select{|i| i.name == 'index_point_models_on_geom'}.first.spatial.should == true end - + it "should be marked as spatial if a GiST index on a geography column" do @indexes = @connection.indexes('geography_point_models') @indexes.select{|i| i.name == 'index_geography_point_models_on_geom'}.first.spatial.should == true end - + it "should not be marked as spatial if not a GiST index" do @indexes.select{|i| i.name == 'index_point_models_on_extra'}.first.spatial.should == false end - + it "should not be marked as spatial if a GiST index on a non-geometry column" do @connection.execute(<<-SQL) create table non_spatial_models @@ -206,22 +206,17 @@ @indexes.select{|i| i.name == 'index_non_spatial_models_on_location'}.first.spatial.should == false @connection.execute 'drop table non_spatial_models' end - end - + end + describe "#add_index" do - after :each do - @connection.should_receive(:execute).with(any_args()) - @connection.remove_index('geometry_models', 'geom') - end - it "should create a spatial index given :spatial => true" do @connection.should_receive(:execute).with(/using gist/i) @connection.add_index('geometry_models', 'geom', :spatial => true) end - + it "should not create a spatial index unless specified" do @connection.should_not_receive(:execute).with(/using gist/i) @connection.add_index('geometry_models', 'extra') end end -end \ No newline at end of file +end diff --git a/spec/postgresql/migration_spec.rb b/spec/postgresql/migration_spec.rb index 5dd3d27..742bc0a 100644 --- a/spec/postgresql/migration_spec.rb +++ b/spec/postgresql/migration_spec.rb @@ -9,12 +9,12 @@ class MigratedGeometryModel < ActiveRecord::Base postgis_connection @connection = ActiveRecord::Base.connection end - + describe "creating tables" do after :each do @connection.drop_table "migrated_geometry_models" end - + SpatialAdapter.geometry_data_types.keys.each do |type| it "should create #{type.to_s} columns" do ActiveRecord::Schema.define do @@ -23,7 +23,7 @@ class MigratedGeometryModel < ActiveRecord::Base t.send(type, :geom) end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -33,7 +33,7 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_m.should == false geom_column.srid.should == -1 end - + it "should create #{type.to_s} geographic columns" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -41,9 +41,9 @@ class MigratedGeometryModel < ActiveRecord::Base t.column :geom, type, :geographic => true end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first - + geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string geom_column.geometry_type.should == type @@ -53,8 +53,8 @@ class MigratedGeometryModel < ActiveRecord::Base #geom_column.srid.should == 4326 # SRID is currently irrelevant for geography columns end end - - + + it "should create 3d (xyz) geometry columns" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -62,15 +62,15 @@ class MigratedGeometryModel < ActiveRecord::Base t.point :geom, :with_z => true end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.with_z.should == true geom_column.with_m.should == false geom_column.srid.should == -1 end - - + + it "should create 3d (xym) geometry columns" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -78,7 +78,7 @@ class MigratedGeometryModel < ActiveRecord::Base t.point :geom, :with_m => true end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == :point @@ -87,8 +87,8 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_m.should == true geom_column.srid.should == -1 end - - + + it "should create 4d (xyzm) geometry columns" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -96,7 +96,7 @@ class MigratedGeometryModel < ActiveRecord::Base t.point :geom, :with_z => true, :with_m => true end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == :point @@ -105,7 +105,7 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_m.should == true geom_column.srid.should == -1 end - + it "should create 3d (xyz) geographic columns" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -113,15 +113,15 @@ class MigratedGeometryModel < ActiveRecord::Base t.point :geom, :with_z => true, :geographic => true end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.should be_geographic geom_column.with_z.should == true geom_column.with_m.should == false end - - + + it "should create 3d (xym) geographic columns" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -129,7 +129,7 @@ class MigratedGeometryModel < ActiveRecord::Base t.point :geom, :with_m => true, :geographic => true end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == :point @@ -138,8 +138,8 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_z.should == false geom_column.with_m.should == true end - - + + it "should create 4d (xyzm) geographic columns" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -147,7 +147,7 @@ class MigratedGeometryModel < ActiveRecord::Base t.point :geom, :with_z => true, :with_m => true, :geographic => true end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == :point @@ -156,8 +156,8 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_z.should == true geom_column.with_m.should == true end - - + + it "should create geometry columns with specified SRID" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -165,7 +165,7 @@ class MigratedGeometryModel < ActiveRecord::Base t.geometry :geom, :srid => 4326 end end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -184,17 +184,17 @@ class MigratedGeometryModel < ActiveRecord::Base end end end - + after :each do @connection.drop_table "migrated_geometry_models" end - + SpatialAdapter.geometry_data_types.keys.each do |type| it "should add #{type.to_s} columns" do ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, type end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -204,12 +204,12 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.srid.should == -1 end end - + it "should add 3d (xyz) geometry columns" do ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, :point, :with_z => true end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -218,13 +218,13 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_m.should == false geom_column.srid.should == -1 end - - + + it "should add 3d (xym) geometry columns" do ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, :point, :with_m => true end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -233,13 +233,13 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_m.should == true geom_column.srid.should == -1 end - - + + it "should add 4d (xyzm) geometry columns" do ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, :point, :with_z => true, :with_m => true end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -248,12 +248,12 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_m.should == true geom_column.srid.should == -1 end - + it "should add 3d (xyz) geography columns" do ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, :point, :with_z => true, :geographic => true end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -262,13 +262,13 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_z.should == true geom_column.with_m.should == false end - - + + it "should add 3d (xym) geography columns" do ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, :point, :with_m => true, :geographic => true end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -277,13 +277,13 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.with_z.should == false geom_column.with_m.should == true end - - + + it "should add 4d (xyzm) geography columns" do ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, :point, :with_z => true, :with_m => true, :geographic => true end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.type.should == :string @@ -297,7 +297,7 @@ class MigratedGeometryModel < ActiveRecord::Base ActiveRecord::Schema.define do add_column :migrated_geometry_models, :geom, :geometry, :srid => 4326 end - + geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == :geometry @@ -307,12 +307,12 @@ class MigratedGeometryModel < ActiveRecord::Base geom_column.srid.should == 4326 end end - + describe "removing columns" do after :each do @connection.drop_table "migrated_geometry_models" end - + SpatialAdapter.geometry_data_types.keys.each do |type| it "should remove #{type.to_s} columns using DropGeometryColumn" do ActiveRecord::Schema.define do @@ -321,7 +321,7 @@ class MigratedGeometryModel < ActiveRecord::Base t.send(type, :geom) end end - + @connection.should_receive(:execute).with(/DropGeometryColumn(.*migrated_geometry_models.*geom)/) ActiveRecord::Schema.define do remove_column :migrated_geometry_models, :geom @@ -346,7 +346,7 @@ class MigratedGeometryModel < ActiveRecord::Base @connection.should_receive(:execute).with(anything()) end end - + it "should still remove non-spatial columns using ALTER TABLE DROP COLUMN" do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -362,4 +362,4 @@ class MigratedGeometryModel < ActiveRecord::Base @connection.should_receive(:execute).with(anything()) end end -end \ No newline at end of file +end diff --git a/spec/postgresql/models_spec.rb b/spec/postgresql/models_spec.rb index 38742cf..f0d3419 100644 --- a/spec/postgresql/models_spec.rb +++ b/spec/postgresql/models_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require 'shared_examples' +require 'shared/common_model_actions_spec' require 'spatial_adapter/postgresql' require 'db/postgis_raw' require 'models/common' @@ -9,68 +9,70 @@ postgis_connection @connection = ActiveRecord::Base.connection end - + + it_should_behave_like 'common model actions' + describe "inserting records" do it 'should save Point objects' do model = PointModel.new(:extra => 'test', :geom => GeometryFactory.point) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.point.as_hex_ewkb)) model.save.should == true end - + it 'should save LineString objects' do model = LineStringModel.new(:extra => 'test', :geom => GeometryFactory.line_string) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.line_string.as_hex_ewkb)) model.save.should == true end - + it 'should save Polygon objects' do model = PolygonModel.new(:extra => 'test', :geom => GeometryFactory.polygon) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.polygon.as_hex_ewkb)) model.save.should == true end - + it 'should save MultiPoint objects' do model = MultiPointModel.new(:extra => 'test', :geom => GeometryFactory.multi_point) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.multi_point.as_hex_ewkb)) model.save.should == true end - + it 'should save MultiLineString objects' do model = MultiLineStringModel.new(:extra => 'test', :geom => GeometryFactory.multi_line_string) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.multi_line_string.as_hex_ewkb)) model.save.should == true end - + it 'should save MultiPolygon objects' do model = MultiPolygonModel.new(:extra => 'test', :geom => GeometryFactory.multi_polygon) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.multi_polygon.as_hex_ewkb)) model.save.should == true end - + it 'should save GeometryCollection objects' do model = GeometryCollectionModel.new(:extra => 'test', :geom => GeometryFactory.geometry_collection) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.geometry_collection.as_hex_ewkb)) model.save.should == true end - + it 'should save Geometry objects' do model = GeometryModel.new(:extra => 'test', :geom => GeometryFactory.point) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.point.as_hex_ewkb)) model.save.should == true end - + it 'should save 3D Point (with Z coord) objects' do model = PointzModel.new(:extra => 'test', :geom => GeometryFactory.pointz) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.pointz.as_hex_ewkb)) model.save.should == true end - + it 'should save 3D Point (with M coord) objects' do model = PointmModel.new(:extra => 'test', :geom => GeometryFactory.pointm) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.pointm.as_hex_ewkb)) model.save.should == true end - + it 'should save 4D Point objects' do model = Point4Model.new(:extra => 'test', :geom => GeometryFactory.point4) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.point4.as_hex_ewkb)) @@ -82,61 +84,61 @@ @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.point.as_hex_ewkb)) model.save.should == true end - + it 'should save LineString geography objects' do model = GeographyLineStringModel.new(:extra => 'test', :geom => GeometryFactory.line_string) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.line_string.as_hex_ewkb)) model.save.should == true end - + it 'should save Polygon geography objects' do model = GeographyPolygonModel.new(:extra => 'test', :geom => GeometryFactory.polygon) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.polygon.as_hex_ewkb)) model.save.should == true end - + it 'should save MultiPoint geography objects' do model = GeographyMultiPointModel.new(:extra => 'test', :geom => GeometryFactory.multi_point) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.multi_point.as_hex_ewkb)) model.save.should == true end - + it 'should save MultiLineString geography objects' do model = GeographyMultiLineStringModel.new(:extra => 'test', :geom => GeometryFactory.multi_line_string) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.multi_line_string.as_hex_ewkb)) model.save.should == true end - + it 'should save MultiPolygon geography objects' do model = GeographyMultiPolygonModel.new(:extra => 'test', :geom => GeometryFactory.multi_polygon) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.multi_polygon.as_hex_ewkb)) model.save.should == true end - + it 'should save GeometryCollection geography objects' do model = GeographyGeometryCollectionModel.new(:extra => 'test', :geom => GeometryFactory.geometry_collection) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.geometry_collection.as_hex_ewkb)) model.save.should == true end - + it 'should save Geography objects' do model = GeographyModel.new(:extra => 'test', :geom => GeometryFactory.point) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.point.as_hex_ewkb)) model.save.should == true end - + it 'should save 3D Point (with Z coord) geography objects' do model = GeographyPointzModel.new(:extra => 'test', :geom => GeometryFactory.pointz) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.pointz.as_hex_ewkb)) model.save.should == true end - + it 'should save 3D Point (with M coord) geography objects' do model = GeographyPointmModel.new(:extra => 'test', :geom => GeometryFactory.pointm) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.pointm.as_hex_ewkb)) model.save.should == true end - + it 'should save 4D Point geography objects' do model = GeographyPoint4Model.new(:extra => 'test', :geom => GeometryFactory.point4) @connection.should_receive(:select_value).with(Regexp.new(GeometryFactory.point4.as_hex_ewkb)) @@ -144,19 +146,17 @@ end end - include CommonModelActions - - describe "finding records" do + describe "finding records" do it 'should retrieve 3D Point (with Z coord) objects' do model = PointzModel.create(:extra => 'test', :geom => GeometryFactory.pointz) PointzModel.find(model.id).geom.should == GeometryFactory.pointz end - + it 'should retrieve 3D Point (with M coord) objects' do model = GeographyPointmModel.create(:extra => 'test', :geom => GeometryFactory.pointm) GeographyPointmModel.find(model.id).geom.should == GeometryFactory.pointm end - + it 'should retrieve 4D Point objects' do model = GeographyPoint4Model.create(:extra => 'test', :geom => GeometryFactory.point4) GeographyPoint4Model.find(model.id).geom.should == GeometryFactory.point4 @@ -166,52 +166,52 @@ model = GeographyPointModel.create(:extra => 'test', :geom => GeometryFactory.point) GeographyPointModel.find(model.id).geom.should == GeometryFactory.point end - + it 'should retrieve LineString geography objects' do model = GeographyLineStringModel.create(:extra => 'test', :geom => GeometryFactory.line_string) GeographyLineStringModel.find(model.id).geom.should == GeometryFactory.line_string end - + it 'should retrieve Polygon geography objects' do model = GeographyPolygonModel.create(:extra => 'test', :geom => GeometryFactory.polygon) GeographyPolygonModel.find(model.id).geom.should == GeometryFactory.polygon end - + it 'should retrieve MultiPoint geography objects' do model = GeographyMultiPointModel.create(:extra => 'test', :geom => GeometryFactory.multi_point) GeographyMultiPointModel.find(model.id).geom.should == GeometryFactory.multi_point end - + it 'should retrieve MultiLineString geography objects' do model = GeographyMultiLineStringModel.create(:extra => 'test', :geom => GeometryFactory.multi_line_string) GeographyMultiLineStringModel.find(model.id).geom.should == GeometryFactory.multi_line_string end - + it 'should retrieve MultiPolygon geography objects' do model = GeographyMultiPolygonModel.create(:extra => 'test', :geom => GeometryFactory.multi_polygon) GeographyMultiPolygonModel.find(model.id).geom.should == GeometryFactory.multi_polygon end - + it 'should retrieve GeometryCollection geography objects' do model = GeographyGeometryCollectionModel.create(:extra => 'test', :geom => GeometryFactory.geometry_collection) GeographyGeometryCollectionModel.find(model.id).geom.should == GeometryFactory.geometry_collection end - + it 'should retrieve Geometry geography objects' do model = GeographyModel.create(:extra => 'test', :geom => GeometryFactory.point) GeographyModel.find(model.id).geom.should == GeometryFactory.point end - + it 'should retrieve 3D Point (with Z coord) geography objects' do model = GeographyPointzModel.create(:extra => 'test', :geom => GeometryFactory.pointz) GeographyPointzModel.find(model.id).geom.should == GeometryFactory.pointz end - + it 'should retrieve 3D Point (with M coord) geography objects' do model = GeographyPointmModel.create(:extra => 'test', :geom => GeometryFactory.pointm) GeographyPointmModel.find(model.id).geom.should == GeometryFactory.pointm end - + it 'should retrieve 4D Point geography objects' do model = GeographyPoint4Model.create(:extra => 'test', :geom => GeometryFactory.point4) GeographyPoint4Model.find(model.id).geom.should == GeometryFactory.point4 diff --git a/spec/postgresql/schema_dumper_spec.rb b/spec/postgresql/schema_dumper_spec.rb index 04b79f6..ab17f2a 100644 --- a/spec/postgresql/schema_dumper_spec.rb +++ b/spec/postgresql/schema_dumper_spec.rb @@ -20,18 +20,18 @@ end end - File.open('schema.rb', "w") do |file| + File.open('schema.rb', "w:UTF-8") do |file| ActiveRecord::SchemaDumper.dump(@connection, file) end - + # Drop the original tables @connection.drop_table "migrated_geometry_models" @connection.drop_table "migrated_geography_models" - + # Load the dumped schema load('schema.rb') end - + after :all do # delete the schema file File.delete('schema.rb') @@ -40,10 +40,10 @@ @connection.drop_table "migrated_geometry_models" @connection.drop_table "migrated_geography_models" end - + it "should preserve spatial attributes of geometry tables" do columns = @connection.columns("migrated_geometry_models") - + columns.should have(3).items geom_column = columns.select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) @@ -53,10 +53,10 @@ geom_column.with_m.should == true geom_column.srid.should == 4326 end - + it "should preserve spatial attributes of geography tables" do columns = @connection.columns("migrated_geography_models") - + columns.should have(3).items geom_column = columns.select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) @@ -66,14 +66,14 @@ geom_column.with_m.should == true geom_column.should be_geographic end - + it "should preserve spatial indexes" do indexes = @connection.indexes("migrated_geometry_models") - + indexes.should have(1).item - + indexes.first.name.should == 'test_spatial_index' indexes.first.columns.should == ["geom"] indexes.first.spatial.should == true end -end \ No newline at end of file +end diff --git a/spec/postgresql_spec.rb b/spec/postgresql_spec.rb new file mode 100644 index 0000000..bded2a9 --- /dev/null +++ b/spec/postgresql_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' +require 'postgresql/connection_adapter_spec' +require 'postgresql/migration_spec' +require 'postgresql/models_spec' +require 'postgresql/schema_dumper_spec' diff --git a/spec/shared_examples.rb b/spec/shared/common_model_actions_spec.rb similarity index 96% rename from spec/shared_examples.rb rename to spec/shared/common_model_actions_spec.rb index 8ccc585..965a0f9 100644 --- a/spec/shared_examples.rb +++ b/spec/shared/common_model_actions_spec.rb @@ -1,5 +1,5 @@ -share_as :CommonModelActions do - describe 'finding records' do +shared_examples_for 'common model actions' do + context 'finding records' do it 'should retrieve Point objects' do model = PointModel.create(:extra => 'test', :geom => GeometryFactory.point) PointModel.find(model.id).geom.should == GeometryFactory.point diff --git a/spec/shared/mysql_connection_adapter_spec.rb b/spec/shared/mysql_connection_adapter_spec.rb new file mode 100644 index 0000000..f4d7663 --- /dev/null +++ b/spec/shared/mysql_connection_adapter_spec.rb @@ -0,0 +1,110 @@ +shared_examples_for 'a modified mysql adapter' do + let(:establish){ mysql_connection } + let(:column){ ActiveRecord::ConnectionAdapters::MysqlColumn } + let(:spatial_column){ ActiveRecord::ConnectionAdapters::SpatialMysqlColumn } + + let(:connection) do + establish + ActiveRecord::Base.connection + end + + context '#supports_geographic?' do + it "should be false" do + connection.supports_geographic?.should == false + end + end + + context "#columns" do + context "type" do + it "should be SpatialMysqlColumn if column is a spatial data type" do + PointModel.columns.select{|c| c.name == 'geom'} \ + .first.should be_a(spatial_column) + end + + it "should be SpatialMysqlColumn if column is not a spatial data type" do + PointModel.columns.select{|c| c.name == 'extra'} \ + .first.should be_a(column) + end + end + + describe "@geometry_type" do + it "should be :point for columns restricted to POINT types" do + PointModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :point + end + + it "should be :line_string for columns restricted to LINESTRING types" do + LineStringModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :line_string + end + + it "should be :polygon for columns restricted to POLYGON types" do + PolygonModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :polygon + end + + it "should be :multi_point for columns restricted to MULTIPOINT types" do + MultiPointModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :multi_point + end + + it "should be :multi_line_string for columns restricted to MULTILINESTRING types" do + MultiLineStringModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :multi_line_string + end + + it "should be :multi_polygon for columns restricted to MULTIPOLYGON types" do + MultiPolygonModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :multi_polygon + end + + it "should be :geometry_collection for columns restricted to GEOMETRYCOLLECTION types" do + GeometryCollectionModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :geometry_collection + end + + it "should be :geometry for columns not restricted to a type" do + GeometryModel.columns.select{|c| c.name == 'geom'} \ + .first.geometry_type.should == :geometry + end + end + end + + context "#indexes" do + let(:indexes){ connection.indexes('point_models') } + + it "should return an IndexDefinition for each index on the table" do + indexes.should have(2).items + indexes.each do |i| + i.should be_a(ActiveRecord::ConnectionAdapters::IndexDefinition) + end + end + + it "should indicate the correct columns in the index" do + indexes.select{|i| i.name == 'index_point_models_on_geom'} \ + .first.columns.should == ['geom'] + indexes.select{|i| i.name == 'index_point_models_on_extra'} \ + .first.columns.should == ['extra', 'more_extra'] + end + + it "should be marked as spatial if a spatial index" do + indexes.select{|i| i.columns.include?('geom')}.first.spatial.should == true + end + + it "should not be marked as spatial if not a spatial index" do + indexes.select{|i| i.columns.include?('extra')}.first.spatial.should == false + end + end + + context "#add_index" do + it "should create a spatial index given :spatial => true" do + connection.should_receive(:execute).with(/create spatial index/i) + connection.add_index('geometry_models', 'geom', :spatial => true) + end + + it "should not create a spatial index unless specified" do + connection.should_not_receive(:execute).with(/create spatial index/i) + connection.add_index('geometry_models', 'extra') + end + end +end diff --git a/spec/mysql/migration_spec.rb b/spec/shared/mysql_migration_spec.rb similarity index 69% rename from spec/mysql/migration_spec.rb rename to spec/shared/mysql_migration_spec.rb index 7ab477a..d258b68 100644 --- a/spec/mysql/migration_spec.rb +++ b/spec/shared/mysql_migration_spec.rb @@ -1,20 +1,19 @@ -require 'spec_helper' -require 'spatial_adapter/mysql' - class MigratedGeometryModel < ActiveRecord::Base end -describe "Spatially-enabled Migrations" do - before :each do - mysql_connection - @connection = ActiveRecord::Base.connection +shared_examples_for 'spatially enabled migrations' do + let(:establish){ mysql_connection } + + let(:connection) do + establish + ActiveRecord::Base.connection end - - describe "creating tables" do + + context "creating tables" do after :each do - @connection.drop_table "migrated_geometry_models" + connection.drop_table "migrated_geometry_models" end - + SpatialAdapter.geometry_data_types.keys.each do |type| it "should create #{type.to_s} columns" do ActiveRecord::Schema.define do @@ -24,7 +23,8 @@ class MigratedGeometryModel < ActiveRecord::Base end end - geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first + geom_column = connection \ + .columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == type geom_column.type.should == :string @@ -32,7 +32,7 @@ class MigratedGeometryModel < ActiveRecord::Base end end - describe "adding columns" do + context "adding columns" do before :each do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :force => true do |t| @@ -40,9 +40,9 @@ class MigratedGeometryModel < ActiveRecord::Base end end end - + after :each do - @connection.drop_table "migrated_geometry_models" + connection.drop_table "migrated_geometry_models" end SpatialAdapter.geometry_data_types.keys.each do |type| @@ -51,7 +51,7 @@ class MigratedGeometryModel < ActiveRecord::Base add_column :migrated_geometry_models, :geom, type end - geom_column = @connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first + geom_column = connection.columns(:migrated_geometry_models).select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == type geom_column.type.should == :string @@ -61,4 +61,4 @@ class MigratedGeometryModel < ActiveRecord::Base end end end -end \ No newline at end of file +end diff --git a/spec/shared/mysql_models_spec.rb b/spec/shared/mysql_models_spec.rb new file mode 100644 index 0000000..693c24c --- /dev/null +++ b/spec/shared/mysql_models_spec.rb @@ -0,0 +1,58 @@ +shared_examples_for 'spatially enabled models' do + let(:establish){ mysql_connection } + + let(:connection) do + establish + ActiveRecord::Base.connection + end + + context "inserting records" do + it 'should save Point objects' do + model = PointModel.new(:extra => 'test', :geom => GeometryFactory.point) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.point.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + + it 'should save LineString objects' do + model = LineStringModel.new(:extra => 'test', :geom => GeometryFactory.line_string) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.line_string.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + + it 'should save Polygon objects' do + model = PolygonModel.new(:extra => 'test', :geom => GeometryFactory.polygon) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.polygon.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + + it 'should save MultiPoint objects' do + model = MultiPointModel.new(:extra => 'test', :geom => GeometryFactory.multi_point) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_point.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + + it 'should save MultiLineString objects' do + model = MultiLineStringModel.new(:extra => 'test', :geom => GeometryFactory.multi_line_string) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_line_string.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + + it 'should save MultiPolygon objects' do + model = MultiPolygonModel.new(:extra => 'test', :geom => GeometryFactory.multi_polygon) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.multi_polygon.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + + it 'should save GeometryCollection objects' do + model = GeometryCollectionModel.new(:extra => 'test', :geom => GeometryFactory.geometry_collection) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.geometry_collection.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + + it 'should save Geometry objects' do + model = GeometryModel.new(:extra => 'test', :geom => GeometryFactory.point) + connection.should_receive(:insert_sql).with(Regexp.new(GeometryFactory.point.as_hex_wkb), anything(), anything(), anything(), anything()) + model.save.should == true + end + end +end diff --git a/spec/mysql/schema_dumper_spec.rb b/spec/shared/mysql_schema_dumper_spec.rb similarity index 56% rename from spec/mysql/schema_dumper_spec.rb rename to spec/shared/mysql_schema_dumper_spec.rb index e5d1b02..b2f609d 100644 --- a/spec/mysql/schema_dumper_spec.rb +++ b/spec/shared/mysql_schema_dumper_spec.rb @@ -1,56 +1,53 @@ -require 'spec_helper' -require 'spatial_adapter/mysql' +shared_examples_for 'spatially enabled schema dump' do + let(:establish){ mysql_connection } -describe "Spatially-enabled Schema Dumps" do - before :all do - mysql_connection - @connection = ActiveRecord::Base.connection + let(:connection) do + establish + ActiveRecord::Base.connection + end - # Create a new table + before :all do ActiveRecord::Schema.define do create_table :migrated_geometry_models, :options=> "ENGINE=MyISAM", :force => true do |t| t.integer :extra t.point :geom, :null => false end - add_index :migrated_geometry_models, :geom, :spatial => true, :name => 'test_spatial_index' + add_index :migrated_geometry_models, :geom, :spatial => true, + :name => 'test_spatial_index' end - File.open('schema.rb', "w") do |file| - ActiveRecord::SchemaDumper.dump(@connection, file) + File.open('schema.rb', "w:UTF-8") do |file| + ActiveRecord::SchemaDumper.dump(connection, file) end - - # Drop the original table - @connection.drop_table "migrated_geometry_models" - - # Load the dumped schema + + connection.drop_table "migrated_geometry_models" + load('schema.rb') end - + after :all do - # delete the schema file File.delete('schema.rb') - # Drop the new table - @connection.drop_table "migrated_geometry_models" + connection.drop_table "migrated_geometry_models" end - + it "should preserve spatial attributes of tables" do - columns = @connection.columns("migrated_geometry_models") - + columns = connection.columns("migrated_geometry_models") + columns.should have(3).items geom_column = columns.select{|c| c.name == 'geom'}.first geom_column.should be_a(SpatialAdapter::SpatialColumn) geom_column.geometry_type.should == :point geom_column.type.should == :string end - + it "should preserve spatial indexes" do - indexes = @connection.indexes("migrated_geometry_models") - + indexes = connection.indexes("migrated_geometry_models") + indexes.should have(1).item - + indexes.first.name.should == 'test_spatial_index' indexes.first.columns.should == ["geom"] indexes.first.spatial.should == true end -end \ No newline at end of file +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3c28c42..8f2de3b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,5 @@ -require 'rubygems' -require 'spec' -require 'geo_ruby' -gem 'activerecord', '=3.0.3' -require 'active_record' - -$:.unshift((File.join(File.dirname(__FILE__), '..', 'lib'))) - -include GeoRuby::SimpleFeatures - +require 'bundler' +Bundler.require(:default) def postgis_connection ActiveRecord::Base.establish_connection( @@ -28,7 +20,7 @@ def mysql_connection :username => 'root', :host => 'localhost' ) - + # Don't output migration logging ActiveRecord::Migration.verbose = false end @@ -40,7 +32,19 @@ def mysql2_connection :username => 'root', :host => 'localhost' ) - + + # Don't output migration logging + ActiveRecord::Migration.verbose = false +end + +def jdbcmysql_connection + ActiveRecord::Base.establish_connection( + :adapter => 'jdbcmysql', + :database => 'spatial_adapter', + :username => 'root', + :host => 'localhost' + ) + # Don't output migration logging ActiveRecord::Migration.verbose = false end @@ -50,39 +54,39 @@ class << self def point Point.from_x_y(1, 2, 4326) end - + def line_string LineString.from_coordinates([[1.4,2.5],[1.5,6.7]], 4326) end - + def polygon Polygon.from_coordinates([[[12.4,-45.3],[45.4,41.6],[4.456,1.0698],[12.4,-45.3]],[[2.4,5.3],[5.4,1.4263],[14.46,1.06],[2.4,5.3]]], 4326) end - + def multi_point MultiPoint.from_coordinates([[12.4,-23.3],[-65.1,23.4],[23.55555555,23]], 4326) end - + def multi_line_string MultiLineString.from_line_strings([LineString.from_coordinates([[1.5,45.2],[-54.12312,-0.012]]),LineString.from_coordinates([[1.5,45.2],[-54.12312,-0.012],[45.123,23.3]])], 4326) end - + def multi_polygon MultiPolygon.from_polygons([Polygon.from_coordinates([[[12.4,-45.3],[45.4,41.6],[4.456,1.0698],[12.4,-45.3]],[[2.4,5.3],[5.4,1.4263],[14.46,1.06],[2.4,5.3]]]),Polygon.from_coordinates([[[0,0],[4,0],[4,4],[0,4],[0,0]],[[1,1],[3,1],[3,3],[1,3],[1,1]]])], 4326) end - + def geometry_collection GeometryCollection.from_geometries([Point.from_x_y(4.67,45.4),LineString.from_coordinates([[5.7,12.45],[67.55,54]])], 4326) end - + def pointz Point.from_x_y_z(1, 2, 3, 4326) end - + def pointm Point.from_x_y_m(1, 2, 3, 4326) end - + def point4 Point.from_x_y_z_m(1, 2, 3, 4, 4326) end From 1e9a903c3b9ce8254eba94a9d578be1b9878d043 Mon Sep 17 00:00:00 2001 From: Sean Todd Date: Tue, 7 Feb 2012 17:07:34 -0800 Subject: [PATCH 2/4] Changed Mysql2Column to Mysql2Adapter::Column --- lib/spatial_adapter/mysql2.rb | 4 ++-- spec/mysql2_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/spatial_adapter/mysql2.rb b/lib/spatial_adapter/mysql2.rb index 0c0b8cc..b67d5c5 100644 --- a/lib/spatial_adapter/mysql2.rb +++ b/lib/spatial_adapter/mysql2.rb @@ -3,7 +3,7 @@ require 'active_record/connection_adapters/mysql2_adapter' module ActiveRecord::ConnectionAdapters - class SpatialMysql2Column < Mysql2Column + class SpatialMysql2Column < Mysql2Adapter::Column include SpatialAdapter::SpatialColumn extend SpatialAdapter::Base::Mysql::SpatialColumn end @@ -18,7 +18,7 @@ def columns(table_name, name = nil)#:nodoc: if field[1] =~ GEOMETRY_REGEXP ActiveRecord::ConnectionAdapters::SpatialMysql2Column else - ActiveRecord::ConnectionAdapters::Mysql2Column + ActiveRecord::ConnectionAdapters::Mysql2Adapter::Column end klass.new(field[0], field[4], field[1], field[2] == "YES") end diff --git a/spec/mysql2_spec.rb b/spec/mysql2_spec.rb index bf0dbda..8e7b2a0 100644 --- a/spec/mysql2_spec.rb +++ b/spec/mysql2_spec.rb @@ -13,7 +13,7 @@ it_should_behave_like 'a modified mysql adapter' do let(:establish){ mysql2_connection } let(:column) do - ActiveRecord::ConnectionAdapters::Mysql2Column + ActiveRecord::ConnectionAdapters::Mysql2Adapter::Column end let(:spatial_column) do ActiveRecord::ConnectionAdapters::SpatialMysql2Column From f36d20623cef4dd3fd19c8bea25cc92fda6c4b53 Mon Sep 17 00:00:00 2001 From: Sean Todd Date: Tue, 7 Feb 2012 17:11:24 -0800 Subject: [PATCH 3/4] Updated the gem spec file to use activerecord >= 3.2.0 --- spatial_adapter.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spatial_adapter.gemspec b/spatial_adapter.gemspec index 07804c3..bf650f1 100644 --- a/spatial_adapter.gemspec +++ b/spatial_adapter.gemspec @@ -34,6 +34,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'activerecord-jdbcmysql-adapter' end - s.add_dependency 'activerecord', '>= 2.2.2', '< 3.1.0' + s.add_dependency 'activerecord', '>= 3.2.0' s.add_dependency 'GeoRuby', '>= 1.3.0' end From cf7b1bdc4d0ce1f5642001196af92bd61853ebc7 Mon Sep 17 00:00:00 2001 From: Sean Todd Date: Tue, 7 Feb 2012 17:38:31 -0800 Subject: [PATCH 4/4] Updated README to show the correct ActiveRecord version required --- README.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rdoc b/README.rdoc index fbf1a52..76e0b98 100644 --- a/README.rdoc +++ b/README.rdoc @@ -11,7 +11,7 @@ handle spatial datatypes in the following databases: The following gems are required: - GeoRuby -- ActiveRecord (version 2.2.2 and up) +- ActiveRecord (version 3.2.0 and up) For PostgreSQL: