beh_spatial_adapter 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,395 @@
1
+ require 'spatial_adapter'
2
+ require 'active_record/connection_adapters/postgresql_adapter'
3
+
4
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
5
+ include SpatialAdapter
6
+
7
+ def postgis_version
8
+ begin
9
+ select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0]
10
+ rescue ActiveRecord::StatementInvalid
11
+ nil
12
+ end
13
+ end
14
+
15
+ def postgis_major_version
16
+ version = postgis_version
17
+ version ? version.scan(/^(\d)\.\d\.\d$/)[0][0].to_i : nil
18
+ end
19
+
20
+ def postgis_minor_version
21
+ version = postgis_version
22
+ version ? version.scan(/^\d\.(\d)\.\d$/)[0][0].to_i : nil
23
+ end
24
+
25
+ def spatial?
26
+ !postgis_version.nil?
27
+ end
28
+
29
+ def supports_geographic?
30
+ postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5)
31
+ end
32
+
33
+ def default_srid
34
+ 4326
35
+ end
36
+
37
+ alias :original_native_database_types :native_database_types
38
+ def native_database_types
39
+ original_native_database_types.merge!(geometry_data_types)
40
+ end
41
+
42
+ alias :original_quote :quote
43
+ #Redefines the quote method to add behaviour for when a Geometry is encountered
44
+ def quote(value, column = nil)
45
+ if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
46
+ "'#{value.as_hex_ewkb}'"
47
+ else
48
+ original_quote(value,column)
49
+ end
50
+ end
51
+
52
+ def columns(table_name, name = nil) #:nodoc:
53
+ raw_geom_infos = column_spatial_info(table_name)
54
+
55
+ column_definitions(table_name).collect do |name, type, default, notnull|
56
+ case type
57
+ when /geography/i
58
+ ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_from_geography(name, default, type, notnull == 'f')
59
+ when /geometry/i
60
+ raw_geom_info = raw_geom_infos[name]
61
+ if raw_geom_info.nil?
62
+ # This column isn't in the geometry_columns table, so we don't know anything else about it
63
+ ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_simplified(name, default, notnull == "f")
64
+ else
65
+ 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)
66
+ end
67
+ else
68
+ ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(name, default, type, notnull == "f")
69
+ end
70
+ end
71
+ end
72
+
73
+ def create_table(table_name, options = {})
74
+ # Using the subclassed table definition
75
+ table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self)
76
+ table_definition.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
77
+
78
+ yield table_definition if block_given?
79
+
80
+ if options[:force] && table_exists?(table_name)
81
+ drop_table(table_name, options)
82
+ end
83
+
84
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
85
+ create_sql << "#{quote_table_name(table_name)} ("
86
+ create_sql << table_definition.to_sql
87
+ create_sql << ") #{options[:options]}"
88
+
89
+ # This is the additional portion for PostGIS
90
+ unless table_definition.geom_columns.nil?
91
+ table_definition.geom_columns.each do |geom_column|
92
+ geom_column.table_name = table_name
93
+ create_sql << "; " + geom_column.to_sql
94
+ end
95
+ end
96
+
97
+ execute create_sql
98
+ end
99
+
100
+ alias :original_remove_column :remove_column
101
+ def remove_column(table_name, *column_names)
102
+ column_names = column_names.flatten.map {|c| c.to_sym}
103
+ columns(table_name).each do |col|
104
+ if column_names.include?(col.name.to_sym)
105
+ # Geometry columns have to be removed using DropGeometryColumn
106
+ if col.is_a?(SpatialColumn) && col.spatial? && !col.geographic?
107
+ execute "SELECT DropGeometryColumn('#{table_name}','#{col.name}')"
108
+ else
109
+ original_remove_column(table_name, col.name)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ alias :original_add_column :add_column
116
+ def add_column(table_name, column_name, type, options = {})
117
+ unless geometry_data_types[type].nil?
118
+ 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)
119
+ if geom_column.geographic
120
+ default = options[:default]
121
+ notnull = options[:null] == false
122
+
123
+ execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{geom_column.to_sql}")
124
+
125
+ change_column_default(table_name, column_name, default) if options_include_default?(options)
126
+ change_column_null(table_name, column_name, false, default) if notnull
127
+ else
128
+ geom_column.table_name = table_name
129
+ execute geom_column.to_sql
130
+ end
131
+ else
132
+ original_add_column(table_name, column_name, type, options)
133
+ end
134
+ end
135
+
136
+ # Adds an index to a column.
137
+ def add_index(table_name, column_name, options = {})
138
+ column_names = Array(column_name)
139
+ index_name = index_name(table_name, :column => column_names)
140
+
141
+ if Hash === options # legacy support, since this param was a string
142
+ index_type = options[:unique] ? "UNIQUE" : ""
143
+ index_name = options[:name] || index_name
144
+ index_method = options[:spatial] ? 'USING GIST' : ""
145
+ else
146
+ index_type = options
147
+ end
148
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
149
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_method} (#{quoted_column_names})"
150
+ end
151
+
152
+ # Returns the list of all indexes for a table.
153
+ #
154
+ # This is a full replacement for the ActiveRecord method and as a result
155
+ # has a higher probability of breaking in future releases.
156
+ def indexes(table_name, name = nil)
157
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
158
+
159
+ # Changed from upstread: link to pg_am to grab the index type (e.g. "gist")
160
+ result = query(<<-SQL, name)
161
+ SELECT distinct i.relname, d.indisunique, d.indkey, t.oid, am.amname
162
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am
163
+ WHERE i.relkind = 'i'
164
+ AND d.indexrelid = i.oid
165
+ AND d.indisprimary = 'f'
166
+ AND t.oid = d.indrelid
167
+ AND t.relname = '#{table_name}'
168
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
169
+ AND i.relam = am.oid
170
+ AND a.attrelid = t.oid
171
+ ORDER BY i.relname
172
+ SQL
173
+
174
+
175
+ indexes = []
176
+
177
+ indexes = result.map do |row|
178
+ index_name = row[0]
179
+ unique = row[1] == 't'
180
+ indkey = row[2].split(" ")
181
+ oid = row[3]
182
+ indtype = row[4]
183
+
184
+ # Changed from upstream: need to get the column types to test for spatial indexes
185
+ columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = [r[0], r[2]]; attlist}
186
+ SELECT a.attname, a.attnum, t.typname
187
+ FROM pg_attribute a, pg_type t
188
+ WHERE a.attrelid = #{oid}
189
+ AND a.attnum IN (#{indkey.join(",")})
190
+ AND a.atttypid = t.oid
191
+ SQL
192
+
193
+ # Only GiST indexes on spatial columns denote a spatial index
194
+ spatial = indtype == 'gist' && columns.size == 1 && (columns.values.first[1] == 'geometry' || columns.values.first[1] == 'geography')
195
+
196
+ column_names = indkey.map {|attnum| columns[attnum] ? columns[attnum][0] : nil }
197
+ ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, spatial)
198
+ end
199
+
200
+ indexes
201
+ end
202
+
203
+ def disable_referential_integrity(&block) #:nodoc:
204
+ if supports_disable_referential_integrity?() then
205
+ execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
206
+ end
207
+ yield
208
+ ensure
209
+ if supports_disable_referential_integrity?() then
210
+ execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
211
+ end
212
+ end
213
+
214
+ private
215
+
216
+ def tables_without_postgis
217
+ tables - %w{ geometry_columns spatial_ref_sys }
218
+ end
219
+
220
+ def column_spatial_info(table_name)
221
+ constr = query("SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'")
222
+
223
+ raw_geom_infos = {}
224
+ constr.each do |constr_def_a|
225
+ raw_geom_infos[constr_def_a[3]] ||= SpatialAdapter::RawGeomInfo.new
226
+ raw_geom_infos[constr_def_a[3]].type = constr_def_a[6]
227
+ raw_geom_infos[constr_def_a[3]].dimension = constr_def_a[4].to_i
228
+ raw_geom_infos[constr_def_a[3]].srid = constr_def_a[5].to_i
229
+
230
+ if raw_geom_infos[constr_def_a[3]].type[-1] == ?M
231
+ raw_geom_infos[constr_def_a[3]].with_m = true
232
+ raw_geom_infos[constr_def_a[3]].type.chop!
233
+ else
234
+ raw_geom_infos[constr_def_a[3]].with_m = false
235
+ end
236
+ end
237
+
238
+ raw_geom_infos.each_value do |raw_geom_info|
239
+ #check the presence of z and m
240
+ raw_geom_info.convert!
241
+ end
242
+
243
+ raw_geom_infos
244
+
245
+ end
246
+
247
+ end
248
+
249
+ module ActiveRecord
250
+ module ConnectionAdapters
251
+ class PostgreSQLTableDefinition < TableDefinition
252
+ attr_reader :geom_columns
253
+
254
+ def column(name, type, options = {})
255
+ unless (@base.geometry_data_types[type.to_sym].nil? or
256
+ (options[:create_using_addgeometrycolumn] == false))
257
+
258
+ column = self[name] || PostgreSQLColumnDefinition.new(@base, name, type)
259
+ column.null = options[:null]
260
+ srid = options[:srid] || -1
261
+ srid = @base.default_srid if srid == :default_srid
262
+ column.srid = srid
263
+ column.with_z = options[:with_z] || false
264
+ column.with_m = options[:with_m] || false
265
+ column.geographic = options[:geographic] || false
266
+
267
+ if column.geographic
268
+ @columns << column unless @columns.include? column
269
+ else
270
+ # Hold this column for later
271
+ @geom_columns ||= []
272
+ @geom_columns << column
273
+ end
274
+ self
275
+ else
276
+ super(name, type, options)
277
+ end
278
+ end
279
+ end
280
+
281
+ class PostgreSQLColumnDefinition < ColumnDefinition
282
+ attr_accessor :table_name
283
+ attr_accessor :srid, :with_z, :with_m, :geographic
284
+ attr_reader :spatial
285
+
286
+ def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil, null=nil, srid=-1, with_z=false, with_m=false, geographic=false)
287
+ super(base, name, type, limit, default, null)
288
+ @table_name = nil
289
+ @spatial = true
290
+ @srid = srid
291
+ @with_z = with_z
292
+ @with_m = with_m
293
+ @geographic = geographic
294
+ end
295
+
296
+ def sql_type
297
+ if geographic
298
+ type_sql = base.geometry_data_types[type.to_sym][:name]
299
+ type_sql += "Z" if with_z
300
+ type_sql += "M" if with_m
301
+ # SRID is not yet supported (defaults to 4326)
302
+ #type_sql += ", #{srid}" if (srid && srid != -1)
303
+ type_sql = "geography(#{type_sql})"
304
+ type_sql
305
+ else
306
+ super
307
+ end
308
+ end
309
+
310
+ def to_sql
311
+ if spatial && !geographic
312
+ type_sql = base.geometry_data_types[type.to_sym][:name]
313
+ type_sql += "M" if with_m and !with_z
314
+ if with_m and with_z
315
+ dimension = 4
316
+ elsif with_m or with_z
317
+ dimension = 3
318
+ else
319
+ dimension = 2
320
+ end
321
+
322
+ column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})"
323
+ column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false
324
+ column_sql
325
+ else
326
+ super
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
332
+
333
+ module ActiveRecord
334
+ module ConnectionAdapters
335
+ class SpatialPostgreSQLColumn < PostgreSQLColumn
336
+ include SpatialAdapter::SpatialColumn
337
+
338
+ def initialize(name, default, sql_type = nil, null = true, srid=-1, with_z=false, with_m=false, geographic = false)
339
+ super(name, default, sql_type, null, srid, with_z, with_m)
340
+ @geographic = geographic
341
+ end
342
+
343
+ def geographic?
344
+ @geographic
345
+ end
346
+
347
+ #Transforms a string to a geometry. PostGIS returns a HewEWKB string.
348
+ def self.string_to_geometry(string)
349
+ return string unless string.is_a?(String)
350
+ GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil
351
+ end
352
+
353
+ def self.create_simplified(name, default, null = true)
354
+ new(name, default, "geometry", null)
355
+ end
356
+
357
+ def self.create_from_geography(name, default, sql_type, null = true)
358
+ params = extract_geography_params(sql_type)
359
+ new(name, default, sql_type, null, params[:srid], params[:with_z], params[:with_m], true)
360
+ end
361
+
362
+ private
363
+
364
+ # Add detection of PostGIS-specific geography columns
365
+ def geometry_simplified_type(sql_type)
366
+ case sql_type
367
+ when /geography\(point/i then :point
368
+ when /geography\(linestring/i then :line_string
369
+ when /geography\(polygon/i then :polygon
370
+ when /geography\(multipoint/i then :multi_point
371
+ when /geography\(multilinestring/i then :multi_line_string
372
+ when /geography\(multipolygon/i then :multi_polygon
373
+ when /geography\(geometrycollection/i then :geometry_collection
374
+ when /geography/i then :geometry
375
+ else
376
+ super
377
+ end
378
+ end
379
+
380
+ def self.extract_geography_params(sql_type)
381
+ params = {
382
+ :srid => 0,
383
+ :with_z => false,
384
+ :with_m => false
385
+ }
386
+ if sql_type =~ /geography(?:\((?:\w+?)(Z)?(M)?(?:,(\d+))?\))?/i
387
+ params[:with_z] = $1 == 'Z'
388
+ params[:with_m] = $2 == 'M'
389
+ params[:srid] = $3.to_i
390
+ end
391
+ params
392
+ end
393
+ end
394
+ end
395
+ end
@@ -0,0 +1,13 @@
1
+ module SpatialAdapter
2
+ class Railtie < Rails::Railtie
3
+ initializer "spatial_adapter.load_current_database_adapter" do
4
+ adapter = ActiveRecord::Base.configurations[Rails.env]['adapter']
5
+ begin
6
+ require "spatial_adapter/#{adapter}"
7
+ rescue LoadError => e
8
+ raise SpatialAdapter::NotCompatibleError.new("spatial_adapter does not currently support the #{adapter} database.")
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,16 @@
1
+ # Rails initialization (for Rails 2.x)
2
+ #
3
+ # This will load the adapter for the currently used database configuration, if
4
+ # it exists.
5
+
6
+ begin
7
+ adapter = ActiveRecord::Base.configurations[RAILS_ENV]['adapter']
8
+ require "spatial_adapter/#{adapter}"
9
+ # Also load the adapter for the test environment. In 2.2, at least, when running db:test:prepare, the first
10
+ # connection instantiated is the RAILS_ENV one, not 'test'.
11
+ test_adapter = ActiveRecord::Base.configurations['test']['adapter']
12
+ require "spatial_adapter/#{test_adapter}"
13
+ rescue LoadError => e
14
+ puts "Caught #{e} #{e.message} #{e.backtrace.join("\n")}"
15
+ raise SpatialAdapter::NotCompatibleError.new("spatial_adapter does not currently support the #{adapter} database.")
16
+ end
@@ -0,0 +1,16 @@
1
+ = Running Tests
2
+
3
+ You will need to set up empty databases for each adapter you want to test.
4
+
5
+ == PostgreSQL
6
+
7
+ Create an empty database "spatial_adapter" and ensure that the PostGIS extensions are loaded.
8
+
9
+ run "rake spec:postgresql" to run the specs
10
+
11
+ == MySQL
12
+
13
+ Create an empty database "spatial_adapter" - the spatial extensions are already available.
14
+
15
+ run "rake spec:mysql" to run the specs
16
+
@@ -0,0 +1,70 @@
1
+ mysql_connection
2
+
3
+ ActiveRecord::Schema.define() do
4
+ execute "drop table if exists point_models"
5
+ execute "create table point_models
6
+ (
7
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
8
+ extra varchar(255),
9
+ more_extra varchar(255),
10
+ geom point not null
11
+ ) ENGINE=MyISAM"
12
+ execute "create spatial index index_point_models_on_geom on point_models (geom)"
13
+ execute "create index index_point_models_on_extra on point_models (extra, more_extra)"
14
+
15
+ execute "drop table if exists line_string_models"
16
+ execute "create table line_string_models
17
+ (
18
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
19
+ extra varchar(255),
20
+ geom linestring
21
+ ) ENGINE=MyISAM"
22
+
23
+ execute "drop table if exists polygon_models"
24
+ execute "create table polygon_models
25
+ (
26
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
27
+ extra varchar(255),
28
+ geom polygon
29
+ ) ENGINE=MyISAM"
30
+
31
+ execute "drop table if exists multi_point_models"
32
+ execute "create table multi_point_models
33
+ (
34
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
35
+ extra varchar(255),
36
+ geom multipoint
37
+ ) ENGINE=MyISAM"
38
+
39
+ execute "drop table if exists multi_line_string_models"
40
+ execute "create table multi_line_string_models
41
+ (
42
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
43
+ extra varchar(255),
44
+ geom multilinestring
45
+ ) ENGINE=MyISAM"
46
+
47
+ execute "drop table if exists multi_polygon_models"
48
+ execute "create table multi_polygon_models
49
+ (
50
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
51
+ extra varchar(255),
52
+ geom multipolygon
53
+ ) ENGINE=MyISAM"
54
+
55
+ execute "drop table if exists geometry_collection_models"
56
+ execute "create table geometry_collection_models
57
+ (
58
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
59
+ extra varchar(255),
60
+ geom geometrycollection
61
+ ) ENGINE=MyISAM"
62
+
63
+ execute "drop table if exists geometry_models"
64
+ execute "create table geometry_models
65
+ (
66
+ id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
67
+ extra varchar(255),
68
+ geom geometry
69
+ ) ENGINE=MyISAM"
70
+ end