From 67d0a74ed680326248ac921536cf036e46aaebf2 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Sun, 14 Aug 2011 16:14:41 -0700 Subject: [PATCH 01/13] Support rspec 2 for development --- Rakefile | 7 ++++--- spec/spec_helper.rb | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index d904afa..1598fb4 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,11 @@ require 'rubygems' -require 'spec/rake/spectask' +require 'rspec/core/rake_task' [: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"] + RSpec::Core::RakeTask.new("spec:#{adapter.to_s}") do |spec| + spec.pattern = "spec/#{adapter}/*_spec.rb" + spec.rspec_opts = ['--backtrace'] end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3c28c42..3fa69bb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,5 @@ require 'rubygems' -require 'spec' +require 'rspec' require 'geo_ruby' gem 'activerecord', '=3.0.3' require 'active_record' From 2f396026f786aa67282c9673ec136125ed8ebbaa Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Sun, 14 Aug 2011 16:15:21 -0700 Subject: [PATCH 02/13] Use method-level rescues for simplicity --- lib/spatial_adapter/mysql.rb | 8 +++----- lib/spatial_adapter/mysql2.rb | 8 +++----- lib/spatial_adapter/postgresql.rb | 8 +++----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/spatial_adapter/mysql.rb b/lib/spatial_adapter/mysql.rb index 8a3b6ed..c9c56db 100644 --- a/lib/spatial_adapter/mysql.rb +++ b/lib/spatial_adapter/mysql.rb @@ -87,11 +87,9 @@ class SpatialMysqlColumn < MysqlColumn #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 + GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1]) + rescue Exception => exception + nil end end end diff --git a/lib/spatial_adapter/mysql2.rb b/lib/spatial_adapter/mysql2.rb index 1636483..2535d2d 100644 --- a/lib/spatial_adapter/mysql2.rb +++ b/lib/spatial_adapter/mysql2.rb @@ -86,11 +86,9 @@ class SpatialMysql2Column < Mysql2Column #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 + GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1]) + rescue Exception => exception + nil end end end diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index d0da480..b84b757 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -5,11 +5,9 @@ include SpatialAdapter def postgis_version - begin - select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] - rescue ActiveRecord::StatementInvalid - nil - end + select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] + rescue ActiveRecord::StatementInvalid + nil end def postgis_major_version From 8ea88fb6d5edacf8bc54485dcae8f67860546c86 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Sun, 14 Aug 2011 16:17:55 -0700 Subject: [PATCH 03/13] Don't capture exceptions into locals if we're not going to use them. --- lib/spatial_adapter/mysql.rb | 2 +- lib/spatial_adapter/mysql2.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/spatial_adapter/mysql.rb b/lib/spatial_adapter/mysql.rb index c9c56db..6c13a8b 100644 --- a/lib/spatial_adapter/mysql.rb +++ b/lib/spatial_adapter/mysql.rb @@ -88,7 +88,7 @@ class SpatialMysqlColumn < MysqlColumn def self.string_to_geometry(string) return string unless string.is_a?(String) GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1]) - rescue Exception => exception + rescue Exception nil end end diff --git a/lib/spatial_adapter/mysql2.rb b/lib/spatial_adapter/mysql2.rb index 2535d2d..0152860 100644 --- a/lib/spatial_adapter/mysql2.rb +++ b/lib/spatial_adapter/mysql2.rb @@ -87,7 +87,7 @@ class SpatialMysql2Column < Mysql2Column def self.string_to_geometry(string) return string unless string.is_a?(String) GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1]) - rescue Exception => exception + rescue Exception nil end end From 1344bf09f1c81a31a382e0a96e8c99121be578fd Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Sun, 14 Aug 2011 16:33:53 -0700 Subject: [PATCH 04/13] Tidy up the postgres adapter a bit --- lib/spatial_adapter/postgresql.rb | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index b84b757..4d62dc5 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -165,9 +165,6 @@ def indexes(table_name, name = nil) ORDER BY i.relname SQL - - indexes = [] - indexes = result.map do |row| index_name = row[0] unique = row[1] == 't' @@ -190,17 +187,15 @@ def indexes(table_name, name = nil) 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 def disable_referential_integrity(&block) #:nodoc: - if supports_disable_referential_integrity?() then + if supports_disable_referential_integrity? 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 + if supports_disable_referential_integrity? execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) end end @@ -235,7 +230,6 @@ def column_spatial_info(table_name) end raw_geom_infos - end end @@ -270,8 +264,7 @@ def column(name, type, options = {}) end class PostgreSQLColumnDefinition < ColumnDefinition - attr_accessor :table_name - attr_accessor :srid, :with_z, :with_m, :geographic + attr_accessor :table_name, :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) @@ -302,13 +295,14 @@ 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 + dimension = + if with_m and with_z + 4 + elsif with_m or with_z + 3 + else + 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 From ad7b9b8b73ea9ac8185be3da26eba47602cc0c78 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Mon, 15 Aug 2011 01:08:10 -0700 Subject: [PATCH 05/13] Allow testing with activerecord > 3.0.3 --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3fa69bb..cf95f45 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,7 @@ require 'rubygems' require 'rspec' require 'geo_ruby' -gem 'activerecord', '=3.0.3' +gem 'activerecord', '>=3.0.3' require 'active_record' $:.unshift((File.join(File.dirname(__FILE__), '..', 'lib'))) From 9a2a6571bb785447aeb3a8465325a54cf1cd9a17 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Mon, 15 Aug 2011 01:24:23 -0700 Subject: [PATCH 06/13] move the postgres adapter extensions into an ActiveSupport::Concern module, with most methods relying directly on super rather than aliasing. Have some failures when moving the rest - not sure why. --- lib/spatial_adapter/postgresql.rb | 406 +++++++++++++++--------------- 1 file changed, 205 insertions(+), 201 deletions(-) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index 4d62dc5..21fed1b 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -1,238 +1,242 @@ require 'spatial_adapter' require 'active_record/connection_adapters/postgresql_adapter' -ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do - include SpatialAdapter - - def postgis_version - select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] - rescue ActiveRecord::StatementInvalid - nil - 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) +module SpatialAdapter + module PostgreSQLAdapterExtensions + extend ActiveSupport::Concern + + def postgis_version + select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] + rescue ActiveRecord::StatementInvalid + nil 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") - 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::PostgreSQLColumn.new(name, default, type, notnull == "f") - end + def postgis_major_version + version = postgis_version + version ? version.scan(/^(\d)\.\d\.\d$/)[0][0].to_i : nil 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 postgis_minor_version + version = postgis_version + version ? version.scan(/^\d\.(\d)\.\d$/)[0][0].to_i : nil + end - yield table_definition if block_given? + def spatial? + !postgis_version.nil? + end - if options[:force] && table_exists?(table_name) - drop_table(table_name, options) + def supports_geographic? + postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5) 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]}" + def native_database_types + super.merge(geometry_data_types) + end - # 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 + #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 + super end end - execute create_sql - 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 - 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) + yield table_definition if block_given? + + 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]}" + + # 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 + + execute create_sql 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 - else - geom_column.table_name = table_name - execute geom_column.to_sql + 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 + super(table_name, col.name) + end + end 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 + def tables_without_postgis + tables - %w{ geometry_columns spatial_ref_sys } 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 = 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 - 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 - end + def column_spatial_info(table_name) + constr = query("SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'") - def disable_referential_integrity(&block) #:nodoc: - if supports_disable_referential_integrity? - execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) - end - yield - ensure - if supports_disable_referential_integrity? - execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) - 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 - 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 + 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 + end end - end - raw_geom_infos.each_value do |raw_geom_info| - #check the presence of z and m - raw_geom_info.convert! + raw_geom_infos.each_value do |raw_geom_info| + #check the presence of z and m + raw_geom_info.convert! + end + + raw_geom_infos end - raw_geom_infos + included do + 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::PostgreSQLColumn.new(name, default, type, notnull == "f") + 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 + else + geom_column.table_name = table_name + execute geom_column.to_sql + 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 + 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 = 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 + 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 + end + + def disable_referential_integrity(&block) #:nodoc: + if supports_disable_referential_integrity? + execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + end + yield + ensure + if supports_disable_referential_integrity? + execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + end + end + end end end +ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do + include SpatialAdapter + include SpatialAdapter::PostgreSQLAdapterExtensions +end + module ActiveRecord module ConnectionAdapters class PostgreSQLTableDefinition < TableDefinition From 8fa78c9e3a74f095ed66c5982fbba5411c8ae53c Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Mon, 15 Aug 2011 01:29:09 -0700 Subject: [PATCH 07/13] Introduce type_cast for our postgres adapter, cutting our failures down to 24 from 46 --- lib/spatial_adapter/postgresql.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index 21fed1b..f8199fa 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -33,6 +33,14 @@ def native_database_types super.merge(geometry_data_types) end + def type_cast(value, column) + if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) + value.as_hex_ewkb + else + super + end + end + #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) From 3126e19e682ab9908c3fea20c874acf40ec09e87 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Mon, 15 Aug 2011 02:04:36 -0700 Subject: [PATCH 08/13] Fix that native_database_types wasn't being properly overridden --- lib/spatial_adapter/postgresql.rb | 8 ++++---- spec/postgresql/connection_adapter_spec.rb | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index f8199fa..641e34e 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -29,10 +29,6 @@ def supports_geographic? postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5) end - def native_database_types - super.merge(geometry_data_types) - end - def type_cast(value, column) if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) value.as_hex_ewkb @@ -122,6 +118,10 @@ def column_spatial_info(table_name) end included do + def native_database_types + super.merge(geometry_data_types) + end + def columns(table_name, name = nil) #:nodoc: raw_geom_infos = column_spatial_info(table_name) diff --git a/spec/postgresql/connection_adapter_spec.rb b/spec/postgresql/connection_adapter_spec.rb index 1c6727c..3d38f64 100644 --- a/spec/postgresql/connection_adapter_spec.rb +++ b/spec/postgresql/connection_adapter_spec.rb @@ -8,7 +8,13 @@ postgis_connection @connection = ActiveRecord::Base.connection end - + + describe '#native_database_types' do + it 'should include the geometry types' do + @connection.native_database_types.should include(@connection.geometry_data_types) + end + 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') From bcac321e6e9fc71c9412cfb1c37057a3583b8cd6 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Mon, 15 Aug 2011 14:24:11 -0700 Subject: [PATCH 09/13] Try to fix the native_database_types by delaying the adapter inclusion until after active record has been loaded. --- lib/spatial_adapter/postgresql.rb | 8 ++++---- lib/spatial_adapter/railtie.rb | 13 +++++++------ spec/postgresql/connection_adapter_spec.rb | 4 ++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index 641e34e..3f0235b 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -117,11 +117,11 @@ def column_spatial_info(table_name) raw_geom_infos end - included do - def native_database_types - super.merge(geometry_data_types) - end + def native_database_types + super.merge(geometry_data_types) + end + included do def columns(table_name, name = nil) #:nodoc: raw_geom_infos = column_spatial_info(table_name) diff --git a/lib/spatial_adapter/railtie.rb b/lib/spatial_adapter/railtie.rb index 7a2ab28..1668bb8 100644 --- a/lib/spatial_adapter/railtie.rb +++ b/lib/spatial_adapter/railtie.rb @@ -1,13 +1,14 @@ module SpatialAdapter class Railtie < Rails::Railtie initializer "spatial_adapter.load_current_database_adapter" do - adapter = ActiveRecord::Base.configurations[Rails.env]['adapter'] - begin - require "spatial_adapter/#{adapter}" - rescue LoadError - raise SpatialAdapter::NotCompatibleError.new("spatial_adapter does not currently support the #{adapter} database.") + ActiveSupport.on_load :active_record do + adapter = ActiveRecord::Base.configurations[Rails.env]['adapter'] + begin + require "spatial_adapter/#{adapter}" + rescue LoadError + raise SpatialAdapter::NotCompatibleError.new("spatial_adapter does not currently support the #{adapter} database.") + end end end end end - diff --git a/spec/postgresql/connection_adapter_spec.rb b/spec/postgresql/connection_adapter_spec.rb index 3d38f64..fd2b13e 100644 --- a/spec/postgresql/connection_adapter_spec.rb +++ b/spec/postgresql/connection_adapter_spec.rb @@ -13,6 +13,10 @@ it 'should include the geometry types' do @connection.native_database_types.should include(@connection.geometry_data_types) end + + it 'should include the basic types' do + @connection.native_database_types.should include(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES) + end end describe '#postgis_version' do From d04ea0d1ee7d76603b7236a451c66a569cd8236a Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Mon, 15 Aug 2011 18:16:00 -0700 Subject: [PATCH 10/13] Revert "move the postgres adapter extensions into an ActiveSupport::Concern module, with most methods relying directly on super rather than aliasing. Have some failures when moving the rest - not sure why." Was having trouble with module methods not overriding the base methods after inclusion - not sure what the cause for that was. This reverts commit 9a2a6571bb785447aeb3a8465325a54cf1cd9a17. Conflicts: lib/spatial_adapter/postgresql.rb --- lib/spatial_adapter/postgresql.rb | 417 +++++++++++++++--------------- 1 file changed, 207 insertions(+), 210 deletions(-) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index 3f0235b..e9adec8 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -1,248 +1,245 @@ require 'spatial_adapter' require 'active_record/connection_adapters/postgresql_adapter' -module SpatialAdapter - module PostgreSQLAdapterExtensions - extend ActiveSupport::Concern - - def postgis_version - select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] - rescue ActiveRecord::StatementInvalid - nil - 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 +ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do + include SpatialAdapter + + def postgis_version + select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0] + rescue ActiveRecord::StatementInvalid + nil + 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 - def supports_geographic? - postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5) + alias :original_type_cast :type_cast + def type_cast(value, column) + if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) + value.as_hex_ewkb + else + original_type_cast(value, column) end + end - def type_cast(value, column) - if value.kind_of?(GeoRuby::SimpleFeatures::Geometry) - value.as_hex_ewkb - else - super - 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 - #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}'" + 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 - super + 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 - - yield table_definition if block_given? - - 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]}" + 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 - # 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 + yield table_definition if block_given? - execute create_sql + if options[:force] && table_exists?(table_name) + drop_table(table_name, options) end - 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 - super(table_name, col.name) - end - end - end - 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]}" - def tables_without_postgis - tables - %w{ geometry_columns spatial_ref_sys } + # 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 - 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 + execute create_sql + end - 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! + 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 - raw_geom_infos[constr_def_a[3]].with_m = false + original_remove_column(table_name, col.name) 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}") - raw_geom_infos.each_value do |raw_geom_info| - #check the presence of z and m - raw_geom_info.convert! + 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 - - raw_geom_infos + else + original_add_column(table_name, column_name, type, options) end + end - def native_database_types - super.merge(geometry_data_types) + # 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 - included do - 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::PostgreSQLColumn.new(name, default, type, notnull == "f") - 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 - else - geom_column.table_name = table_name - execute geom_column.to_sql - 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) + # 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 = 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 + 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 + end - 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 + def disable_referential_integrity(&block) #:nodoc: + if supports_disable_referential_integrity? + execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + end + yield + ensure + if supports_disable_referential_integrity? + execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + end + 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 = 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 - 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 + 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 end + end - def disable_referential_integrity(&block) #:nodoc: - if supports_disable_referential_integrity? - execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) - end - yield - ensure - if supports_disable_referential_integrity? - 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! end - end -end -ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do - include SpatialAdapter - include SpatialAdapter::PostgreSQLAdapterExtensions + raw_geom_infos + end end module ActiveRecord From 868b6095b64bcfdabd10070d21b0021e552525d2 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Tue, 16 Aug 2011 00:49:23 -0700 Subject: [PATCH 11/13] Use explicit inner joins to add tables in the postgres indexes lookup, as is done in the rails 3.1 codebase --- lib/spatial_adapter/postgresql.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index e9adec8..10ce14c 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -162,15 +162,15 @@ def indexes(table_name, name = nil) # 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 + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + INNER JOIN pg_attribute a ON a.attrelid = t.oid + INNER JOIN pg_am am ON i.relam = am.oid 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 @@ -184,10 +184,10 @@ def indexes(table_name, name = nil) # 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 + FROM pg_attribute a + INNER JOIN pg_type t ON a.atttypid = t.oid 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 From fd09f853093d0af6cb26783ea2c0c2a43ebd640a Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Tue, 16 Aug 2011 02:32:08 -0700 Subject: [PATCH 12/13] Compact away the nil columns in postgres #indexes, as they do in 3.1 --- lib/spatial_adapter/postgresql.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spatial_adapter/postgresql.rb b/lib/spatial_adapter/postgresql.rb index 10ce14c..117983c 100644 --- a/lib/spatial_adapter/postgresql.rb +++ b/lib/spatial_adapter/postgresql.rb @@ -193,7 +193,7 @@ def indexes(table_name, name = nil) # 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 } + column_names = indkey.map {|attnum| columns[attnum] ? columns[attnum][0] : nil }.compact ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, spatial) end end From d64ac1c03c837d6fffddfac1a0dc0818bf10bce6 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Tue, 16 Aug 2011 02:32:49 -0700 Subject: [PATCH 13/13] Fix the postgres add_index specs to use #indexes rather than mocks to detect their success, and properly remove the created indices --- spec/postgresql/connection_adapter_spec.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spec/postgresql/connection_adapter_spec.rb b/spec/postgresql/connection_adapter_spec.rb index fd2b13e..7e2afbb 100644 --- a/spec/postgresql/connection_adapter_spec.rb +++ b/spec/postgresql/connection_adapter_spec.rb @@ -219,19 +219,18 @@ 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) + @connection.indexes('geometry_models').first.spatial.should be_true + @connection.indexes('geometry_models').first.columns.should == ['geom'] + @connection.remove_index('geometry_models', 'geom') 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') + @connection.indexes('geometry_models').first.spatial.should be_false + @connection.indexes('geometry_models').first.columns.should == ['extra'] + @connection.remove_index('geometry_models', 'extra') end end end \ No newline at end of file