spatial_adapter 0.2.0
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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +178 -0
- data/VERSION +1 -0
- data/lib/spatial_adapter.rb +26 -0
- data/lib/spatial_adapter/adapters/mysql.rb +101 -0
- data/lib/spatial_adapter/adapters/postgis.rb +302 -0
- data/lib/spatial_adapter/raw_geom_info.rb +23 -0
- data/lib/spatial_adapter/schema_definitions.rb +11 -0
- data/lib/spatial_adapter/schema_dumper.rb +135 -0
- data/lib/spatial_adapter/spatial_column.rb +68 -0
- data/lib/spatial_adapter/table_definition.rb +14 -0
- data/rails/init.rb +1 -0
- data/spec/README.txt +16 -0
- data/spec/db/mysql_raw.rb +69 -0
- data/spec/db/postgis_raw.rb +95 -0
- data/spec/models/common.rb +32 -0
- data/spec/mysql/connection_adapter_spec.rb +93 -0
- data/spec/mysql/migration_spec.rb +63 -0
- data/spec/mysql/models_spec.rb +103 -0
- data/spec/mysql/schema_dumper_spec.rb +55 -0
- data/spec/postgis/connection_adapter_spec.rb +152 -0
- data/spec/postgis/migration_spec.rb +218 -0
- data/spec/postgis/models_spec.rb +136 -0
- data/spec/postgis/schema_dumper_spec.rb +58 -0
- data/spec/spec_helper.rb +73 -0
- metadata +97 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Guilhem Vellut <guilhem.vellut+georuby@gmail.com>
|
2
|
+
Copyright (c) 2010 Pete Deffendol <pete@fragility.us>
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
|
+
SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
= Spatial Adapter for ActiveRecord
|
2
|
+
|
3
|
+
This is the Spatial Adapter for ActiveRecord. It enhances ActiveRecord to
|
4
|
+
handle spatial datatypes in the following databases:
|
5
|
+
|
6
|
+
- PostgreSQL (using PostGIS)
|
7
|
+
- MySQL (using Spatial Extensions)
|
8
|
+
|
9
|
+
== Dependencies
|
10
|
+
|
11
|
+
The following gems are required:
|
12
|
+
|
13
|
+
- GeoRuby
|
14
|
+
- ActiveRecord (version 2.2.2 and up)
|
15
|
+
|
16
|
+
For PostgreSQL:
|
17
|
+
|
18
|
+
- PostGIS version 1.4.0 or higher should be installed in your database
|
19
|
+
|
20
|
+
== Installation
|
21
|
+
|
22
|
+
Choose ONE of the following installation methods. You shouldn't have to do both.
|
23
|
+
|
24
|
+
=== From RubyGems
|
25
|
+
|
26
|
+
This is the preferred method of installation, and will pull in the required
|
27
|
+
dependencies as well.
|
28
|
+
|
29
|
+
gem install spatial_adapter
|
30
|
+
|
31
|
+
In your Rails app, you can add a gem dependency in environment.rb:
|
32
|
+
|
33
|
+
config.gem "spatial_adapter"
|
34
|
+
|
35
|
+
=== As a Rails Plugin
|
36
|
+
|
37
|
+
In your Rails project, run the following:
|
38
|
+
|
39
|
+
script/plugin install git://github.com/fragility/spatial_adapter.git
|
40
|
+
|
41
|
+
You need to have Git installed first.
|
42
|
+
|
43
|
+
== Operations
|
44
|
+
|
45
|
+
Geometric columns in your ActiveRecord models now appear just like any other
|
46
|
+
column of other basic data types. They can also be dumped in ruby schema mode
|
47
|
+
and loaded in migrations the same way as columns of basic types.
|
48
|
+
|
49
|
+
=== Migrations
|
50
|
+
|
51
|
+
Here is an example of code for the creation of a table with a geometric column
|
52
|
+
in PostGIS, along with the addition of a spatial index on the column:
|
53
|
+
|
54
|
+
ActiveRecord::Schema.define do
|
55
|
+
create_table :table_points, :force => true do |t|
|
56
|
+
t.string :data
|
57
|
+
t.point :geom, :null => false, :srid => 123, :with_z => true
|
58
|
+
end
|
59
|
+
|
60
|
+
add_index :table_points, :geom, :spatial => true
|
61
|
+
end
|
62
|
+
|
63
|
+
Here is a related statement valid for MySql version <= 5.0.16:
|
64
|
+
|
65
|
+
ActiveRecord::Schema.define do
|
66
|
+
create_table "table_points", ;options=>"ENGINE=MyISAM", :force => true do |t|
|
67
|
+
t.string :data
|
68
|
+
t.point :geom, :null => false
|
69
|
+
end
|
70
|
+
|
71
|
+
add_index :table_points, :geom, :spatial => true
|
72
|
+
end
|
73
|
+
|
74
|
+
=== Differences Between Databases
|
75
|
+
|
76
|
+
- On all versions of MySQL, the :srid, :with_z, and :with_m options are ignored, since
|
77
|
+
they are not supported.
|
78
|
+
|
79
|
+
- On MySQL versions <= 5.0.16, you have to add <tt>:options =>
|
80
|
+
"ENGINE=MyISAM"</tt> to the create_table statement, since only MyISAM tables
|
81
|
+
can have spatial columns. In addition, only MyISAM tables may have spatial
|
82
|
+
indexes.
|
83
|
+
|
84
|
+
=== Models
|
85
|
+
|
86
|
+
Create your ActiveRecord models normally. Spatial Adapter will automatically
|
87
|
+
handle spatial columns, converting them to the appropriate GeoRuby type.
|
88
|
+
|
89
|
+
class TablePoint < ActiveRecord::Base
|
90
|
+
end
|
91
|
+
|
92
|
+
=== Access
|
93
|
+
|
94
|
+
Here is an example of row creation and access, using the model and the table
|
95
|
+
defined above:
|
96
|
+
|
97
|
+
pt = TablePoint.new(
|
98
|
+
:data => "Hello!",
|
99
|
+
:geom => Point.from_x_y_z(-1.6, 2.8, -3.4, 123))
|
100
|
+
pt.save
|
101
|
+
pt = TablePoint.find_first
|
102
|
+
puts pt.geom.x #access the geom column like any other
|
103
|
+
|
104
|
+
=== Fixtures
|
105
|
+
|
106
|
+
If you use fixtures for your unit tests, at some point, you will want to input
|
107
|
+
a geometry. You could transform your geometries to a form suitable for YAML
|
108
|
+
yourself every time but Spatial Adapter provides a method to do it for you:
|
109
|
+
+to_fixture_format+. You would use it like this, if the geometric column is a
|
110
|
+
point:
|
111
|
+
|
112
|
+
fixture:
|
113
|
+
id: 1
|
114
|
+
data: HELLO
|
115
|
+
geom: <%= Point.from_x_y(123.5,321.9).to_fixture_format %>
|
116
|
+
|
117
|
+
=== Finder Enhancements
|
118
|
+
|
119
|
+
Enhancements to find_by_* and friends has been removed from this version of
|
120
|
+
Spatial Adapter until a cleaner implementation can be made. (The previous
|
121
|
+
implementation made adapter-specific modifications to ActiveRecord::Base,
|
122
|
+
which prevented multiple adapters from being loaded at once.)
|
123
|
+
|
124
|
+
=== Geometric data types
|
125
|
+
|
126
|
+
Ruby geometric datatypes are currently made available only through the GeoRuby
|
127
|
+
library (http://georuby.rubyforge.org/): This is where the
|
128
|
+
<tt>Point.from_x_y</tt> in the example above comes from.
|
129
|
+
|
130
|
+
== Warning
|
131
|
+
|
132
|
+
- Since ActiveRecord seems to keep only the string values directly returned
|
133
|
+
from the database, it translates from these to the correct types everytime
|
134
|
+
an attribute is read, which is probably ok for simple types, but might be
|
135
|
+
less than efficient for geometries, since the EWKB string has to be parsed
|
136
|
+
everytime. Also it means you cannot modify the geometry object returned from
|
137
|
+
an attribute directly:
|
138
|
+
|
139
|
+
place = Place.find_first
|
140
|
+
place.the_geom.y=123456.7 # this doesn't work
|
141
|
+
|
142
|
+
Since the translation to a geometry is performed every time the_geom is read,
|
143
|
+
the change to y will not be saved! You would have to do something like this:
|
144
|
+
|
145
|
+
place = Place.find_first
|
146
|
+
the_geom = place.the_geom
|
147
|
+
the_geom.y=123456.7
|
148
|
+
place.the_geom = the_geom
|
149
|
+
|
150
|
+
== License
|
151
|
+
|
152
|
+
The Spatial Adapter for ActiveRecord is released under the MIT license.
|
153
|
+
|
154
|
+
== Latest Changes
|
155
|
+
|
156
|
+
Spatial Adapter has been refactored and is now available as a Ruby gem. The
|
157
|
+
dependency on Rails has been removed. Unfortunately, the current version is
|
158
|
+
without some of the previous functionality, until a cleaner implementation is
|
159
|
+
made.
|
160
|
+
|
161
|
+
The previous release is available on the "legacy" branch.
|
162
|
+
|
163
|
+
=== Removed Features in 0.2.0
|
164
|
+
|
165
|
+
- Compatibility with ActiveRecord/Rails older than version 2.2.2
|
166
|
+
- enhancements to find_by_* for spatial columns
|
167
|
+
- to_fixture_format extension to the GeoRuby types
|
168
|
+
|
169
|
+
These will hopefully be added back in the near future.
|
170
|
+
|
171
|
+
== TODO
|
172
|
+
|
173
|
+
- Add support for PostGIS geography types (in PostGIS 1.5.0+)
|
174
|
+
|
175
|
+
== Support
|
176
|
+
|
177
|
+
Any questions, enhancement proposals, bug notifications or corrections can be
|
178
|
+
made via the project page at http://github.com/fragility/spatial_adapter
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'geo_ruby'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module SpatialAdapter
|
5
|
+
#Translation of geometric data types
|
6
|
+
def geometry_data_types
|
7
|
+
{
|
8
|
+
:point => { :name => "POINT" },
|
9
|
+
:line_string => { :name => "LINESTRING" },
|
10
|
+
:polygon => { :name => "POLYGON" },
|
11
|
+
:geometry_collection => { :name => "GEOMETRYCOLLECTION" },
|
12
|
+
:multi_point => { :name => "MULTIPOINT" },
|
13
|
+
:multi_line_string => { :name => "MULTILINESTRING" },
|
14
|
+
:multi_polygon => { :name => "MULTIPOLYGON" },
|
15
|
+
:geometry => { :name => "GEOMETRY"}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spatial_adapter/raw_geom_info'
|
21
|
+
require 'spatial_adapter/spatial_column'
|
22
|
+
require 'spatial_adapter/schema_definitions'
|
23
|
+
require 'spatial_adapter/schema_dumper'
|
24
|
+
require 'spatial_adapter/table_definition'
|
25
|
+
require 'spatial_adapter/adapters/postgis'
|
26
|
+
require 'spatial_adapter/adapters/mysql'
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'active_record/connection_adapters/mysql_adapter'
|
2
|
+
|
3
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
4
|
+
include SpatialAdapter
|
5
|
+
|
6
|
+
def supports_geography?
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
alias :original_native_database_types :native_database_types
|
11
|
+
def native_database_types
|
12
|
+
original_native_database_types.merge!(geometry_data_types)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias :original_quote :quote
|
16
|
+
#Redefines the quote method to add behaviour for when a Geometry is encountered ; used when binding variables in find_by methods
|
17
|
+
def quote(value, column = nil)
|
18
|
+
if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
|
19
|
+
"GeomFromWKB(0x#{value.as_hex_wkb},#{value.srid})"
|
20
|
+
else
|
21
|
+
original_quote(value,column)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#Redefinition of columns to add the information that a column is geometric
|
26
|
+
def columns(table_name, name = nil)#:nodoc:
|
27
|
+
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
28
|
+
columns = []
|
29
|
+
result = execute(sql, name)
|
30
|
+
result.each do |field|
|
31
|
+
if field[1] =~ /geometry|point|linestring|polygon|multipoint|multilinestring|multipolygon|geometrycollection/i
|
32
|
+
#to note that the column is spatial
|
33
|
+
columns << ActiveRecord::ConnectionAdapters::SpatialMysqlColumn.new(field[0], field[4], field[1], field[2] == "YES")
|
34
|
+
else
|
35
|
+
columns << ActiveRecord::ConnectionAdapters::MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
result.free
|
39
|
+
columns
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
#operations relative to migrations
|
44
|
+
|
45
|
+
#Redefines add_index to support the case where the index is spatial
|
46
|
+
#If the :spatial key in the options table is true, then the sql string for a spatial index is created
|
47
|
+
def add_index(table_name,column_name,options = {})
|
48
|
+
index_name = options[:name] || index_name(table_name,:column => Array(column_name))
|
49
|
+
|
50
|
+
if options[:spatial]
|
51
|
+
execute "CREATE SPATIAL INDEX #{index_name} ON #{table_name} (#{Array(column_name).join(", ")})"
|
52
|
+
else
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#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)
|
58
|
+
def indexes(table_name, name = nil)#:nodoc:
|
59
|
+
indexes = []
|
60
|
+
current_index = nil
|
61
|
+
execute("SHOW KEYS FROM #{table_name}", name).each do |row|
|
62
|
+
if current_index != row[2]
|
63
|
+
next if row[2] == "PRIMARY" # skip the primary key
|
64
|
+
current_index = row[2]
|
65
|
+
indexes << ActiveRecord::ConnectionAdapters::IndexDefinition.new(row[0], row[2], row[1] == "0", [], row[10] == "SPATIAL")
|
66
|
+
end
|
67
|
+
indexes.last.columns << row[4]
|
68
|
+
end
|
69
|
+
indexes
|
70
|
+
end
|
71
|
+
|
72
|
+
#Get the table creation options : Only the engine for now. The text encoding could also be parsed and returned here.
|
73
|
+
def options_for(table)
|
74
|
+
result = execute("show table status like '#{table}'")
|
75
|
+
engine = result.fetch_row[1]
|
76
|
+
if engine !~ /inno/i #inno is default so do nothing for it in order not to clutter the migration
|
77
|
+
"ENGINE=#{engine}"
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
module ActiveRecord
|
86
|
+
module ConnectionAdapters
|
87
|
+
class SpatialMysqlColumn < MysqlColumn
|
88
|
+
include SpatialAdapter::SpatialColumn
|
89
|
+
|
90
|
+
#MySql-specific geometry string parsing. By default, MySql returns geometries in strict wkb format with "0" characters in the first 4 positions.
|
91
|
+
def self.string_to_geometry(string)
|
92
|
+
return string unless string.is_a?(String)
|
93
|
+
begin
|
94
|
+
GeoRuby::SimpleFeatures::Geometry.from_ewkb(string[4..-1])
|
95
|
+
rescue Exception => exception
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
2
|
+
|
3
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
4
|
+
include SpatialAdapter
|
5
|
+
|
6
|
+
def postgis_version
|
7
|
+
begin
|
8
|
+
select_value("SELECT postgis_full_version()").scan(/POSTGIS="([\d\.]*)"/)[0][0]
|
9
|
+
rescue ActiveRecord::StatementInvalid
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def postgis_major_version
|
15
|
+
version = postgis_version
|
16
|
+
version ? version.scan(/^(\d)\.\d\.\d$/)[0][0].to_i : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def postgis_minor_version
|
20
|
+
version = postgis_version
|
21
|
+
version ? version.scan(/^\d\.(\d)\.\d$/)[0][0].to_i : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def spatial?
|
25
|
+
!postgis_version.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def supports_geography?
|
29
|
+
postgis_major_version > 1 || (postgis_major_version == 1 && postgis_minor_version >= 5)
|
30
|
+
end
|
31
|
+
|
32
|
+
alias :original_native_database_types :native_database_types
|
33
|
+
def native_database_types
|
34
|
+
original_native_database_types.merge!(geometry_data_types)
|
35
|
+
end
|
36
|
+
|
37
|
+
alias :original_quote :quote
|
38
|
+
#Redefines the quote method to add behaviour for when a Geometry is encountered
|
39
|
+
def quote(value, column = nil)
|
40
|
+
if value.kind_of?(GeoRuby::SimpleFeatures::Geometry)
|
41
|
+
"'#{value.as_hex_ewkb}'"
|
42
|
+
else
|
43
|
+
original_quote(value,column)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def columns(table_name, name = nil) #:nodoc:
|
48
|
+
raw_geom_infos = column_spatial_info(table_name)
|
49
|
+
|
50
|
+
column_definitions(table_name).collect do |name, type, default, notnull|
|
51
|
+
case type
|
52
|
+
when /geometry/i
|
53
|
+
raw_geom_info = raw_geom_infos[name]
|
54
|
+
if raw_geom_info.nil?
|
55
|
+
ActiveRecord::ConnectionAdapters::SpatialPostgreSQLColumn.create_simplified(name, default, notnull == "f")
|
56
|
+
else
|
57
|
+
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)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new(name, default, type, notnull == "f")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_table(table_name, options = {})
|
66
|
+
# Using the subclassed table definition
|
67
|
+
table_definition = ActiveRecord::ConnectionAdapters::PostgreSQLTableDefinition.new(self)
|
68
|
+
table_definition.primary_key(options[:primary_key] || ActiveRecord::Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
|
69
|
+
|
70
|
+
yield table_definition if block_given?
|
71
|
+
|
72
|
+
if options[:force] && table_exists?(table_name)
|
73
|
+
drop_table(table_name, options)
|
74
|
+
end
|
75
|
+
|
76
|
+
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
77
|
+
create_sql << "#{quote_table_name(table_name)} ("
|
78
|
+
create_sql << table_definition.to_sql
|
79
|
+
create_sql << ") #{options[:options]}"
|
80
|
+
execute create_sql
|
81
|
+
|
82
|
+
# This is the additional portion for PostGIS
|
83
|
+
unless table_definition.geom_columns.nil?
|
84
|
+
table_definition.geom_columns.each do |geom_column|
|
85
|
+
execute geom_column.to_sql(table_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
alias :original_remove_column :remove_column
|
91
|
+
def remove_column(table_name, column_name, options = {})
|
92
|
+
columns(table_name).each do |col|
|
93
|
+
if col.name == column_name.to_s
|
94
|
+
#check if the column is geometric
|
95
|
+
unless geometry_data_types[col.type].nil? or
|
96
|
+
(options[:remove_using_dropgeometrycolumn] == false)
|
97
|
+
execute "SELECT DropGeometryColumn('#{table_name}','#{column_name}')"
|
98
|
+
else
|
99
|
+
original_remove_column(table_name, column_name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
alias :original_add_column :add_column
|
106
|
+
def add_column(table_name, column_name, type, options = {})
|
107
|
+
unless geometry_data_types[type].nil? or (options[:create_using_addgeometrycolumn] == false)
|
108
|
+
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)
|
109
|
+
execute geom_column.to_sql(table_name)
|
110
|
+
else
|
111
|
+
original_add_column(table_name, column_name, type, options)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Adds an index to a column.
|
116
|
+
def add_index(table_name, column_name, options = {})
|
117
|
+
column_names = Array(column_name)
|
118
|
+
index_name = index_name(table_name, :column => column_names)
|
119
|
+
|
120
|
+
if Hash === options # legacy support, since this param was a string
|
121
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
122
|
+
index_name = options[:name] || index_name
|
123
|
+
index_method = options[:spatial] ? 'USING GIST' : ""
|
124
|
+
else
|
125
|
+
index_type = options
|
126
|
+
end
|
127
|
+
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
|
128
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_method} (#{quoted_column_names})"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns the list of all indexes for a table.
|
132
|
+
# This is a full replacement for the ActiveRecord method and as a result
|
133
|
+
# has a higher probability of breaking in future releases
|
134
|
+
def indexes(table_name, name = nil)
|
135
|
+
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
136
|
+
result = query(<<-SQL, name)
|
137
|
+
SELECT i.relname, d.indisunique, d.indkey, t.oid, am.amname
|
138
|
+
FROM pg_class t, pg_class i, pg_index d, pg_attribute a, pg_am am
|
139
|
+
WHERE i.relkind = 'i'
|
140
|
+
AND d.indexrelid = i.oid
|
141
|
+
AND d.indisprimary = 'f'
|
142
|
+
AND t.oid = d.indrelid
|
143
|
+
AND i.relam = am.oid
|
144
|
+
AND t.relname = '#{table_name}'
|
145
|
+
AND a.attrelid = t.oid
|
146
|
+
AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
|
147
|
+
OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
|
148
|
+
OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
|
149
|
+
OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
|
150
|
+
OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
|
151
|
+
ORDER BY i.relname
|
152
|
+
SQL
|
153
|
+
|
154
|
+
indexes = []
|
155
|
+
|
156
|
+
indexes = result.map do |row|
|
157
|
+
index_name = row[0]
|
158
|
+
unique = row[1] == 't'
|
159
|
+
indkey = row[2].split(" ")
|
160
|
+
oid = row[3]
|
161
|
+
spatial = row[4] == "gist"
|
162
|
+
|
163
|
+
columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
|
164
|
+
SELECT a.attname, a.attnum
|
165
|
+
FROM pg_attribute a
|
166
|
+
WHERE a.attrelid = #{oid}
|
167
|
+
AND a.attnum IN (#{indkey.join(",")})
|
168
|
+
SQL
|
169
|
+
|
170
|
+
column_names = indkey.map {|attnum| columns[attnum] }
|
171
|
+
ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, spatial)
|
172
|
+
end
|
173
|
+
|
174
|
+
indexes
|
175
|
+
end
|
176
|
+
|
177
|
+
def disable_referential_integrity(&block) #:nodoc:
|
178
|
+
if supports_disable_referential_integrity?() then
|
179
|
+
execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
|
180
|
+
end
|
181
|
+
yield
|
182
|
+
ensure
|
183
|
+
if supports_disable_referential_integrity?() then
|
184
|
+
execute(tables_without_postgis.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def tables_without_postgis
|
191
|
+
tables - %w{ geometry_columns spatial_ref_sys }
|
192
|
+
end
|
193
|
+
|
194
|
+
def column_spatial_info(table_name)
|
195
|
+
constr = query("SELECT * FROM geometry_columns WHERE f_table_name = '#{table_name}'")
|
196
|
+
|
197
|
+
raw_geom_infos = {}
|
198
|
+
constr.each do |constr_def_a|
|
199
|
+
raw_geom_infos[constr_def_a[3]] ||= SpatialAdapter::RawGeomInfo.new
|
200
|
+
raw_geom_infos[constr_def_a[3]].type = constr_def_a[6]
|
201
|
+
raw_geom_infos[constr_def_a[3]].dimension = constr_def_a[4].to_i
|
202
|
+
raw_geom_infos[constr_def_a[3]].srid = constr_def_a[5].to_i
|
203
|
+
|
204
|
+
if raw_geom_infos[constr_def_a[3]].type[-1] == ?M
|
205
|
+
raw_geom_infos[constr_def_a[3]].with_m = true
|
206
|
+
raw_geom_infos[constr_def_a[3]].type.chop!
|
207
|
+
else
|
208
|
+
raw_geom_infos[constr_def_a[3]].with_m = false
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
raw_geom_infos.each_value do |raw_geom_info|
|
213
|
+
#check the presence of z and m
|
214
|
+
raw_geom_info.convert!
|
215
|
+
end
|
216
|
+
|
217
|
+
raw_geom_infos
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
module ActiveRecord
|
223
|
+
module ConnectionAdapters
|
224
|
+
class PostgreSQLTableDefinition < TableDefinition
|
225
|
+
attr_reader :geom_columns
|
226
|
+
|
227
|
+
def column(name, type, options = {})
|
228
|
+
unless (@base.geometry_data_types[type.to_sym].nil? or
|
229
|
+
(options[:create_using_addgeometrycolumn] == false))
|
230
|
+
|
231
|
+
geom_column = PostgreSQLColumnDefinition.new(@base, name, type)
|
232
|
+
geom_column.null = options[:null]
|
233
|
+
geom_column.srid = options[:srid] || -1
|
234
|
+
geom_column.with_z = options[:with_z] || false
|
235
|
+
geom_column.with_m = options[:with_m] || false
|
236
|
+
|
237
|
+
@geom_columns ||= []
|
238
|
+
@geom_columns << geom_column
|
239
|
+
else
|
240
|
+
super(name, type, options)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class PostgreSQLColumnDefinition < ColumnDefinition
|
246
|
+
attr_accessor :srid, :with_z, :with_m
|
247
|
+
attr_reader :spatial
|
248
|
+
|
249
|
+
def initialize(base = nil, name = nil, type=nil, limit=nil, default=nil, null=nil, srid=-1, with_z=false, with_m=false)
|
250
|
+
super(base, name, type, limit, default,null)
|
251
|
+
@spatial = true
|
252
|
+
@srid = srid
|
253
|
+
@with_z = with_z
|
254
|
+
@with_m = with_m
|
255
|
+
end
|
256
|
+
|
257
|
+
def to_sql(table_name)
|
258
|
+
if @spatial
|
259
|
+
type_sql = type_to_sql(type.to_sym)
|
260
|
+
type_sql += "M" if with_m and !with_z
|
261
|
+
if with_m and with_z
|
262
|
+
dimension = 4
|
263
|
+
elsif with_m or with_z
|
264
|
+
dimension = 3
|
265
|
+
else
|
266
|
+
dimension = 2
|
267
|
+
end
|
268
|
+
|
269
|
+
column_sql = "SELECT AddGeometryColumn('#{table_name}','#{name}',#{srid},'#{type_sql}',#{dimension})"
|
270
|
+
column_sql += ";ALTER TABLE #{table_name} ALTER #{name} SET NOT NULL" if null == false
|
271
|
+
column_sql
|
272
|
+
else
|
273
|
+
super
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
private
|
278
|
+
|
279
|
+
def type_to_sql(name, limit=nil)
|
280
|
+
base.type_to_sql(name, limit) rescue name
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
module ActiveRecord
|
287
|
+
module ConnectionAdapters
|
288
|
+
class SpatialPostgreSQLColumn < PostgreSQLColumn
|
289
|
+
include SpatialAdapter::SpatialColumn
|
290
|
+
|
291
|
+
#Transforms a string to a geometry. PostGIS returns a HewEWKB string.
|
292
|
+
def self.string_to_geometry(string)
|
293
|
+
return string unless string.is_a?(String)
|
294
|
+
GeoRuby::SimpleFeatures::Geometry.from_hex_ewkb(string) rescue nil
|
295
|
+
end
|
296
|
+
|
297
|
+
def self.create_simplified(name,default,null = true)
|
298
|
+
new(name,default,"geometry",null,nil,nil,nil)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|