activerecord-postgis-adapter 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Documentation.rdoc +322 -0
- data/History.rdoc +5 -0
- data/README.rdoc +42 -290
- data/Version +1 -1
- data/lib/active_record/connection_adapters/postgis_adapter.rb +35 -21
- data/lib/active_record/connection_adapters/postgis_adapter/rails3/create_connection.rb +96 -0
- data/lib/active_record/connection_adapters/postgis_adapter/{databases.rake → rails3/databases.rake} +7 -1
- data/lib/active_record/connection_adapters/postgis_adapter/{main_adapter.rb → rails3/main_adapter.rb} +9 -9
- data/lib/active_record/connection_adapters/postgis_adapter/{spatial_column.rb → rails3/spatial_column.rb} +4 -8
- data/lib/active_record/connection_adapters/postgis_adapter/{spatial_table_definition.rb → rails3/spatial_table_definition.rb} +5 -9
- data/lib/active_record/connection_adapters/postgis_adapter/rails4/create_connection.rb +88 -0
- data/lib/active_record/connection_adapters/postgis_adapter/rails4/databases.rake +52 -0
- data/lib/active_record/connection_adapters/postgis_adapter/rails4/main_adapter.rb +310 -0
- data/lib/active_record/connection_adapters/postgis_adapter/rails4/postgis_database_tasks.rb +232 -0
- data/lib/active_record/connection_adapters/postgis_adapter/rails4/spatial_column.rb +220 -0
- data/lib/active_record/connection_adapters/postgis_adapter/rails4/spatial_table_definition.rb +140 -0
- data/lib/active_record/connection_adapters/postgis_adapter/railtie.rb +3 -28
- data/lib/active_record/connection_adapters/postgis_adapter/{arel_tosql.rb → shared/arel_tosql.rb} +3 -7
- data/lib/active_record/connection_adapters/postgis_adapter/shared/jdbc_compat.rb +133 -0
- data/lib/active_record/connection_adapters/postgis_adapter/shared/railtie.rb +66 -0
- data/lib/active_record/connection_adapters/postgis_adapter/shared/setup.rb +57 -0
- data/lib/active_record/connection_adapters/postgis_adapter/{version.rb → shared/version.rb} +1 -1
- data/lib/activerecord-postgis-adapter.rb +37 -0
- data/lib/rgeo/active_record/postgis_adapter/railtie.rb +1 -1
- data/test/tc_basic.rb +43 -16
- data/test/tc_ddl.rb +2 -2
- data/test/tc_nested_class.rb +2 -2
- data/test/tc_spatial_queries.rb +14 -9
- data/test/tc_tasks.rb +110 -0
- metadata +27 -14
- data/lib/active_record/connection_adapters/postgis_adapter/jdbc_connection.rb +0 -78
- data/lib/active_record/connection_adapters/postgis_adapter/pg_connection.rb +0 -27
@@ -0,0 +1,310 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# PostGIS adapter for ActiveRecord
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010-2012 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
module ActiveRecord # :nodoc:
|
38
|
+
|
39
|
+
module ConnectionAdapters # :nodoc:
|
40
|
+
|
41
|
+
module PostGISAdapter # :nodoc:
|
42
|
+
|
43
|
+
|
44
|
+
class MainAdapter < PostgreSQLAdapter # :nodoc:
|
45
|
+
|
46
|
+
|
47
|
+
SPATIAL_COLUMN_CONSTRUCTORS = ::RGeo::ActiveRecord::DEFAULT_SPATIAL_COLUMN_CONSTRUCTORS.merge(
|
48
|
+
:geography => {:type => 'geometry', :geographic => true}
|
49
|
+
)
|
50
|
+
|
51
|
+
@@native_database_types = nil
|
52
|
+
|
53
|
+
|
54
|
+
# Overridden to change the visitor
|
55
|
+
|
56
|
+
def initialize(*args_)
|
57
|
+
super
|
58
|
+
@visitor = ::Arel::Visitors::PostGIS.new(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def set_rgeo_factory_settings(factory_settings_)
|
63
|
+
@rgeo_factory_settings = factory_settings_
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def adapter_name
|
68
|
+
PostGISAdapter::ADAPTER_NAME
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def spatial_column_constructor(name_)
|
73
|
+
SPATIAL_COLUMN_CONSTRUCTORS[name_]
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Overridden to add the :spatial type
|
78
|
+
|
79
|
+
def native_database_types
|
80
|
+
@@native_database_types ||= super.merge(:spatial => {:name => 'geometry'})
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def postgis_lib_version
|
85
|
+
unless defined?(@postgis_lib_version)
|
86
|
+
@postgis_lib_version = select_value("SELECT PostGIS_Lib_Version()") rescue nil
|
87
|
+
end
|
88
|
+
@postgis_lib_version
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def srs_database_columns
|
93
|
+
{:srtext_column => 'srtext', :proj4text_column => 'proj4text', :auth_name_column => 'auth_name', :auth_srid_column => 'auth_srid'}
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def quote(value_, column_=nil)
|
98
|
+
# Overridden to recognize geometry types
|
99
|
+
if ::RGeo::Feature::Geometry.check_type(value_)
|
100
|
+
"'#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true, :type_format => :ewkb, :emit_ewkb_srid => true).generate(value_)}'"
|
101
|
+
elsif value_.is_a?(::RGeo::Cartesian::BoundingBox)
|
102
|
+
"'#{value_.min_x},#{value_.min_y},#{value_.max_x},#{value_.max_y}'::box"
|
103
|
+
else
|
104
|
+
super
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def type_cast(value_, column_, array_member_=false)
|
110
|
+
# Overridden to recognize geometry types
|
111
|
+
if ::RGeo::Feature::Geometry.check_type(value_)
|
112
|
+
::RGeo::WKRep::WKBGenerator.new(:hex_format => true, :type_format => :ewkb, :emit_ewkb_srid => true).generate(value_)
|
113
|
+
else
|
114
|
+
super
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def columns(table_name_, name_=nil)
|
120
|
+
# FULL REPLACEMENT. RE-CHECK ON NEW VERSIONS.
|
121
|
+
# We needed to return a spatial column subclass.
|
122
|
+
table_name_ = table_name_.to_s
|
123
|
+
spatial_info_ = spatial_column_info(table_name_)
|
124
|
+
column_definitions(table_name_).collect do |col_name_, type_, default_, notnull_, oid_, fmod_|
|
125
|
+
# JDBC support: JDBC adapter returns a hash for column definitions,
|
126
|
+
# instead of an array of values.
|
127
|
+
if col_name_.kind_of?(::Hash)
|
128
|
+
notnull_ = col_name_["column_not_null"]
|
129
|
+
default_ = col_name_["column_default"]
|
130
|
+
type_ = col_name_["column_type"]
|
131
|
+
col_name_ = col_name_["column_name"]
|
132
|
+
# TODO: get oid and fmod from jdbc
|
133
|
+
end
|
134
|
+
oid_ = OID::TYPE_MAP.fetch(oid_.to_i, fmod_.to_i) {
|
135
|
+
OID::Identity.new
|
136
|
+
}
|
137
|
+
SpatialColumn.new(@rgeo_factory_settings, table_name_, col_name_, default_, oid_, type_,
|
138
|
+
notnull_ == 'f', type_ =~ /geometry/i ? spatial_info_[col_name_] : nil)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
def indexes(table_name_, name_=nil)
|
144
|
+
# FULL REPLACEMENT. RE-CHECK ON NEW VERSIONS.
|
145
|
+
result_ = query(<<-SQL, 'SCHEMA')
|
146
|
+
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
|
147
|
+
FROM pg_class t
|
148
|
+
INNER JOIN pg_index d ON t.oid = d.indrelid
|
149
|
+
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
150
|
+
WHERE i.relkind = 'i'
|
151
|
+
AND d.indisprimary = 'f'
|
152
|
+
AND t.relname = '#{table_name_}'
|
153
|
+
AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
|
154
|
+
ORDER BY i.relname
|
155
|
+
SQL
|
156
|
+
|
157
|
+
result_.map do |row_|
|
158
|
+
index_name_ = row_[0]
|
159
|
+
unique_ = row_[1] == 't'
|
160
|
+
indkey_ = row_[2].split(" ")
|
161
|
+
inddef_ = row_[3]
|
162
|
+
oid_ = row_[4]
|
163
|
+
|
164
|
+
columns_ = query(<<-SQL, "SCHEMA")
|
165
|
+
SELECT a.attnum, a.attname, t.typname
|
166
|
+
FROM pg_attribute a, pg_type t
|
167
|
+
WHERE a.attrelid = #{oid_}
|
168
|
+
AND a.attnum IN (#{indkey_.join(",")})
|
169
|
+
AND a.atttypid = t.oid
|
170
|
+
SQL
|
171
|
+
columns_ = columns_.inject({}){ |h_, r_| h_[r_[0].to_s] = [r_[1], r_[2]]; h_ }
|
172
|
+
column_names_ = columns_.values_at(*indkey_).compact.map{ |a_| a_[0] }
|
173
|
+
|
174
|
+
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
175
|
+
desc_order_columns_ = inddef_.scan(/(\w+) DESC/).flatten
|
176
|
+
orders_ = desc_order_columns_.any? ? Hash[desc_order_columns_.map {|order_column_| [order_column_, :desc]}] : {}
|
177
|
+
where_ = inddef_.scan(/WHERE (.+)$/).flatten[0]
|
178
|
+
spatial_ = inddef_ =~ /using\s+gist/i && columns_.size == 1 &&
|
179
|
+
(columns_.values.first[1] == 'geometry' || columns_.values.first[1] == 'geography')
|
180
|
+
|
181
|
+
if column_names_.empty?
|
182
|
+
nil
|
183
|
+
else
|
184
|
+
::RGeo::ActiveRecord::SpatialIndexDefinition.new(table_name_, index_name_, unique_, column_names_, [], orders_, where_, spatial_ ? true : false)
|
185
|
+
end
|
186
|
+
end.compact
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def table_definition
|
191
|
+
# Override to create a spatial table definition
|
192
|
+
SpatialTableDefinition.new(self)
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
def create_table(table_name_, options_={}, &block_)
|
197
|
+
table_name_ = table_name_.to_s
|
198
|
+
# Call super and snag the table definition
|
199
|
+
table_definition_ = nil
|
200
|
+
super(table_name_, options_) do |td_|
|
201
|
+
block_.call(td_) if block_
|
202
|
+
table_definition_ = td_
|
203
|
+
end
|
204
|
+
table_definition_.non_geographic_spatial_columns.each do |col_|
|
205
|
+
type_ = col_.spatial_type.gsub('_', '').upcase
|
206
|
+
has_z_ = col_.has_z?
|
207
|
+
has_m_ = col_.has_m?
|
208
|
+
type_ = "#{type_}M" if has_m_ && !has_z_
|
209
|
+
dimensions_ = 2
|
210
|
+
dimensions_ += 1 if has_z_
|
211
|
+
dimensions_ += 1 if has_m_
|
212
|
+
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(col_.name.to_s)}', #{col_.srid}, '#{quote_string(type_)}', #{dimensions_})")
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
def drop_table(table_name_, *options_)
|
218
|
+
execute("DELETE from geometry_columns where f_table_name='#{quote_string(table_name_.to_s)}'")
|
219
|
+
super
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
def add_column(table_name_, column_name_, type_, options_={})
|
224
|
+
table_name_ = table_name_.to_s
|
225
|
+
column_name_ = column_name_.to_s
|
226
|
+
if (info_ = spatial_column_constructor(type_.to_sym))
|
227
|
+
limit_ = options_[:limit]
|
228
|
+
if type_.to_s == 'geometry' &&
|
229
|
+
(options_[:no_constraints] || limit_.is_a?(::Hash) && limit_[:no_constraints])
|
230
|
+
then
|
231
|
+
options_.delete(:limit)
|
232
|
+
super
|
233
|
+
else
|
234
|
+
options_.merge!(limit_) if limit_.is_a?(::Hash)
|
235
|
+
type_ = (options_[:type] || info_[:type] || type_).to_s.gsub('_', '').upcase
|
236
|
+
has_z_ = options_[:has_z]
|
237
|
+
has_m_ = options_[:has_m]
|
238
|
+
srid_ = (options_[:srid] || -1).to_i
|
239
|
+
if options_[:geographic]
|
240
|
+
type_ << 'Z' if has_z_
|
241
|
+
type_ << 'M' if has_m_
|
242
|
+
execute("ALTER TABLE #{quote_table_name(table_name_)} ADD COLUMN #{quote_column_name(column_name_)} GEOGRAPHY(#{type_},#{srid_})")
|
243
|
+
change_column_default(table_name_, column_name_, options_[:default]) if options_include_default?(options_)
|
244
|
+
change_column_null(table_name_, column_name_, false, options_[:default]) if options_[:null] == false
|
245
|
+
else
|
246
|
+
type_ = "#{type_}M" if has_m_ && !has_z_
|
247
|
+
dimensions_ = 2
|
248
|
+
dimensions_ += 1 if has_z_
|
249
|
+
dimensions_ += 1 if has_m_
|
250
|
+
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(column_name_)}', #{srid_}, '#{quote_string(type_)}', #{dimensions_})")
|
251
|
+
end
|
252
|
+
end
|
253
|
+
else
|
254
|
+
super
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
def remove_column(table_name_, column_name_, type_=nil, options_={})
|
260
|
+
table_name_ = table_name_.to_s
|
261
|
+
column_name_ = column_name_.to_s
|
262
|
+
spatial_info_ = spatial_column_info(table_name_)
|
263
|
+
if spatial_info_.include?(column_name_)
|
264
|
+
execute("SELECT DropGeometryColumn('#{quote_string(table_name_)}','#{quote_string(column_name_)}')")
|
265
|
+
else
|
266
|
+
super
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
def add_index(table_name_, column_name_, options_={})
|
272
|
+
# FULL REPLACEMENT. RE-CHECK ON NEW VERSIONS.
|
273
|
+
# We have to fully-replace because of the gist_clause.
|
274
|
+
gist_clause_ = options_.delete(:spatial) ? ' USING GIST' : ''
|
275
|
+
index_name_, index_type_, index_columns_, index_options_ = add_index_options(table_name_, column_name_, options_)
|
276
|
+
execute "CREATE #{index_type_} INDEX #{quote_column_name(index_name_)} ON #{quote_table_name(table_name_)}#{gist_clause_} (#{index_columns_})#{index_options_}"
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
def spatial_column_info(table_name_)
|
281
|
+
info_ = query("SELECT f_geometry_column,coord_dimension,srid,type FROM geometry_columns WHERE f_table_name='#{quote_string(table_name_.to_s)}'")
|
282
|
+
result_ = {}
|
283
|
+
info_.each do |row_|
|
284
|
+
name_ = row_[0]
|
285
|
+
type_ = row_[3]
|
286
|
+
dimension_ = row_[1].to_i
|
287
|
+
has_m_ = type_ =~ /m$/i ? true : false
|
288
|
+
type_.sub!(/m$/, '')
|
289
|
+
has_z_ = dimension_ > 3 || dimension_ == 3 && !has_m_
|
290
|
+
result_[name_] = {
|
291
|
+
:name => name_,
|
292
|
+
:type => type_,
|
293
|
+
:dimension => dimension_,
|
294
|
+
:srid => row_[2].to_i,
|
295
|
+
:has_z => has_z_,
|
296
|
+
:has_m => has_m_,
|
297
|
+
}
|
298
|
+
end
|
299
|
+
result_
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
end
|
304
|
+
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# PostGIS adapter for ActiveRecord
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010-2012 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
module ActiveRecord # :nodoc:
|
38
|
+
|
39
|
+
module ConnectionAdapters # :nodoc:
|
40
|
+
|
41
|
+
module PostGISAdapter # :nodoc:
|
42
|
+
|
43
|
+
class PostGISDatabaseTasks < ::ActiveRecord::Tasks::PostgreSQLDatabaseTasks # :nodoc:
|
44
|
+
|
45
|
+
|
46
|
+
def initialize(config_)
|
47
|
+
super
|
48
|
+
ensure_installation_configs
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def setup_gis
|
53
|
+
setup_gis_schemas
|
54
|
+
if script_dir
|
55
|
+
setup_gis_from_script_dir
|
56
|
+
elsif extension_names
|
57
|
+
setup_gis_from_extension
|
58
|
+
end
|
59
|
+
if has_su? && (script_dir || extension_names)
|
60
|
+
setup_gis_grant_privileges
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Overridden to set the database owner and call setup_gis
|
66
|
+
|
67
|
+
def create(master_established_=false)
|
68
|
+
establish_master_connection unless master_established_
|
69
|
+
extra_configs_ = {'encoding' => encoding}
|
70
|
+
extra_configs_['owner'] = username if has_su?
|
71
|
+
connection.create_database(configuration['database'], configuration.merge(extra_configs_))
|
72
|
+
establish_connection(configuration) unless master_established_
|
73
|
+
setup_gis
|
74
|
+
rescue ::ActiveRecord::StatementInvalid => error_
|
75
|
+
if /database .* already exists/ === error_.message
|
76
|
+
raise ::ActiveRecord::Tasks::DatabaseAlreadyExists
|
77
|
+
else
|
78
|
+
raise
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Overridden to remove postgis schema
|
84
|
+
|
85
|
+
def structure_dump(filename_)
|
86
|
+
set_psql_env
|
87
|
+
search_path_ = search_path.dup
|
88
|
+
search_path_.delete('postgis')
|
89
|
+
search_path_ = ['public'] if search_path_.length == 0
|
90
|
+
search_path_clause_ = search_path_.map{ |part_| "--schema=#{::Shellwords.escape(part_)}" }.join(' ')
|
91
|
+
command_ = "pg_dump -i -s -x -O -f #{::Shellwords.escape(filename_)} #{search_path_clause_} #{::Shellwords.escape(configuration['database'])}"
|
92
|
+
raise 'Error dumping database' unless ::Kernel.system(command_)
|
93
|
+
::File.open(filename_, "a") { |f_| f_ << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
|
100
|
+
# Overridden to use su_username and su_password
|
101
|
+
|
102
|
+
def establish_master_connection
|
103
|
+
establish_connection(configuration.merge(
|
104
|
+
'database' => 'postgres',
|
105
|
+
'schema_search_path' => 'public',
|
106
|
+
'username' => su_username,
|
107
|
+
'password' => su_password))
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def username
|
112
|
+
@username ||= configuration['username']
|
113
|
+
end
|
114
|
+
|
115
|
+
def quoted_username
|
116
|
+
@quoted_username ||= ::PGconn.quote_ident(username)
|
117
|
+
end
|
118
|
+
|
119
|
+
def password
|
120
|
+
@password ||= configuration['password']
|
121
|
+
end
|
122
|
+
|
123
|
+
def su_username
|
124
|
+
@su_username ||= configuration['su_username'] || username
|
125
|
+
end
|
126
|
+
|
127
|
+
def su_password
|
128
|
+
@su_password ||= configuration['su_password'] || password
|
129
|
+
end
|
130
|
+
|
131
|
+
def has_su?
|
132
|
+
@has_su = configuration.include?('su_username') unless defined?(@has_su)
|
133
|
+
@has_su
|
134
|
+
end
|
135
|
+
|
136
|
+
def search_path
|
137
|
+
@search_path ||= configuration['schema_search_path'].to_s.strip.split(',').map(&:strip)
|
138
|
+
end
|
139
|
+
|
140
|
+
def postgis_schema
|
141
|
+
@postgis_schema ||= search_path.include?('postgis') ? 'postgis' : (search_path.last || 'public')
|
142
|
+
end
|
143
|
+
|
144
|
+
def script_dir
|
145
|
+
@script_dir = configuration['script_dir'] unless defined?(@script_dir)
|
146
|
+
@script_dir
|
147
|
+
end
|
148
|
+
|
149
|
+
def extension_names
|
150
|
+
@extension_names ||= begin
|
151
|
+
ext_ = configuration['postgis_extension']
|
152
|
+
case ext_
|
153
|
+
when ::String
|
154
|
+
ext_.split(',')
|
155
|
+
when ::Array
|
156
|
+
ext_
|
157
|
+
else
|
158
|
+
['postgis']
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def ensure_installation_configs
|
165
|
+
if configuration['setup'] == 'default' && !configuration['script_dir'] && !configuration['postgis_extension']
|
166
|
+
share_dir_ = `pg_config --sharedir`.strip rescue '/usr/share'
|
167
|
+
script_dir_ = ::File.expand_path('contrib/postgis-1.5', share_dir_)
|
168
|
+
control_file_ = ::File.expand_path('extension/postgis.control', share_dir_)
|
169
|
+
if ::File.readable?(control_file_)
|
170
|
+
configuration['postgis_extension'] = 'postgis'
|
171
|
+
elsif ::File.directory?(script_dir_)
|
172
|
+
configuration['script_dir'] = script_dir_
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
def setup_gis_schemas
|
179
|
+
establish_connection(configuration.merge('schema_search_path' => 'public'))
|
180
|
+
auth_ = has_su? ? " AUTHORIZATION #{quoted_username}" : ''
|
181
|
+
search_path.each do |schema_|
|
182
|
+
if schema_.downcase != 'public' && !connection.execute("SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname='#{schema_}'").try(:first)
|
183
|
+
connection.execute("CREATE SCHEMA #{schema_}#{auth_}")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
establish_connection(configuration)
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def setup_gis_from_extension
|
191
|
+
extension_names.each do |extname_|
|
192
|
+
if extname_ == 'postgis_topology'
|
193
|
+
raise ::ArgumentError, "'topology' must be in schema_search_path for postgis_topology" unless search_path.include?('topology')
|
194
|
+
connection.execute("CREATE EXTENSION #{extname_} SCHEMA topology")
|
195
|
+
else
|
196
|
+
connection.execute("CREATE EXTENSION #{extname_} SCHEMA #{postgis_schema}")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
def setup_gis_from_script_dir
|
203
|
+
connection.execute("SET search_path TO #{postgis_schema}")
|
204
|
+
connection.execute(::File.read(::File.expand_path('postgis.sql', script_dir)))
|
205
|
+
connection.execute(::File.read(::File.expand_path('spatial_ref_sys.sql', script_dir)))
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
def setup_gis_grant_privileges
|
210
|
+
connection.execute("GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA #{postgis_schema} TO #{quoted_username}")
|
211
|
+
connection.execute("GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA #{postgis_schema} TO #{quoted_username}")
|
212
|
+
postgis_version_ = connection.execute( "SELECT #{postgis_schema}.postgis_version();" ).first['postgis_version']
|
213
|
+
if postgis_version_ =~ /^2/
|
214
|
+
connection.execute("ALTER VIEW #{postgis_schema}.geometry_columns OWNER TO #{quoted_username}")
|
215
|
+
else
|
216
|
+
connection.execute("ALTER TABLE #{postgis_schema}.geometry_columns OWNER TO #{quoted_username}")
|
217
|
+
end
|
218
|
+
connection.execute("ALTER TABLE #{postgis_schema}.spatial_ref_sys OWNER TO #{quoted_username}")
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
::ActiveRecord::Tasks::DatabaseTasks.register_task(/postgis/, PostGISDatabaseTasks)
|
226
|
+
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|