activerecord-postgis-adapter 2.2.2 → 3.0.0.beta1

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.
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