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,70 @@
1
+ module SpatialAdapter
2
+ module SpatialColumn
3
+ attr_reader :geometry_type, :srid, :with_z, :with_m
4
+
5
+ def initialize(name, default, sql_type = nil, null = true, srid=-1, with_z=false, with_m=false)
6
+ super(name, default, sql_type, null)
7
+ @geometry_type = geometry_simplified_type(@sql_type)
8
+ @srid = srid
9
+ @with_z = with_z
10
+ @with_m = with_m
11
+ end
12
+
13
+ def spatial?
14
+ !@geometry_type.nil?
15
+ end
16
+
17
+ def geographic?
18
+ false
19
+ end
20
+
21
+ # Redefines type_cast to add support for geometries
22
+ # alias_method :type_cast_without_spatial, :type_cast
23
+ def type_cast(value)
24
+ return nil if value.nil?
25
+ spatial? ? self.class.string_to_geometry(value) : super
26
+ end
27
+
28
+ #Redefines type_cast_code to add support for geometries.
29
+ #
30
+ #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.
31
+ # alias_method :type_cast_code_without_spatial, :type_cast_code
32
+ def type_cast_code(var_name)
33
+ spatial? ? "#{self.class.name}.string_to_geometry(#{var_name})" : super
34
+ end
35
+
36
+
37
+ #Redefines klass to add support for geometries
38
+ # alias_method :klass_without_spatial, :klass
39
+ def klass
40
+ spatial? ? GeoRuby::SimpleFeatures::Geometry : super
41
+ end
42
+
43
+ private
44
+
45
+ # Maps additional data types to base Rails/Arel types
46
+ #
47
+ # For Rails 3, only the types defined by Arel can be used. We'll
48
+ # use :string since the database returns the columns as hex strings.
49
+ def simplified_type(field_type)
50
+ case field_type
51
+ when /geography|geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i then :string
52
+ else super
53
+ end
54
+ end
55
+
56
+ # less simlpified geometric type to be use in migrations
57
+ def geometry_simplified_type(sql_type)
58
+ case sql_type
59
+ when /^point$/i then :point
60
+ when /^linestring$/i then :line_string
61
+ when /^polygon$/i then :polygon
62
+ when /^geometry$/i then :geometry
63
+ when /multipoint/i then :multi_point
64
+ when /multilinestring/i then :multi_line_string
65
+ when /multipolygon/i then :multi_polygon
66
+ when /geometrycollection/i then :geometry_collection
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,14 @@
1
+ include SpatialAdapter
2
+
3
+ ActiveRecord::ConnectionAdapters::TableDefinition.class_eval do
4
+ SpatialAdapter.geometry_data_types.keys.each do |column_type|
5
+ class_eval <<-EOV
6
+ def #{column_type}(*args)
7
+ options = args.extract_options!
8
+ column_names = args
9
+
10
+ column_names.each { |name| column(name, '#{column_type}', options) }
11
+ end
12
+ EOV
13
+ end
14
+ end
@@ -0,0 +1,102 @@
1
+ require 'spatial_adapter'
2
+ require 'active_record/connection_adapters/mysql_adapter'
3
+
4
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
5
+ include SpatialAdapter
6
+
7
+ def supports_geographic?
8
+ false
9
+ end
10
+
11
+ def default_srid
12
+ 4326
13
+ end
14
+
15
+ alias :original_native_database_types :native_database_types
16
+ def native_database_types
17
+ original_native_database_types.merge!(geometry_data_types)
18
+ end
19
+
20
+ alias :original_quote :quote
21
+ #Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
22
+ def quote(value, column = nil)
23
+ if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
24
+ "GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
25
+ else
26
+ original_quote(value,column)
27
+ end
28
+ end
29
+
30
+ #Redefinition of columns to add the information that a column is geometric
31
+ def columns(table_name, name = nil)#:nodoc:
32
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
33
+ columns = []
34
+ result = execute(sql, name)
35
+ result.each do |field|
36
+ klass = field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i ? ActiveRecord::ConnectionAdapters::SpatialMysqlColumn : ActiveRecord::ConnectionAdapters::MysqlColumn
37
+ columns << klass.new(field[0], field[4], field[1], field[2] == "YES")
38
+ end
39
+ result.free
40
+ columns
41
+ end
42
+
43
+
44
+ #operations relative to migrations
45
+
46
+ #Redefines add_index to support the case where the index is spatial
47
+ #If the :spatial key in the options table is true, then the sql string for a spatial index is created
48
+ def add_index(table_name,column_name,options = {})
49
+ index_name = options[:name] || index_name(table_name,:column => Array(column_name))
50
+
51
+ if options[:spatial]
52
+ execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ #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)
59
+ def indexes(table_name, name = nil)#:nodoc:
60
+ indexes = []
61
+ current_index = nil
62
+ execute("SHOW KEYS FROM #{table_name}", name).each do |row|
63
+ if current_index != row[2]
64
+ next if row[2] == "PRIMARY" # skip the primary key
65
+ current_index = row[2]
66
+ indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL")
67
+ end
68
+ indexes.last.columns << row[4]
69
+ end
70
+ indexes
71
+ end
72
+
73
+ #Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
74
+ def options_for(table)
75
+ result = execute("show table status like '#{table}'")
76
+ engine = result.fetch_row[1]
77
+ if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
78
+ "ENGINE=#{engine}"
79
+ else
80
+ nil
81
+ end
82
+ end
83
+ end
84
+
85
+
86
+ module ActiveRecord
87
+ module ConnectionAdapters
88
+ class SpatialMysqlColumn < MysqlColumn
89
+ include SpatialAdapter::SpatialColumn
90
+
91
+ #MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
92
+ def self.string_to_geometry(string)
93
+ return string unless string.is_a?(String)
94
+ begin
95
+ GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
96
+ rescue Exception => exception
97
+ nil
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,651 @@
1
+ require 'spatial_adapter'
2
+ require 'active_record/connection_adapters/oracle_enhanced_adapter'
3
+
4
+ include GeoRuby::SimpleFeatures
5
+ include SpatialAdapter
6
+
7
+
8
+ module ActiveRecord
9
+ module ConnectionAdapters
10
+ class OracleEnhancedAdapter
11
+
12
+ def spatial?
13
+ rval = select_value("select object_id from all_objects where owner = 'MDSYS' and object_name = 'SDO_GEOMETRY'").to_i
14
+ rval > 0 ? true : false
15
+ end
16
+
17
+ def columns_without_cache(table_name, name = nil) #:nodoc:
18
+ table_name = table_name.to_s
19
+ # get ignored_columns by original table name
20
+ ignored_columns = ignored_table_columns(table_name)
21
+
22
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
23
+
24
+ @@do_not_prefetch_primary_key[table_name] =
25
+ !has_primary_key?(table_name, owner, desc_table_name, db_link) ||
26
+ has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
27
+
28
+ table_cols = <<-SQL
29
+ select column_name as name, data_type as sql_type, data_default, nullable,
30
+ decode(data_type, 'NUMBER', data_precision,
31
+ 'FLOAT', data_precision,
32
+ 'VARCHAR2', decode(char_used, 'C', char_length, data_length),
33
+ 'CHAR', decode(char_used, 'C', char_length, data_length),
34
+ null) as limit,
35
+ decode(data_type, 'NUMBER', data_scale, null) as scale
36
+ from all_tab_columns#{db_link}
37
+ where owner = '#{owner}'
38
+ and table_name = '#{desc_table_name}'
39
+ order by column_id
40
+ SQL
41
+
42
+ raw_geom_infos = column_spatial_info(desc_table_name)
43
+
44
+ # added deletion of ignored columns
45
+ select_all(table_cols, name).delete_if do |row|
46
+ ignored_columns && ignored_columns.include?(row['name'].downcase)
47
+ end.map do |row|
48
+ limit, scale = row['limit'], row['scale']
49
+ if limit || scale
50
+ row['sql_type'] += "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
51
+ end
52
+
53
+ # clean up odd default spacing from Oracle
54
+ if row['data_default']
55
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
56
+
57
+ # If a default contains a newline these cleanup regexes need to
58
+ # match newlines.
59
+ row['data_default'].sub!(/^'(.*)'$/m, '\1')
60
+ row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
61
+ end
62
+
63
+ if row['sql_type'] =~ /sdo_geometry/i
64
+ raw_geom_info = raw_geom_infos[oracle_downcase(row['name'])]
65
+ if(raw_geom_info.nil?)
66
+ puts "Couldn't find geom info for #{row['name']} in #{raw_geom_infos.inspect}"
67
+ end
68
+ ActiveRecord::ConnectionAdapters::SpatialOracleColumn.new(
69
+ oracle_downcase(row['name']),
70
+ row['data_default'],
71
+ raw_geom_info.type,
72
+ row['nullable'] == 'Y',
73
+ raw_geom_info.srid,
74
+ raw_geom_info.with_z,
75
+ raw_geom_info.with_m)
76
+ else
77
+ OracleEnhancedColumn.new(oracle_downcase(row['name']),
78
+ row['data_default'],
79
+ row['sql_type'],
80
+ row['nullable'] == 'Y',
81
+ # pass table name for table specific column definitions
82
+ table_name,
83
+ # pass column type if specified in class definition
84
+ get_type_for_column(table_name, oracle_downcase(row['name'])))
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ ActiveRecord::ConnectionAdapters::OracleEnhancedIndexDefinition.class_eval do
93
+ def spatial
94
+ type == "MDSYS.SPATIAL_INDEX"
95
+ end
96
+ end
97
+
98
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
99
+
100
+ include SpatialAdapter
101
+
102
+ SPATIAL_TOLERANCE = 0.5
103
+
104
+ def supports_geographic?
105
+ false
106
+ end
107
+
108
+ def default_srid
109
+ 8307
110
+ end
111
+
112
+ alias :original_native_database_types :native_database_types
113
+ def native_database_types
114
+ types = original_native_database_types.merge!(geometry_data_types)
115
+
116
+ # Change mapping of :float from NUMBER to FLOAT, as oracle
117
+ # recognizes the ansi keyword and it helps keep the schema
118
+ # stable, even though it ends up mapping to the same underlying
119
+ # native type.
120
+ types[:float] = {:name => "FLOAT", :limit => 126 }
121
+
122
+ types
123
+ end
124
+
125
+ #Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
126
+ def quote(value, column = nil)
127
+ if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
128
+ quote_generic_geom( value )
129
+ elsif value && column && [:text, :binary].include?(column.type)
130
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
131
+ else
132
+ super
133
+ end
134
+ end
135
+
136
+ def quote_point_geom( value )
137
+ if !value.with_z && !value.with_m
138
+ "SDO_GEOMETRY( 2001, #{value.srid}, MDSYS.SDO_POINT_TYPE( #{value.x}, #{value.y}, NULL ), NULL, NULL )"
139
+ elsif value.with_z && value.with_m
140
+ raise ArgumentError, "4d points not currently supported in the oracle_enhanced spatial adapter."
141
+ #"SDO_GEOMETRY( 2001, #{value.srid}, MDSYS.SDO_POINT_TYPE( #{value.x}, #{value.y}, #{value.z}, #{value.m} ), NULL, NULL )"
142
+ elsif value.with_z
143
+ "SDO_GEOMETRY( 3001, #{value.srid}, MDSYS.SDO_POINT_TYPE( #{value.x}, #{value.y}, #{value.z} ), NULL, NULL )"
144
+ elsif value.with_m
145
+ raise ArgumentError, "3d points (with M coord) not currently supported in the oracle_enhanced spatial adapter."
146
+ #"SDO_GEOMETRY( 2001, #{value.srid}, MDSYS.SDO_POINT_TYPE( #{value.x}, #{value.y}, #{value.m} ), NULL, NULL )"
147
+ end
148
+ end
149
+
150
+ # This technique only supports 2d geometries
151
+ def quote_generic_geom( value )
152
+ if value.kind_of?( GeoRuby::SimpleFeatures::Point )
153
+ # Small optimization for the most commonly used type: points. Oracle's WKT parsing seems slow
154
+ quote_point_geom( value )
155
+ else
156
+ "SDO_GEOMETRY( '#{value.as_wkt}', #{value.srid} )"
157
+ end
158
+ end
159
+
160
+ def create_table(name, options = {})
161
+ table_definition = ActiveRecord::ConnectionAdapters::OracleTableDefinition.new(self)
162
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
163
+
164
+ yield table_definition if block_given?
165
+
166
+ # table_exists? is slow
167
+ #if options[:force] && table_exists?(name)
168
+ if options[:force]
169
+ drop_table(name) rescue nil
170
+ end
171
+
172
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
173
+ create_sql << "#{name} ("
174
+ create_sql << table_definition.to_sql
175
+ create_sql << ") #{options[:options]}"
176
+ execute create_sql
177
+ execute "CREATE SEQUENCE #{name}_seq START WITH 10000" unless options[:id] == false
178
+
179
+ #added to create the geometric columns identified during the table definition
180
+ unless table_definition.geom_columns.nil?
181
+ table_definition.geom_columns.each do |geom_column|
182
+ geom_column.sql_create_statements(name).each do |stmt|
183
+ execute stmt
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ # We override this for three reasons:
190
+ # 1) Drop spatial indexes before dropping the tables, as "drop table cascade constraints"
191
+ # seems to leave metadata around in SDO_INDEX_METADATA_TABLE
192
+ # 2) Avoid dropping sequences that end in '$', as those are for the spatial indexes (which get dropped above)
193
+ # 3) Need to clear out USER_SDO_GEOM_METADATA after everything else is done.
194
+ def structure_drop
195
+ s = []
196
+ select_values("select sdo_index_name from user_sdo_index_metadata").uniq.each do |idx|
197
+ s << "DROP INDEX \"#{idx}\""
198
+ end
199
+
200
+ select_values("select sequence_name from user_sequences order by 1").each do |seq|
201
+ s << "DROP SEQUENCE \"#{seq}\"" unless seq[-1,1] == '$'
202
+ end
203
+ select_values("select table_name from all_tables t
204
+ where owner = sys_context('userenv','session_user') and secondary='N'
205
+ and not exists (select mv.mview_name from all_mviews mv where mv.owner = t.owner and mv.mview_name = t.table_name)
206
+ and not exists (select mvl.log_table from all_mview_logs mvl where mvl.log_owner = t.owner and mvl.log_table = t.table_name)
207
+ order by 1").each do |table|
208
+ s << "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS"
209
+ end
210
+ s << "DELETE FROM USER_SDO_GEOM_METADATA"
211
+ join_with_statement_token(s)
212
+ end
213
+
214
+ alias :original_remove_column :remove_column
215
+ def remove_column(table_name,column_name)
216
+ columns(table_name).each do |col|
217
+ if col.name.to_s == column_name.to_s
218
+ #check if the column is geometric
219
+ if col.is_a?(SpatialColumn) && col.spatial?
220
+ execute "DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = '#{table_name.upcase}' AND COLUMN_NAME = '#{column_name.to_s.upcase}'"
221
+ execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
222
+ else
223
+ original_remove_column(table_name,column_name)
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ alias :original_add_column :add_column
230
+ def add_column(table_name, column_name, type, options = {})
231
+ unless geometry_data_types[type].nil?
232
+ geom_column = ActiveRecord::ConnectionAdapters::OracleColumnDefinition.new(self,column_name, type, nil,nil,options[:null],options[:srid] || -1 , options[:with_z] || false , options[:with_m] || false)
233
+ geom_column.sql_create_statements(table_name).each do |stmt|
234
+ execute stmt
235
+ end
236
+ else
237
+ original_add_column(table_name,column_name,type,options)
238
+ end
239
+ end
240
+
241
+ #Adds a spatial index to a column. Its name will be index_<table_name>_on_<column_name> unless the key :name is present in the options hash, in which case its value is taken as the name of the index.
242
+ def add_index(table_name,column_name,options = {})
243
+ # invalidate index cache
244
+ self.all_schema_indexes = nil
245
+ index_name = options[:name] || "index_#{table_name}_on_#{column_name}"
246
+ if options[:spatial]
247
+ execute "CREATE INDEX #{index_name} ON #{table_name} (#{column_name}) INDEXTYPE IS mdsys.spatial_index"
248
+ else
249
+ index_type = options[:unique] ? "UNIQUE" : ""
250
+ #all together
251
+ execute "CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
252
+ end
253
+ end
254
+
255
+ private
256
+
257
+ def column_spatial_info(table_name)
258
+ rows = select_all <<-end_sql
259
+ SELECT column_name, diminfo, srid
260
+ FROM user_sdo_geom_metadata
261
+ WHERE table_name = '#{table_name}'
262
+ end_sql
263
+
264
+ raw_geom_infos = {}
265
+ rows.each do |row|
266
+ column_name = oracle_downcase(row['column_name'])
267
+ dims = row['diminfo'].to_ary
268
+ raw_geom_info = raw_geom_infos[column_name] || ActiveRecord::ConnectionAdapters::RawGeomInfo.new
269
+ raw_geom_info.type = "geometry"
270
+ raw_geom_info.with_m = dims.any? {|d| d.sdo_dimname == 'M'}
271
+ raw_geom_info.with_z = dims.any? {|d| d.sdo_dimname == 'Z'}
272
+ raw_geom_info.srid = row['srid'].to_i
273
+ #raw_geom_info.diminfo = row['diminfo']
274
+ raw_geom_infos[column_name] = raw_geom_info
275
+ end #constr.each
276
+
277
+ raw_geom_infos
278
+ end
279
+
280
+ end
281
+
282
+ module ActiveRecord
283
+ module ConnectionAdapters
284
+ class RawGeomInfo < Struct.new(:type,:srid,:dimension,:with_z,:with_m) #:nodoc:
285
+ end
286
+ end
287
+ end
288
+
289
+
290
+ module ActiveRecord
291
+ module ConnectionAdapters
292
+ class OracleTableDefinition < TableDefinition
293
+ attr_reader :geom_columns
294
+
295
+ def column(name, type, options = {})
296
+ unless @base.geometry_data_types[type.to_sym].nil?
297
+ geom_column = OracleColumnDefinition.new(@base, name, type)
298
+ geom_column.null = options[:null]
299
+ srid = options[:srid] || -1
300
+ srid = @base.default_srid if srid == :default_srid
301
+ geom_column.srid = srid
302
+ geom_column.with_z = options[:with_z] || false
303
+ geom_column.with_m = options[:with_m] || false
304
+
305
+ @geom_columns = [] if @geom_columns.nil?
306
+ @geom_columns << geom_column
307
+ else
308
+ super(name,type,options)
309
+ end
310
+ end
311
+
312
+ SpatialAdapter.geometry_data_types.keys.each do |column_type|
313
+ class_eval <<-EOV
314
+ def #{column_type}(*args)
315
+ options = args.extract_options!
316
+ column_names = args
317
+
318
+ column_names.each { |name| column(name, '#{column_type}', options) }
319
+ end
320
+ EOV
321
+ end
322
+ end
323
+
324
+ class OracleColumnDefinition < ColumnDefinition
325
+ attr_accessor :srid, :with_z,:with_m
326
+ attr_reader :spatial
327
+
328
+ def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil,null=nil,srid=-1,with_z=false,with_m=false)
329
+ super(base, name, type, limit, default,null)
330
+ @spatial=true
331
+ @srid=srid
332
+ @with_z=with_z
333
+ @with_m=with_m
334
+ end
335
+
336
+ def sql_create_statements(table_name)
337
+ type_sql = type_to_sql(type)
338
+
339
+ #column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})"
340
+ column_sql = "ALTER TABLE #{table_name} ADD (#{name} #{type_sql}"
341
+ column_sql += " NOT NULL" if null == false
342
+ column_sql += ")"
343
+ stmts = [column_sql]
344
+ if srid == 8307 # There are others we should probably support, but this is common
345
+ dim_elems = ["MDSYS.SDO_DIM_ELEMENT('X', -180.0, 180.0, 0.005)",
346
+ "MDSYS.SDO_DIM_ELEMENT('Y', -90.0, 90.0, 0.005)"]
347
+ else
348
+ dim_elems = ["MDSYS.SDO_DIM_ELEMENT('X', -1000, 1000, 0.005)",
349
+ "MDSYS.SDO_DIM_ELEMENT('Y', -1000, 1000, 0.005)"]
350
+ end
351
+ if @with_z
352
+ dim_elems << "MDSYS.SDO_DIM_ELEMENT('Z', -1000, 1000, 0.005)"
353
+ end
354
+ if @with_m
355
+ dim_elems << "MDSYS.SDO_DIM_ELEMENT('M', -1000, 1000, 0.005)"
356
+ end
357
+
358
+ stmts << "DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = '#{table_name.to_s.upcase}' AND COLUMN_NAME = '#{name.to_s.upcase}'"
359
+ stmts << "INSERT INTO USER_SDO_GEOM_METADATA (TABLE_NAME, COLUMN_NAME, DIMINFO, SRID) VALUES (" +
360
+ "'#{table_name}', '#{name}', MDSYS.SDO_DIM_ARRAY(#{dim_elems.join(',')}),#{srid})"
361
+ stmts
362
+ end
363
+
364
+ def to_sql(table_name)
365
+ if @spatial
366
+ raise "Got here!"
367
+ else
368
+ super
369
+ end
370
+ end
371
+
372
+
373
+ private
374
+ def type_to_sql(name, limit=nil)
375
+ case name.to_s
376
+ when /geometry|point|line_string|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i
377
+ "MDSYS.SDO_GEOMETRY"
378
+ else base.type_to_sql(name, limit) rescue name
379
+ end
380
+ end
381
+
382
+ end
383
+
384
+ end
385
+ end
386
+
387
+ #Would prefer creation of a OracleColumn type instead but I would need to reimplement methods where Column objects are instantiated so I leave it like this
388
+ module ActiveRecord
389
+ module ConnectionAdapters
390
+ class SpatialOracleColumn < Column
391
+
392
+ include SpatialColumn
393
+
394
+ # With the ruby-oci8 adapter, we get objects back from spatial columns
395
+ # so this method name is a bit of a misnomer. TODO: change this method name to 'to_geometry'
396
+ def self.string_to_geometry(obj)
397
+ return obj unless obj && obj.class.to_s == "OCI8::Object::Mdsys::SdoGeometry"
398
+ raise "Bad #{obj.class} object: #{obj.inspect}" if obj.sdo_gtype.nil?
399
+ ndim = obj.sdo_gtype.to_int/1000
400
+ gtype = obj.sdo_gtype.to_int%1000
401
+ eleminfo = obj.sdo_elem_info.instance_variable_get('@attributes')
402
+ ordinates = obj.sdo_ordinates.instance_variable_get('@attributes')
403
+ case gtype
404
+ when 1
405
+ geom = point_from_sdo_geometry(eleminfo, ordinates, ndim, obj.sdo_point)
406
+ when 2
407
+ geom = linestrings_from_sdo_geometry(eleminfo, ordinates, ndim)[0]
408
+ when 3
409
+ geom = polygons_from_sdo_geometry(eleminfo, ordinates, ndim)[0]
410
+ when 4
411
+ geom = geomcollection_from_sdo_geometry(eleminfo, ordinates, ndim)
412
+ when 5
413
+ geom = MultiPoint.from_coordinates(group_ordinates(ordinates, ndim))
414
+ when 6
415
+ linestrings = linestrings_from_sdo_geometry(eleminfo, ordinates, ndim)
416
+ geom = MultiLineString.from_line_strings(linestrings)
417
+ when 7
418
+ polygons = polygons_from_sdo_geometry(eleminfo, ordinates, ndim)
419
+ geom = MultiPolygon.from_polygons(polygons)
420
+ else
421
+ raise "Unhandled geometry type #{obj.sdo_gtype.to_int}"
422
+ end
423
+ geom.srid = obj.sdo_srid.to_i
424
+ geom
425
+ end
426
+
427
+ def self.group_ordinates(ordinates, num_dims)
428
+ coords = []
429
+ ordinates.each_slice(num_dims) do |coord|
430
+ x,y = coord
431
+ coords << [x.to_f, y.to_f]
432
+ end
433
+ coords
434
+ end
435
+
436
+ def self.point_from_sdo_point(sdo_point, ndim)
437
+ if ndim == 2
438
+ Point.from_x_y(sdo_point.x.to_f, sdo_point.y.to_f)
439
+ elsif ndim == 3
440
+ Point.from_x_y_z(sdo_point.x.to_f, sdo_point.y.to_f, sdo_point.z.to_f)
441
+ else
442
+ raise "Not supporting #{ndim} dimensional points"
443
+ end
444
+ end
445
+
446
+ def self.point_from_sdo_geometry(elem_info, ordinates, ndim, sdo_point)
447
+ if sdo_point
448
+ point_from_sdo_point(sdo_point, ndim)
449
+ else
450
+ coords = ordinates.slice(0,ndim)
451
+ if ndim == 2
452
+ Point.from_x_y(ordinates[0].to_f, ordinates[1].to_f)
453
+ elsif ndim == 3
454
+ Point.from_x_y_z(ordinates[0].to_f, ordinates[1].to_f, ordinates[2].to_f)
455
+ end
456
+ end
457
+ end
458
+
459
+ def self.linestrings_from_sdo_geometry(elem_info, ordinates, ndim)
460
+ num_ls = elem_info.size / 3
461
+ linestrings = []
462
+ 0.upto(num_ls-1) do |ring_idx|
463
+ (start_ord, ring_type, gtype, end_ord) = elem_info.slice(ring_idx*3,4)
464
+ end_ord ||= ordinates.size + 1
465
+ num_ords = end_ord - start_ord
466
+ linestrings << LineString.from_coordinates(group_ordinates(ordinates.slice(start_ord-1, num_ords), ndim))
467
+ end
468
+ linestrings
469
+ end
470
+
471
+ def self.polygons_from_sdo_geometry(elem_info, ordinates, ndim)
472
+ num_rings = elem_info.size / 3
473
+ polygons = []
474
+ rings = []
475
+ cur_polygon = nil
476
+ 0.upto(num_rings-1) do |ring_idx|
477
+ (start_ord, etype, interpretation, end_ord) = elem_info.slice(ring_idx*3,4)
478
+ end_ord ||= ordinates.size + 1
479
+ num_ords = end_ord - start_ord
480
+ if etype == 1003 && rings.size > 0 # exterior ring
481
+ polygons << Polygon.from_linear_rings(rings)
482
+ rings = []
483
+ end
484
+ rings << LinearRing.from_coordinates(group_ordinates(ordinates.slice(start_ord-1, num_ords), ndim))
485
+ end
486
+ if rings.size > 0 # exterior ring
487
+ polygons << Polygon.from_linear_rings(rings)
488
+ rings = []
489
+ end
490
+ polygons
491
+ end
492
+
493
+ def self.geomcollection_from_sdo_geometry(elem_info, ordinates, ndim)
494
+ num_elems = elem_info.size / 3
495
+ geometries = []
496
+ 0.upto(num_elems-1) do |idx|
497
+ (start_ord, etype, interpretation, end_ord) = elem_info.slice(idx*3,4)
498
+ end_ord ||= ordinates.size + 1
499
+ num_ords = end_ord - start_ord
500
+ geom = nil
501
+ case etype
502
+ when 1
503
+ if ndim == 2
504
+ geom = Point.from_x_y(ordinates[start_ord-1].to_f, ordinates[start_ord].to_f)
505
+ elsif ndim == 3
506
+ geom = Point.from_x_y_z(ordinates[start_ord-1].to_f, ordinates[start_ord].to_f, ordinates[start_ord+1].to_f)
507
+ else
508
+ raise "Not supporting #{ndim} dimensional points"
509
+ end
510
+ when 2
511
+ geom = LineString.from_coordinates(group_ordinates(ordinates.slice(start_ord-1, num_ords), ndim))
512
+ when 1003
513
+ raise "Unsupported interpretation #{interpretation} for etype 1003" if interpretation != 1
514
+ ring = LinearRing.from_coordinates(group_ordinates(ordinates.slice(start_ord-1, num_ords), ndim))
515
+ geom = Polygon.from_linear_rings([ring])
516
+ when 2003
517
+ raise "Unsupported interpretation #{interpretation} for etype 2003" if interpretation != 1
518
+ ring = LinearRing.from_coordinates(group_ordinates(ordinates.slice(start_ord-1, num_ords), ndim))
519
+ last_geom = geometries.pop
520
+ geom = Polygon.from_linear_rings(last_geom.rings + [ring])
521
+ else
522
+ raise "Unsupported type in collection: etype = #{etype}, interpretation = #{interpretation}"
523
+ end
524
+ geometries << geom if geom
525
+ end
526
+ GeometryCollection.from_geometries(geometries)
527
+ end
528
+ end
529
+ end
530
+ end
531
+
532
+ ActiveRecord::SchemaDumper.class_eval do
533
+ def oracle_enhanced_table(table, stream)
534
+ columns = @connection.columns(table)
535
+ begin
536
+ tbl = StringIO.new
537
+
538
+ # first dump primary key column
539
+ if @connection.respond_to?(:pk_and_sequence_for)
540
+ pk, pk_seq = @connection.pk_and_sequence_for(table)
541
+ elsif @connection.respond_to?(:primary_key)
542
+ pk = @connection.primary_key(table)
543
+ end
544
+
545
+ tbl.print " create_table #{table.inspect}"
546
+
547
+ # addition to make temporary option work
548
+ tbl.print ", :temporary => true" if @connection.temporary_table?(table)
549
+
550
+ if columns.detect { |c| c.name == pk }
551
+ if pk != 'id'
552
+ tbl.print %Q(, :primary_key => "#{pk}")
553
+ end
554
+ else
555
+ tbl.print ", :id => false"
556
+ end
557
+ tbl.print ", :force => true"
558
+ tbl.puts " do |t|"
559
+
560
+ # then dump all non-primary key columns
561
+ column_specs = columns.map do |column|
562
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
563
+ next if column.name == pk
564
+ spec = column_spec(column)
565
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
566
+ spec
567
+ end.compact
568
+
569
+ # find all migration keys used in this table
570
+ keys = [:name, :limit, :precision, :scale, :default, :null, :with_z, :with_m, :srid] & column_specs.map(&:keys).flatten
571
+
572
+ # figure out the lengths for each column based on above keys
573
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
574
+
575
+ # the string we're going to sprintf our values against, with standardized column widths
576
+ format_string = lengths.map{ |len| "%-#{len}s" }
577
+
578
+ # find the max length for the 'type' column, which is special
579
+ type_length = column_specs.map{ |column| column[:type].length }.max
580
+
581
+ # add column type definition to our format string
582
+ format_string.unshift " t.%-#{type_length}s "
583
+
584
+ format_string *= ''
585
+
586
+ column_specs.each do |colspec|
587
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
588
+ values.unshift colspec[:type]
589
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
590
+ tbl.puts
591
+ end
592
+
593
+ tbl.puts " end"
594
+ tbl.puts
595
+
596
+ indexes(table, tbl)
597
+
598
+ tbl.rewind
599
+ stream.print tbl.read
600
+ rescue => e
601
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
602
+ stream.puts "# #{e.message} #{e.backtrace.join("\n")}"
603
+ stream.puts
604
+ end
605
+
606
+ stream
607
+ end
608
+
609
+ private
610
+
611
+ def indexes_with_oracle_enhanced_spatial(table, stream)
612
+ if (indexes = @connection.indexes(table)).any?
613
+ add_index_statements = indexes.map do |index|
614
+ case index.type
615
+ when nil
616
+ # use table.inspect as it will remove prefix and suffix
617
+ statement_parts = [ ('add_index ' + table.inspect) ]
618
+ statement_parts << index.columns.inspect
619
+ statement_parts << (':name => ' + index.name.inspect)
620
+ statement_parts << ':unique => true' if index.unique
621
+ statement_parts << ':tablespace => ' + index.tablespace.inspect if index.tablespace
622
+ when 'MDSYS.SPATIAL_INDEX'
623
+ statement_parts = [ ('add_index ' + table.inspect) ]
624
+ statement_parts << index.columns.inspect
625
+ statement_parts << (':name => ' + index.name.inspect)
626
+ statement_parts << ':unique => true' if index.unique
627
+ statement_parts << ':spatial => true' if index.spatial
628
+ statement_parts << ':tablespace => ' + index.tablespace.inspect if index.tablespace
629
+ when 'CTXSYS.CONTEXT'
630
+ if index.statement_parameters
631
+ statement_parts = [ ('add_context_index ' + table.inspect) ]
632
+ statement_parts << index.statement_parameters
633
+ else
634
+ statement_parts = [ ('add_context_index ' + table.inspect) ]
635
+ statement_parts << index.columns.inspect
636
+ statement_parts << (':name => ' + index.name.inspect)
637
+ end
638
+ else
639
+ # unrecognized index type
640
+ statement_parts = ["# unrecognized index #{index.name.inspect} with type #{index.type.inspect}"]
641
+ end
642
+ ' ' + statement_parts.join(', ')
643
+ end
644
+
645
+ stream.puts add_index_statements.sort.join("\n")
646
+ stream.puts
647
+ end
648
+ end
649
+ alias_method_chain :indexes, :oracle_enhanced_spatial
650
+
651
+ end