beh_spatial_adapter 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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