activerecord-postgis-adapter 2.2.2 → 3.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76d9431bb3c7e39d5fbd758bb5a5235c5bdf2679
4
- data.tar.gz: 00b2d3a6e72cd2c66b93de93a4a50f9d011fdba1
3
+ metadata.gz: 5da85e153ac728aed4dd05c63c31db2b8408db33
4
+ data.tar.gz: e1fb8123e8ac236ff5299d19f691147a510f9d22
5
5
  SHA512:
6
- metadata.gz: 96a805abbd12a4d90038b7896198c12370214881fba6ddd62cee2b062720c575b4f9479e63746fa385363484161c65070be30484cf14d208616c4c1f15aac93f
7
- data.tar.gz: 64f9df06696a296d146d07323b4cd6b7c11797f756447264262d68fd992f487e9ed0a0f03e4696542356da07add81ffc4d8513ec5615fc844bf72a940c628a8c
6
+ metadata.gz: b6af550d08c16c0eff95249fc80e08eb9aabf59e1cfac0c163edd43506ee6c6887b6acba7e7eac4f6ca3f63a6052f4479ba3337c48c4cbdfce88d21cf0874de8
7
+ data.tar.gz: 9bbe835d52ed5d41f91c1d22e58564775f7c200d52ea772a7818c9987456949fbffa7393e81ad20acec16edd6ccae52b1b95363f3403a920e046f810e3451ee6
@@ -17,13 +17,14 @@ require 'active_record/connection_adapters/postgresql_adapter'
17
17
  require 'rgeo/active_record'
18
18
 
19
19
  require 'active_record/connection_adapters/postgis_adapter/version'
20
- require 'active_record/connection_adapters/postgis_adapter/common_adapter_methods'
20
+ require 'active_record/connection_adapters/postgis_adapter/schema_statements'
21
21
  require 'active_record/connection_adapters/postgis_adapter/main_adapter'
22
22
  require 'active_record/connection_adapters/postgis_adapter/spatial_column_info'
23
23
  require 'active_record/connection_adapters/postgis_adapter/spatial_table_definition'
24
24
  require 'active_record/connection_adapters/postgis_adapter/spatial_column'
25
25
  require 'active_record/connection_adapters/postgis_adapter/arel_tosql'
26
26
  require 'active_record/connection_adapters/postgis_adapter/setup'
27
+ require 'active_record/connection_adapters/postgis_adapter/oid/spatial'
27
28
  require 'active_record/connection_adapters/postgis_adapter/create_connection'
28
29
  require 'active_record/connection_adapters/postgis_adapter/postgis_database_tasks'
29
30
 
@@ -4,7 +4,7 @@ module Arel # :nodoc:
4
4
  PostGISSuperclass = if defined?(::ArJdbc::PostgreSQL::BindSubstitution)
5
5
  ::ArJdbc::PostgreSQL::BindSubstitution
6
6
  else
7
- ::Arel::Visitors::PostgreSQL
7
+ PostgreSQL
8
8
  end
9
9
 
10
10
  class PostGIS < PostGISSuperclass # :nodoc:
@@ -13,17 +13,21 @@ module Arel # :nodoc:
13
13
  'st_wkttosql' => 'ST_GeomFromEWKT',
14
14
  }
15
15
 
16
- include ::RGeo::ActiveRecord::SpatialToSql
16
+ include RGeo::ActiveRecord::SpatialToSql
17
17
 
18
18
  def st_func(standard_name)
19
19
  FUNC_MAP[standard_name.downcase] || standard_name
20
20
  end
21
21
 
22
- alias_method :visit_in_spatial_context, :visit
22
+ def visit_String(node, collector)
23
+ collector << "#{st_func('ST_WKTToSQL')}(#{quote(node)})"
24
+ end
23
25
 
24
- end
26
+ def visit_RGeo_ActiveRecord_SpatialNamedFunction(node, collector)
27
+ aggregate(st_func(node.name), node, collector)
28
+ end
25
29
 
26
- VISITORS['postgis'] = ::Arel::Visitors::PostGIS
30
+ end
27
31
 
28
32
  end
29
33
  end
@@ -6,13 +6,18 @@ end
6
6
 
7
7
  module ActiveRecord # :nodoc:
8
8
  module ConnectionHandling # :nodoc:
9
+
9
10
  if(defined?(::RUBY_ENGINE) && ::RUBY_ENGINE == 'jruby')
11
+
10
12
  def postgis_connection(config)
11
13
  config[:adapter_class] = ::ActiveRecord::ConnectionAdapters::PostGISAdapter::MainAdapter
12
14
  postgresql_connection(config)
13
15
  end
16
+
14
17
  alias_method :jdbcpostgis_connection, :postgis_connection
18
+
15
19
  else
20
+
16
21
  # Based on the default <tt>postgresql_connection</tt> definition from ActiveRecord.
17
22
  # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
18
23
  def postgis_connection(config)
@@ -30,8 +35,9 @@ module ActiveRecord # :nodoc:
30
35
 
31
36
  # The postgres drivers don't allow the creation of an unconnected PGconn object,
32
37
  # so just pass a nil connection object for the time being.
33
- ::ActiveRecord::ConnectionAdapters::PostGISAdapter::MainAdapter.new(nil, logger, conn_params, config)
38
+ ConnectionAdapters::PostGISAdapter::MainAdapter.new(nil, logger, conn_params, config)
34
39
  end
40
+
35
41
  end
36
42
 
37
43
  end
@@ -2,213 +2,72 @@ module ActiveRecord # :nodoc:
2
2
  module ConnectionAdapters # :nodoc:
3
3
  module PostGISAdapter # :nodoc:
4
4
  class MainAdapter < PostgreSQLAdapter # :nodoc:
5
+ SPATIAL_COLUMN_OPTIONS =
6
+ {
7
+ geography: { geographic: true },
8
+ geometry: {},
9
+ geometry_collection: {},
10
+ line_string: {},
11
+ multi_line_string: {},
12
+ multi_point: {},
13
+ multi_polygon: {},
14
+ spatial: {},
15
+ st_point: {},
16
+ st_polygon: {},
17
+ }
18
+
19
+ # http://postgis.17.x6.nabble.com/Default-SRID-td5001115.html
20
+ DEFAULT_SRID = 0
21
+
5
22
  def initialize(*args)
6
- # Overridden to change the visitor
7
23
  super
8
- @visitor = ::Arel::Visitors::PostGIS.new(self)
24
+ @visitor = Arel::Visitors::PostGIS.new(self)
9
25
  end
10
26
 
11
- include PostGISAdapter::CommonAdapterMethods
27
+ include PostGISAdapter::SchemaStatements
12
28
 
13
- @@native_database_types = nil
29
+ # def schema_creation
30
+ # PostGISAdapter::SchemaCreation.new self
31
+ # end
14
32
 
15
- def native_database_types
16
- # Overridden to add the :spatial type
17
- @@native_database_types ||= super.merge(
18
- geography: { name: 'geography' },
19
- spatial: { name: 'geometry' },
20
- )
33
+ def set_rgeo_factory_settings(factory_settings)
34
+ @rgeo_factory_settings = factory_settings
21
35
  end
22
36
 
23
- def type_cast(value, column, array_member = false)
24
- if ::RGeo::Feature::Geometry.check_type(value)
25
- ::RGeo::WKRep::WKBGenerator.new(hex_format: true, type_format: :ewkb, emit_ewkb_srid: true).generate(value)
26
- else
27
- super
28
- end
37
+ def adapter_name
38
+ ADAPTER_NAME
29
39
  end
30
40
 
31
- # FULL REPLACEMENT. RE-CHECK ON NEW VERSIONS
32
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
33
- def columns(table_name, name = nil)
34
- column_info = SpatialColumnInfo.new(self, quote_string(table_name.to_s))
35
- # Limit, precision, and scale are all handled by the superclass.
36
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
37
- # JDBC gets true/false in Rails 4, where other platforms get
38
- # 't'/'f' strings.
39
- if(notnull.is_a?(String))
40
- notnull = (notnull == 't')
41
- end
42
-
43
- oid = column_type_map.fetch(oid.to_i, fmod.to_i) { OID::Identity.new }
44
- SpatialColumn.new(@rgeo_factory_settings,
45
- table_name,
46
- column_name,
47
- default,
48
- oid,
49
- type,
50
- !notnull,
51
- column_info.get(column_name, type))
52
- end
41
+ def self.spatial_column_options(key)
42
+ SPATIAL_COLUMN_OPTIONS[key]
53
43
  end
54
44
 
55
- # FULL REPLACEMENT. RE-CHECK ON NEW VERSIONS
56
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
57
- def indexes(table_name, name = nil)
58
- result = query(<<-SQL, 'SCHEMA')
59
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
60
- FROM pg_class t
61
- INNER JOIN pg_index d ON t.oid = d.indrelid
62
- INNER JOIN pg_class i ON d.indexrelid = i.oid
63
- WHERE i.relkind = 'i'
64
- AND d.indisprimary = 'f'
65
- AND t.relname = '#{table_name}'
66
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
67
- ORDER BY i.relname
68
- SQL
69
-
70
- result.map do |row|
71
- index_name = row[0]
72
- unique = row[1] == 't'
73
- indkey = row[2].split(" ")
74
- inddef = row[3]
75
- oid = row[4]
76
-
77
- columns = query(<<-SQL, "SCHEMA")
78
- SELECT a.attnum, a.attname, t.typname
79
- FROM pg_attribute a, pg_type t
80
- WHERE a.attrelid = #{oid}
81
- AND a.attnum IN (#{indkey.join(",")})
82
- AND a.atttypid = t.oid
83
- SQL
84
- columns = columns.inject({}){ |h, r| h[r[0].to_s] = [r[1], r[2]]; h }
85
- column_names = columns.values_at(*indkey).compact.map{ |a| a[0] }
86
-
87
- unless column_names.empty?
88
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
89
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
90
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
91
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
92
- # using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
93
-
94
- spatial = inddef =~ /using\s+gist/i &&
95
- columns.size == 1 &&
96
- %w[geometry geography].include?(columns.values.first[1])
97
-
98
- # IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
99
- ::RGeo::ActiveRecord::SpatialIndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, !!spatial)
100
- end
101
- end.compact
45
+ def postgis_lib_version
46
+ @postgis_lib_version ||= select_value("SELECT PostGIS_Lib_Version()")
102
47
  end
103
48
 
104
- def create_table_definition(name, temporary, options, as = nil)
105
- # Override to create a spatial table definition
106
- if ActiveRecord::VERSION::STRING > '4.1'
107
- PostGISAdapter::TableDefinition.new(native_database_types, name, temporary, options, as, self)
108
- else
109
- PostGISAdapter::TableDefinition.new(native_database_types, name, temporary, options, self)
110
- end
49
+ def default_srid
50
+ DEFAULT_SRID
111
51
  end
112
52
 
113
- def create_table(table_name, options = {}, &block)
114
- table_name = table_name.to_s
115
- # Call super and snag the table definition
116
- table_definition = nil
117
- super(table_name, options) do |td|
118
- block.call(td) if block
119
- table_definition = td
120
- end
121
- table_definition.non_geographic_spatial_columns.each do |col|
122
- options = {
123
- default: col.default,
124
- has_m: col.has_m?,
125
- has_z: col.has_z?,
126
- null: col.null,
127
- srid: col.srid,
128
- type: col.spatial_type,
129
- }
130
- column_name = col.name.to_s
131
- type = col.spatial_type
132
-
133
- add_spatial_column(table_name, column_name, type, options)
134
- end
53
+ def srs_database_columns
54
+ {
55
+ auth_name_column: 'auth_name',
56
+ auth_srid_column: 'auth_srid',
57
+ proj4text_column: 'proj4text',
58
+ srtext_column: 'srtext',
59
+ }
135
60
  end
136
61
 
137
- def add_column(table_name, column_name, type, options = {})
138
- table_name = table_name.to_s
139
- column_name = column_name.to_s
140
- if (info = spatial_column_constructor(type.to_sym))
141
- options[:info] = info
142
- add_spatial_column(table_name, column_name, type, options)
62
+ def quote(value, column = nil)
63
+ if RGeo::Feature::Geometry.check_type(value)
64
+ "'#{ RGeo::WKRep::WKBGenerator.new(hex_format: true, type_format: :ewkb, emit_ewkb_srid: true).generate(value) }'"
65
+ elsif value.is_a?(RGeo::Cartesian::BoundingBox)
66
+ "'#{ value.min_x },#{ value.min_y },#{ value.max_x },#{ value.max_y }'::box"
143
67
  else
144
68
  super
145
69
  end
146
70
  end
147
-
148
- def remove_column(table_name, column_name, type = nil, options = {})
149
- table_name = table_name.to_s
150
- column_name = column_name.to_s
151
- spatial_info = spatial_column_info(table_name)
152
- if spatial_info.include?(column_name)
153
- execute("SELECT DropGeometryColumn('#{quote_string(table_name)}','#{quote_string(column_name)}')")
154
- else
155
- super
156
- end
157
- end
158
-
159
- # FULL REPLACEMENT. RE-CHECK ON NEW VERSIONS
160
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
161
- def add_index(table_name, column_name, options = {})
162
- # We have to fully replace to add the gist clause.
163
- options ||= {} # in case nil explicitly passed
164
- gist = options.delete(:spatial)
165
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
166
- index_using = 'USING GIST' if gist
167
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
168
- end
169
-
170
- def spatial_column_info(table_name)
171
- SpatialColumnInfo.new(self, quote_string(table_name.to_s)).all
172
- end
173
-
174
- private
175
-
176
- def add_spatial_column(table_name, column_name, type, options)
177
- limit = options[:limit]
178
- info = options[:info] || {}
179
- options.merge!(limit) if limit.is_a?(::Hash)
180
- type = (options[:type] || info[:type] || type).to_s.gsub('_', '').upcase
181
- has_z = options[:has_z]
182
- has_m = options[:has_m]
183
- srid = (options[:srid] || PostGISAdapter::DEFAULT_SRID).to_i
184
- if options[:geographic]
185
- type << 'Z' if has_z
186
- type << 'M' if has_m
187
- execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} GEOGRAPHY(#{type},#{srid})")
188
- change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
189
- change_column_null(table_name, column_name, false, options[:default]) if options[:null] == false
190
- else
191
- type = "#{type}M" if has_m && !has_z
192
- dimensions = set_dimensions(has_m, has_z)
193
- execute("SELECT AddGeometryColumn('#{quote_string(table_name)}', '#{quote_string(column_name)}', #{srid}, '#{quote_string(type)}', #{dimensions})")
194
- change_column_null(table_name, column_name, false, options[:default]) if options[:null] == false
195
- end
196
- end
197
-
198
- def column_type_map
199
- if defined?(type_map) # ActiveRecord 4.1+
200
- type_map
201
- else # ActiveRecord 4.0.x
202
- OID::TYPE_MAP
203
- end
204
- end
205
-
206
- def set_dimensions(has_m, has_z)
207
- dimensions = 2
208
- dimensions += 1 if has_z
209
- dimensions += 1 if has_m
210
- dimensions
211
- end
212
71
  end
213
72
  end
214
73
  end
@@ -0,0 +1,119 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module PostGISAdapter
4
+ module OID
5
+ class Spatial < Type::Value
6
+ # sql_type is a string that comes from the database definition
7
+ # examples:
8
+ # "geometry(Point,4326)"
9
+ # "geography(Point,4326)"
10
+ # "geometry(Polygon,4326) NOT NULL"
11
+ # "geometry(Geography,4326)"
12
+ def initialize(oid, sql_type)
13
+ @geo_type, @srid, @has_z, @has_m = self.class.parse_sql_type(sql_type)
14
+ @factory_generator = RGeo::Geographic.spherical_factory(srid: 4326) if oid =~ /geography/
15
+ end
16
+
17
+ # sql_type: geometry, geometry(Point), geometry(Point,4326), ...
18
+ #
19
+ # returns [geo_type, srid, has_z, has_m]
20
+ # geo_type: geography, geometry, point, line_string, polygon, ...
21
+ # srid: 1234
22
+ # has_z: false
23
+ # has_m: false
24
+ def self.parse_sql_type(sql_type)
25
+ geo_type, srid, has_z, has_m = nil, 0, false, false
26
+
27
+ if (sql_type =~ /[geography,geography]\((.*)\)$/i)
28
+ # geometry(Point,4326)
29
+ params = $1.split(',')
30
+ if params.size > 1
31
+ if params.first =~ /([a-z]+[^zm])(z?)(m?)/i
32
+ has_z = $2.length > 0
33
+ has_m = $3.length > 0
34
+ geo_type = $1
35
+ end
36
+ if params.last =~ /(\d+)/
37
+ srid = $1.to_i
38
+ end
39
+ else
40
+ # geometry(Point)
41
+ geo_type = params[0]
42
+ end
43
+ else
44
+ # geometry
45
+ geo_type = sql_type
46
+ end
47
+ [geo_type, srid, has_z, has_m]
48
+ end
49
+
50
+ def factory_generator
51
+ @factory_generator
52
+ end
53
+
54
+ def geographic?
55
+ !!factory_generator
56
+ end
57
+
58
+ def spatial?
59
+ true
60
+ end
61
+
62
+ def type
63
+ geographic? ? :geography : :geometry
64
+ end
65
+
66
+ # support setting an RGeo object or a WKT string
67
+ def type_cast_for_database(value)
68
+ return if value.nil?
69
+ geo_value = type_cast(value)
70
+
71
+ # TODO - only valid types should be allowed
72
+ # e.g. linestring is not valid for point column
73
+ # raise "maybe should raise" unless RGeo::Feature::Geometry.check_type(geo_value)
74
+
75
+ RGeo::WKRep::WKBGenerator.new(hex_format: true, type_format: :ewkb, emit_ewkb_srid: true)
76
+ .generate(geo_value)
77
+ end
78
+
79
+ private
80
+
81
+ def type_cast(value)
82
+ return if value.nil?
83
+ String === value ? parse_wkt(value) : value
84
+ end
85
+
86
+ def cast_value(value)
87
+ return if value.nil?
88
+ RGeo::WKRep::WKBParser.new(@factory_generator, support_ewkb: true).parse(value)
89
+ rescue RGeo::Error::ParseError
90
+ puts "\ncast failed!!\n\n"
91
+ nil
92
+ end
93
+
94
+ # convert WKT string into RGeo object
95
+ def parse_wkt(string)
96
+ # factory = factory_settings.get_column_factory(table_name, column, constraints)
97
+ factory = @factory_generator || RGeo::ActiveRecord::RGeoFactorySettings.new
98
+ wkt_parser(factory, string).parse(string)
99
+ rescue RGeo::Error::ParseError
100
+ nil
101
+ end
102
+
103
+ def binary?(string)
104
+ string[0] == "\x00" || string[0] == "\x01" || string[0, 4] =~ /[0-9a-fA-F]{4}/
105
+ end
106
+
107
+ def wkt_parser(factory, string)
108
+ if binary?(string)
109
+ RGeo::WKRep::WKBParser.new(factory, support_ewkb: true, default_srid: @srid)
110
+ else
111
+ RGeo::WKRep::WKTParser.new(factory, support_ewkt: true, default_srid: @srid)
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end