activerecord-mysql2rgeo-adapter 7.2.0 → 7.3.2
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 +4 -4
- data/lib/active_record/connection_adapters/mysql2rgeo/arel_tosql.rb +31 -16
- data/lib/active_record/connection_adapters/mysql2rgeo/column_methods.rb +6 -0
- data/lib/active_record/connection_adapters/mysql2rgeo/schema_creation.rb +24 -5
- data/lib/active_record/connection_adapters/mysql2rgeo/schema_statements.rb +133 -7
- data/lib/active_record/connection_adapters/mysql2rgeo/spatial_column.rb +41 -10
- data/lib/active_record/connection_adapters/mysql2rgeo/spatial_column_info.rb +8 -3
- data/lib/active_record/connection_adapters/mysql2rgeo/spatial_table_definition.rb +108 -6
- data/lib/active_record/connection_adapters/mysql2rgeo/version.rb +1 -1
- data/lib/active_record/connection_adapters/mysql2rgeo_adapter.rb +99 -30
- data/lib/active_record/type/spatial.rb +68 -17
- data/lib/activerecord-mysql2rgeo-adapter.rb +0 -2
- metadata +28 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 816dbf40bdc492a42c20ded6aef4f247a76dedbbdd73bc6d705a400f8b8cfdc7
|
|
4
|
+
data.tar.gz: 4a04ba14c239948df20572c8b29a13f4c6296dfc11edb3beba35b1b76c26124f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dce6e73f2b2f93d1cdd543e6c30e305adf20135faec080f0361a00f1b7f95d34556ac51e4d98fdaa41c565674db4444353529b06b413ac708cf2b0c6750600e
|
|
7
|
+
data.tar.gz: 2d697dc4767e38740316346fd727dcf352ab1bc41340b4a45ec4c15a3f73f0e1742ba3dc70c0af231aca6f5d091e5d87f9c17b2d6f96493625bc00a3c275ddbd
|
|
@@ -9,12 +9,10 @@ module Arel # :nodoc:
|
|
|
9
9
|
MySQL
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
class Mysql2Rgeo < MySQLSuperclass
|
|
12
|
+
class Mysql2Rgeo < MySQLSuperclass # :nodoc:
|
|
13
13
|
include RGeo::ActiveRecord::SpatialToSql
|
|
14
14
|
|
|
15
|
-
if ::Arel::Visitors.const_defined?(:BindVisitor)
|
|
16
|
-
include ::Arel::Visitors::BindVisitor
|
|
17
|
-
end
|
|
15
|
+
include ::Arel::Visitors::BindVisitor if ::Arel::Visitors.const_defined?(:BindVisitor)
|
|
18
16
|
|
|
19
17
|
FUNC_MAP = {
|
|
20
18
|
"st_wkttosql" => "ST_GeomFromText",
|
|
@@ -28,12 +26,10 @@ module Arel # :nodoc:
|
|
|
28
26
|
|
|
29
27
|
def visit_String(node, collector)
|
|
30
28
|
node, srid = Mysql2Rgeo.parse_node(node)
|
|
31
|
-
collector <<
|
|
32
|
-
"#{st_func('ST_WKTToSQL')}(#{quote(node)})"
|
|
33
|
-
else
|
|
34
|
-
"#{st_func('ST_WKTToSQL')}(#{quote(node)}, #{srid})"
|
|
35
|
-
end
|
|
29
|
+
collector << spatial_constant_sql(node, srid)
|
|
36
30
|
end
|
|
31
|
+
alias visit_RGeo_Feature_Instance visit_String
|
|
32
|
+
alias visit_RGeo_Cartesian_BoundingBox visit_String
|
|
37
33
|
|
|
38
34
|
def visit_RGeo_ActiveRecord_SpatialNamedFunction(node, collector)
|
|
39
35
|
aggregate(st_func(node.name), node, collector)
|
|
@@ -43,21 +39,28 @@ module Arel # :nodoc:
|
|
|
43
39
|
case node
|
|
44
40
|
when String
|
|
45
41
|
node, srid = Mysql2Rgeo.parse_node(node)
|
|
46
|
-
collector <<
|
|
47
|
-
"#{st_func('ST_WKTToSQL')}(#{quote(node)})"
|
|
48
|
-
else
|
|
49
|
-
"#{st_func('ST_WKTToSQL')}(#{quote(node)}, #{srid})"
|
|
50
|
-
end
|
|
42
|
+
collector << spatial_constant_sql(node, srid)
|
|
51
43
|
when RGeo::Feature::Instance
|
|
52
|
-
|
|
44
|
+
visit_RGeo_Feature_Instance(node, collector)
|
|
53
45
|
when RGeo::Cartesian::BoundingBox
|
|
54
|
-
|
|
46
|
+
visit_RGeo_Cartesian_BoundingBox(node, collector)
|
|
55
47
|
else
|
|
56
48
|
visit(node, collector)
|
|
57
49
|
end
|
|
58
50
|
end
|
|
59
51
|
|
|
60
52
|
def self.parse_node(node)
|
|
53
|
+
if node.is_a?(RGeo::Feature::Instance)
|
|
54
|
+
wkt = RGeo::WKRep::WKTGenerator.new(tag_format: :wkt11, emit_ewkt_srid: false).generate(node)
|
|
55
|
+
return [wkt, node.srid]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if node.is_a?(RGeo::Cartesian::BoundingBox)
|
|
59
|
+
geometry = node.to_geometry
|
|
60
|
+
wkt = RGeo::WKRep::WKTGenerator.new(tag_format: :wkt11, emit_ewkt_srid: false).generate(geometry)
|
|
61
|
+
return [wkt, geometry.srid]
|
|
62
|
+
end
|
|
63
|
+
|
|
61
64
|
value, srid = nil, 0
|
|
62
65
|
if node =~ /.*;.*$/i
|
|
63
66
|
params = Regexp.last_match(0).split(";")
|
|
@@ -76,6 +79,18 @@ module Arel # :nodoc:
|
|
|
76
79
|
end
|
|
77
80
|
[value, srid]
|
|
78
81
|
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def spatial_constant_sql(node, srid)
|
|
86
|
+
if srid == 0
|
|
87
|
+
"#{st_func('ST_WKTToSQL')}(#{quote(node)})"
|
|
88
|
+
elsif srid == 4326
|
|
89
|
+
"#{st_func('ST_WKTToSQL')}(#{quote(node)}, #{srid}, 'axis-order=long-lat')"
|
|
90
|
+
else
|
|
91
|
+
"#{st_func('ST_WKTToSQL')}(#{quote(node)}, #{srid})"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
79
94
|
end
|
|
80
95
|
end
|
|
81
96
|
end
|
|
@@ -14,6 +14,10 @@ module ActiveRecord
|
|
|
14
14
|
column(name, :geometry, **options)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def geography(name, options = {})
|
|
18
|
+
column(name, :geometry, geographic: true, **options)
|
|
19
|
+
end
|
|
20
|
+
|
|
17
21
|
def geometry_collection(name, options = {})
|
|
18
22
|
column(name, :geometrycollection, **options)
|
|
19
23
|
end
|
|
@@ -42,10 +46,12 @@ module ActiveRecord
|
|
|
42
46
|
def point(name, options = {})
|
|
43
47
|
column(name, :point, **options)
|
|
44
48
|
end
|
|
49
|
+
alias st_point point
|
|
45
50
|
|
|
46
51
|
def polygon(name, options = {})
|
|
47
52
|
column(name, :polygon, **options)
|
|
48
53
|
end
|
|
54
|
+
alias st_polygon polygon
|
|
49
55
|
end
|
|
50
56
|
end
|
|
51
57
|
|
|
@@ -6,13 +6,32 @@ module ActiveRecord
|
|
|
6
6
|
class SchemaCreation < MySQL::SchemaCreation # :nodoc:
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
def visit_IndexDefinition(o, create = false)
|
|
10
|
+
if o.using&.to_sym == :gist
|
|
11
|
+
sql = create ? ["CREATE SPATIAL INDEX"] : ["SPATIAL INDEX"]
|
|
12
|
+
sql << quote_column_name(o.name)
|
|
13
|
+
sql << "ON #{quote_table_name(o.table)}" if create
|
|
14
|
+
sql << "(#{quoted_columns(o)})"
|
|
15
|
+
return sql.join(" ")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
end
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
def add_column_options!(sql, options)
|
|
22
|
+
if options[:srid]
|
|
23
|
+
sql << " /*!80003 SRID #{options[:srid]} */"
|
|
24
|
+
options = options.except(:srid)
|
|
15
25
|
end
|
|
26
|
+
|
|
27
|
+
options = options.except(:default) if options_include_default?(options) && spatial_column_definition?(options[:column])
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def spatial_column_definition?(column)
|
|
33
|
+
column.respond_to?(:type) && column.type && @conn.class.spatial_column_options(column.type.to_sym)
|
|
34
|
+
end
|
|
16
35
|
end
|
|
17
36
|
end
|
|
18
37
|
end
|
|
@@ -7,19 +7,44 @@ module ActiveRecord
|
|
|
7
7
|
# super: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
|
|
8
8
|
|
|
9
9
|
# override
|
|
10
|
-
def indexes(table_name)
|
|
10
|
+
def indexes(table_name) # :nodoc:
|
|
11
11
|
indexes = super
|
|
12
12
|
# HACK(aleks, 06/15/18): MySQL 5 does not support prefix lengths for spatial indexes
|
|
13
13
|
# https://dev.mysql.com/doc/refman/5.6/en/create-index.html
|
|
14
14
|
indexes.select do |idx|
|
|
15
15
|
idx.type == :spatial
|
|
16
|
-
end.each
|
|
16
|
+
end.each do |idx|
|
|
17
|
+
if idx.is_a?(Struct)
|
|
18
|
+
idx.lengths = {}
|
|
19
|
+
idx.using = :gist if idx.members.include?(:using)
|
|
20
|
+
else
|
|
21
|
+
idx.instance_variable_set(:@lengths, {})
|
|
22
|
+
idx.instance_variable_set(:@using, :gist)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
17
25
|
indexes
|
|
18
26
|
end
|
|
19
27
|
|
|
28
|
+
def add_index(table_name, column_name, **options) # :nodoc:
|
|
29
|
+
if options[:using]&.to_sym == :gist
|
|
30
|
+
Array(column_name).each do |name|
|
|
31
|
+
column = columns(table_name).find { |col| col.name == name.to_s }
|
|
32
|
+
next unless column&.spatial? && column.null
|
|
33
|
+
|
|
34
|
+
column_options = column.limit.is_a?(Hash) ? column.limit.symbolize_keys.except(:type) : {}
|
|
35
|
+
column_options[:comment] = column.comment if column.comment.present?
|
|
36
|
+
change_column(table_name, name, column.limit[:type].to_sym, **column_options, null: false)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
|
|
20
43
|
# override
|
|
21
44
|
def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
|
|
22
|
-
|
|
45
|
+
return "geometry(#{limit})" if type.to_sym == :geometry && limit.is_a?(String)
|
|
46
|
+
|
|
47
|
+
if RGeo::ActiveRecord.geometric_type_from_name(type.to_s.delete("_"))
|
|
23
48
|
type = limit[:type] || type if limit.is_a?(::Hash)
|
|
24
49
|
type = type.to_s.delete("_").upcase
|
|
25
50
|
end
|
|
@@ -38,20 +63,67 @@ module ActiveRecord
|
|
|
38
63
|
Mysql2Rgeo::TableDefinition.new(self, *args, **options)
|
|
39
64
|
end
|
|
40
65
|
|
|
66
|
+
def update_table_definition(table_name, base)
|
|
67
|
+
Mysql2Rgeo::Table.new(table_name, base)
|
|
68
|
+
end
|
|
69
|
+
|
|
41
70
|
# override
|
|
42
71
|
def new_column_from_field(table_name, field, _definitions)
|
|
43
72
|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
|
|
44
73
|
default, default_function = field[:Default], nil
|
|
74
|
+
metadata = Mysql2Rgeo::ColumnDefinitionUtils.extract_metadata(field[:Comment])
|
|
75
|
+
comment = Mysql2Rgeo::ColumnDefinitionUtils.strip_metadata_comment(field[:Comment])
|
|
76
|
+
default = metadata[:default_hex] if default.nil? && metadata[:default_hex].present?
|
|
45
77
|
|
|
46
78
|
if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
|
|
47
79
|
default, default_function = nil, default
|
|
48
80
|
elsif type_metadata.extra == "DEFAULT_GENERATED"
|
|
49
|
-
default
|
|
50
|
-
|
|
81
|
+
if default == "NULL" && metadata[:default_hex].present?
|
|
82
|
+
default = metadata[:default_hex]
|
|
83
|
+
elsif default == "NULL"
|
|
84
|
+
default = generated_default_for(table_name, field[:Field])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if default&.match?(/\Ast_geomfromtext\(/i)
|
|
88
|
+
default = "(#{default})" unless default.start_with?("(")
|
|
89
|
+
default, default_function = nil, default
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if type_metadata.extra.to_s.match?(/(?:VIRTUAL|STORED|PERSISTENT)\s+GENERATED/i)
|
|
94
|
+
default_function = generation_expression_for(table_name, field[:Field])
|
|
51
95
|
end
|
|
52
96
|
|
|
53
97
|
# {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
|
|
54
98
|
spatial = spatial_column_info(table_name).get(field[:Field], type_metadata.sql_type)
|
|
99
|
+
if spatial
|
|
100
|
+
spatial[:has_z] ||= metadata[:has_z]
|
|
101
|
+
spatial[:has_m] ||= metadata[:has_m]
|
|
102
|
+
spatial[:geographic] ||= metadata[:geographic]
|
|
103
|
+
geo_type = spatial[:type].camelize
|
|
104
|
+
geo_type = "#{geo_type}Z" if spatial[:has_z]
|
|
105
|
+
geo_type = "#{geo_type}M" if spatial[:has_m]
|
|
106
|
+
sql_type = if spatial[:geographic]
|
|
107
|
+
"geography(#{geo_type},#{spatial[:srid]})"
|
|
108
|
+
else
|
|
109
|
+
"geometry(#{geo_type},#{spatial[:srid]})"
|
|
110
|
+
end
|
|
111
|
+
type_metadata = MySQL::TypeMetadata.new(
|
|
112
|
+
ConnectionAdapters::SqlTypeMetadata.new(
|
|
113
|
+
sql_type: sql_type,
|
|
114
|
+
type: :geometry,
|
|
115
|
+
limit: nil,
|
|
116
|
+
precision: type_metadata.precision,
|
|
117
|
+
scale: type_metadata.scale
|
|
118
|
+
),
|
|
119
|
+
extra: type_metadata.extra
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if default_function&.match?(/\A\(?st_geomfromtext\(/i)
|
|
123
|
+
default = extract_spatial_default_hex(default_function, spatial)
|
|
124
|
+
default_function = nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
55
127
|
|
|
56
128
|
SpatialColumn.new(
|
|
57
129
|
field[:Field],
|
|
@@ -60,8 +132,9 @@ module ActiveRecord
|
|
|
60
132
|
field[:Null] == "YES",
|
|
61
133
|
default_function,
|
|
62
134
|
collation: field[:Collation],
|
|
63
|
-
comment:
|
|
64
|
-
spatial: spatial
|
|
135
|
+
comment: comment,
|
|
136
|
+
spatial: spatial,
|
|
137
|
+
array: metadata[:array]
|
|
65
138
|
)
|
|
66
139
|
end
|
|
67
140
|
|
|
@@ -70,6 +143,59 @@ module ActiveRecord
|
|
|
70
143
|
@spatial_column_info ||= {}
|
|
71
144
|
@spatial_column_info[table_name.to_sym] = SpatialColumnInfo.new(self, table_name.to_s)
|
|
72
145
|
end
|
|
146
|
+
|
|
147
|
+
def extract_spatial_default_hex(default_function, spatial)
|
|
148
|
+
default_sql = default_function.delete_prefix("(").delete_suffix(")").delete("\\")
|
|
149
|
+
match = default_sql.match(/\Ast_geomfromtext\(_utf8mb4'(.+?)',\s*(\d+)\)\z/i)
|
|
150
|
+
return unless match
|
|
151
|
+
|
|
152
|
+
wkt = match[1]
|
|
153
|
+
srid = match[2].to_i
|
|
154
|
+
type = ActiveRecord::Type::Spatial.new(
|
|
155
|
+
spatial[:geographic] ? "geography" : "geometry",
|
|
156
|
+
geo_type: ActiveRecord::Type::Spatial.normalize_geo_type(spatial[:type]),
|
|
157
|
+
srid: srid,
|
|
158
|
+
geographic: spatial[:geographic],
|
|
159
|
+
has_z: spatial[:has_z],
|
|
160
|
+
has_m: spatial[:has_m]
|
|
161
|
+
)
|
|
162
|
+
geometry = type.serialize(wkt)
|
|
163
|
+
return unless geometry
|
|
164
|
+
|
|
165
|
+
geometry = RGeo::Feature.cast(geometry, factory: type.send(:spatial_factory), project: true) if spatial[:geographic]
|
|
166
|
+
|
|
167
|
+
wkb = RGeo::WKRep::WKBGenerator.new(
|
|
168
|
+
hex_format: true,
|
|
169
|
+
little_endian: true,
|
|
170
|
+
type_format: :wkb11,
|
|
171
|
+
emit_ewkb_srid: false
|
|
172
|
+
).generate(geometry)
|
|
173
|
+
return wkb.upcase unless spatial[:geographic]
|
|
174
|
+
|
|
175
|
+
srid_hex = [spatial[:srid].to_i].pack("V").unpack1("H*")
|
|
176
|
+
"#{srid_hex}#{wkb}".upcase
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def generation_expression_for(table_name, column_name)
|
|
180
|
+
query_value(<<~SQL)&.gsub("`", "")
|
|
181
|
+
SELECT generation_expression
|
|
182
|
+
FROM information_schema.columns
|
|
183
|
+
WHERE table_schema = DATABASE()
|
|
184
|
+
AND table_name = #{quote(table_name)}
|
|
185
|
+
AND column_name = #{quote(column_name)}
|
|
186
|
+
SQL
|
|
187
|
+
&.gsub(/st_buffer\(([^,]+),\s*(\d+)\)/i, 'st_buffer(\1, (\2)::double precision)')
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def generated_default_for(table_name, column_name)
|
|
191
|
+
query_value(<<~SQL)
|
|
192
|
+
SELECT column_default
|
|
193
|
+
FROM information_schema.columns
|
|
194
|
+
WHERE table_schema = DATABASE()
|
|
195
|
+
AND table_name = #{quote(table_name)}
|
|
196
|
+
AND column_name = #{quote(column_name)}
|
|
197
|
+
SQL
|
|
198
|
+
end
|
|
73
199
|
end
|
|
74
200
|
end
|
|
75
201
|
end
|
|
@@ -4,12 +4,17 @@ module ActiveRecord # :nodoc:
|
|
|
4
4
|
module ConnectionAdapters # :nodoc:
|
|
5
5
|
module Mysql2Rgeo # :nodoc:
|
|
6
6
|
class SpatialColumn < ConnectionAdapters::MySQL::Column # :nodoc:
|
|
7
|
-
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil,
|
|
7
|
+
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil,
|
|
8
|
+
spatial: nil, array: false, **)
|
|
8
9
|
@sql_type_metadata = sql_type_metadata
|
|
10
|
+
@array = array
|
|
9
11
|
if spatial
|
|
10
12
|
# This case comes from an entry in the geometry_columns table
|
|
11
13
|
set_geometric_type_from_name(spatial[:type])
|
|
12
14
|
@srid = spatial[:srid].to_i
|
|
15
|
+
@has_z = spatial[:has_z]
|
|
16
|
+
@has_m = spatial[:has_m]
|
|
17
|
+
@geographic = spatial[:geographic]
|
|
13
18
|
elsif sql_type =~ /geometry|point|linestring|polygon/i
|
|
14
19
|
build_from_sql_type(sql_type_metadata.sql_type)
|
|
15
20
|
elsif sql_type_metadata.sql_type =~ /geometry|point|linestring|polygon/i
|
|
@@ -18,25 +23,32 @@ module ActiveRecord # :nodoc:
|
|
|
18
23
|
build_from_sql_type(sql_type_metadata.sql_type)
|
|
19
24
|
end
|
|
20
25
|
super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
return unless spatial?
|
|
27
|
+
return unless @srid
|
|
28
|
+
|
|
29
|
+
@limit = { type: limit_type_name, srid: @srid }
|
|
30
|
+
@limit[:geographic] = true if geographic?
|
|
31
|
+
@limit[:has_z] = true if has_z?
|
|
32
|
+
@limit[:has_m] = true if has_m?
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
attr_reader :geometric_type, :srid
|
|
29
36
|
|
|
37
|
+
def array
|
|
38
|
+
@array || false
|
|
39
|
+
end
|
|
40
|
+
alias array? array
|
|
41
|
+
|
|
30
42
|
def has_z
|
|
31
|
-
false
|
|
43
|
+
spatial? ? (@has_z || false) : nil
|
|
32
44
|
end
|
|
33
45
|
|
|
34
46
|
def has_m
|
|
35
|
-
false
|
|
47
|
+
spatial? ? (@has_m || false) : nil
|
|
36
48
|
end
|
|
37
49
|
|
|
38
50
|
def geographic
|
|
39
|
-
false
|
|
51
|
+
spatial? ? (@geographic || false) : nil
|
|
40
52
|
end
|
|
41
53
|
|
|
42
54
|
alias geographic? geographic
|
|
@@ -59,12 +71,31 @@ module ActiveRecord # :nodoc:
|
|
|
59
71
|
|
|
60
72
|
def set_geometric_type_from_name(name)
|
|
61
73
|
@geometric_type = RGeo::ActiveRecord.geometric_type_from_name(name) || RGeo::Feature::Geometry
|
|
74
|
+
@geo_type_name = ActiveRecord::Type::Spatial.normalize_geo_type(name)
|
|
62
75
|
end
|
|
63
76
|
|
|
64
77
|
def build_from_sql_type(sql_type)
|
|
65
|
-
geo_type, @srid = Type::Spatial.parse_sql_type(sql_type)
|
|
78
|
+
geo_type, @srid, @has_z, @has_m, @geographic = Type::Spatial.parse_sql_type(sql_type)
|
|
66
79
|
set_geometric_type_from_name(geo_type)
|
|
67
80
|
end
|
|
81
|
+
|
|
82
|
+
def limit_type_name
|
|
83
|
+
type_name = @geo_type_name || geometric_type.type_name.underscore
|
|
84
|
+
case type_name
|
|
85
|
+
when "point", "polygon"
|
|
86
|
+
"st_#{type_name}"
|
|
87
|
+
when "linestring"
|
|
88
|
+
"line_string"
|
|
89
|
+
when "multilinestring"
|
|
90
|
+
"multi_line_string"
|
|
91
|
+
when "multipoint"
|
|
92
|
+
"multi_point"
|
|
93
|
+
when "multipolygon"
|
|
94
|
+
"multi_polygon"
|
|
95
|
+
else
|
|
96
|
+
type_name
|
|
97
|
+
end
|
|
98
|
+
end
|
|
68
99
|
end
|
|
69
100
|
end
|
|
70
101
|
end
|
|
@@ -13,11 +13,11 @@ module ActiveRecord # :nodoc:
|
|
|
13
13
|
def all
|
|
14
14
|
info = if @adapter.supports_expression_index?
|
|
15
15
|
@adapter.query(
|
|
16
|
-
"SELECT column_name, srs_id, column_type FROM INFORMATION_SCHEMA.Columns WHERE table_name='#{@table_name}'"
|
|
16
|
+
"SELECT column_name, srs_id, column_type, column_comment FROM INFORMATION_SCHEMA.Columns WHERE table_schema = DATABASE() AND table_name='#{@table_name}'"
|
|
17
17
|
)
|
|
18
18
|
else
|
|
19
19
|
@adapter.query(
|
|
20
|
-
"SELECT column_name, 0, column_type FROM INFORMATION_SCHEMA.Columns WHERE table_name='#{@table_name}'"
|
|
20
|
+
"SELECT column_name, 0, column_type, column_comment FROM INFORMATION_SCHEMA.Columns WHERE table_schema = DATABASE() AND table_name='#{@table_name}'"
|
|
21
21
|
)
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -25,11 +25,16 @@ module ActiveRecord # :nodoc:
|
|
|
25
25
|
info.each do |row|
|
|
26
26
|
name = row[0]
|
|
27
27
|
type = row[2]
|
|
28
|
-
|
|
28
|
+
column_comment = row[3].to_s
|
|
29
|
+
has_z = type.sub!(/z$/i, "").present?
|
|
30
|
+
has_m = type.sub!(/m$/i, "").present?
|
|
29
31
|
result[name] = {
|
|
30
32
|
name: name,
|
|
31
33
|
srid: row[1].to_i,
|
|
32
34
|
type: type,
|
|
35
|
+
has_z: has_z,
|
|
36
|
+
has_m: has_m,
|
|
37
|
+
geographic: column_comment.include?("mysql2rgeo:geographic"),
|
|
33
38
|
}
|
|
34
39
|
end
|
|
35
40
|
result
|
|
@@ -5,38 +5,140 @@ module ActiveRecord # :nodoc:
|
|
|
5
5
|
module Mysql2Rgeo # :nodoc:
|
|
6
6
|
class TableDefinition < MySQL::TableDefinition # :nodoc:
|
|
7
7
|
include ColumnMethods
|
|
8
|
+
|
|
8
9
|
# super: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
|
|
9
10
|
def new_column_definition(name, type, **options)
|
|
10
|
-
|
|
11
|
+
spatial_type = type.to_sym == :virtual ? options[:type]&.to_sym : type.to_sym
|
|
12
|
+
|
|
13
|
+
if spatial_type && (info = Mysql2RgeoAdapter.spatial_column_options(spatial_type))
|
|
11
14
|
if (limit = options.delete(:limit)) && limit.is_a?(::Hash)
|
|
12
15
|
options.merge!(limit)
|
|
13
16
|
end
|
|
14
17
|
|
|
15
|
-
geo_type =
|
|
18
|
+
geo_type = if type.to_sym == :virtual
|
|
19
|
+
"GEOMETRY"
|
|
20
|
+
else
|
|
21
|
+
ColumnDefinitionUtils.geo_type(options[:type] || spatial_type || info[:type])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if type.to_sym == :virtual
|
|
25
|
+
options.delete(:srid)
|
|
26
|
+
else
|
|
27
|
+
options[:srid] ||= ColumnDefinitionUtils.default_srid(options)
|
|
28
|
+
options[:comment] = ColumnDefinitionUtils.add_metadata_comment(
|
|
29
|
+
options[:comment],
|
|
30
|
+
geographic: options[:geographic],
|
|
31
|
+
has_m: options[:has_m],
|
|
32
|
+
has_z: options[:has_z],
|
|
33
|
+
array: options[:array],
|
|
34
|
+
default: options[:default],
|
|
35
|
+
srid: options[:srid],
|
|
36
|
+
geo_type: geo_type
|
|
37
|
+
)
|
|
38
|
+
end
|
|
16
39
|
|
|
17
40
|
options[:spatial_type] = geo_type
|
|
18
|
-
column =
|
|
41
|
+
column = if type.to_sym == :virtual
|
|
42
|
+
super(name, type, **options.merge(type: geo_type.downcase.to_sym))
|
|
43
|
+
else
|
|
44
|
+
super(name, geo_type.downcase.to_sym, **options)
|
|
45
|
+
end
|
|
19
46
|
else
|
|
20
|
-
|
|
47
|
+
if options[:array]
|
|
48
|
+
options[:comment] =
|
|
49
|
+
ColumnDefinitionUtils.add_metadata_comment(options[:comment], array: options[:array])
|
|
50
|
+
end
|
|
51
|
+
column = super
|
|
21
52
|
end
|
|
22
53
|
|
|
23
54
|
column
|
|
24
55
|
end
|
|
25
56
|
|
|
26
57
|
def valid_column_definition_options
|
|
27
|
-
super + %i[geographic has_m spatial_type srid]
|
|
58
|
+
super + %i[array geographic has_m has_z spatial_type srid]
|
|
28
59
|
end
|
|
29
60
|
end
|
|
30
61
|
|
|
62
|
+
class Table < MySQL::Table # :nodoc:
|
|
63
|
+
include ColumnMethods
|
|
64
|
+
end
|
|
65
|
+
|
|
31
66
|
module ColumnDefinitionUtils
|
|
67
|
+
METADATA_TOKENS = {
|
|
68
|
+
geographic: "mysql2rgeo:geographic",
|
|
69
|
+
has_m: "mysql2rgeo:has_m",
|
|
70
|
+
has_z: "mysql2rgeo:has_z",
|
|
71
|
+
array: "mysql2rgeo:array",
|
|
72
|
+
default_prefix: "mysql2rgeo:default:"
|
|
73
|
+
}.freeze
|
|
74
|
+
|
|
32
75
|
class << self
|
|
33
76
|
def geo_type(type = "GEOMETRY")
|
|
34
|
-
type.to_s.delete("_").upcase
|
|
77
|
+
type.to_s.sub(/\Ast_/, "").delete("_").upcase
|
|
35
78
|
end
|
|
36
79
|
|
|
37
80
|
def default_srid(options)
|
|
38
81
|
options[:geographic] ? 4326 : Mysql2RgeoAdapter::DEFAULT_SRID
|
|
39
82
|
end
|
|
83
|
+
|
|
84
|
+
def add_metadata_comment(comment, geographic: false, has_m: false, has_z: false, array: false, default: nil, srid: nil,
|
|
85
|
+
geo_type: nil)
|
|
86
|
+
values = [comment]
|
|
87
|
+
values << METADATA_TOKENS[:geographic] if geographic
|
|
88
|
+
values << METADATA_TOKENS[:has_m] if has_m
|
|
89
|
+
values << METADATA_TOKENS[:has_z] if has_z
|
|
90
|
+
values << METADATA_TOKENS[:array] if array
|
|
91
|
+
if default
|
|
92
|
+
values << "#{METADATA_TOKENS[:default_prefix]}#{encode_default(default, geographic: geographic, srid: srid,
|
|
93
|
+
geo_type: geo_type, has_m: has_m, has_z: has_z)}"
|
|
94
|
+
end
|
|
95
|
+
values.compact_blank.join(" ")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def extract_metadata(comment)
|
|
99
|
+
text = comment.to_s
|
|
100
|
+
default_hex = text[/#{Regexp.escape(METADATA_TOKENS[:default_prefix])}([0-9A-F]+)/i, 1]
|
|
101
|
+
{
|
|
102
|
+
geographic: text.include?(METADATA_TOKENS[:geographic]),
|
|
103
|
+
has_m: text.include?(METADATA_TOKENS[:has_m]),
|
|
104
|
+
has_z: text.include?(METADATA_TOKENS[:has_z]),
|
|
105
|
+
array: text.include?(METADATA_TOKENS[:array]),
|
|
106
|
+
default_hex: default_hex,
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def strip_metadata_comment(comment)
|
|
111
|
+
text = comment.to_s
|
|
112
|
+
METADATA_TOKENS.each_value do |token|
|
|
113
|
+
text = text.gsub(token, "")
|
|
114
|
+
end
|
|
115
|
+
text = text.gsub(/mysql2rgeo:default:[0-9A-F]+/i, "")
|
|
116
|
+
text.squeeze(" ").strip.presence
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def encode_default(default, geographic:, srid:, geo_type:, has_m:, has_z:)
|
|
120
|
+
type = ActiveRecord::Type::Spatial.new(
|
|
121
|
+
geographic ? "geography" : "geometry",
|
|
122
|
+
geo_type: ActiveRecord::Type::Spatial.normalize_geo_type(geo_type),
|
|
123
|
+
srid: srid,
|
|
124
|
+
geographic: geographic,
|
|
125
|
+
has_z: has_z,
|
|
126
|
+
has_m: has_m
|
|
127
|
+
)
|
|
128
|
+
geometry = type.serialize(default)
|
|
129
|
+
geometry = RGeo::Feature.cast(geometry, factory: type.send(:spatial_factory), project: true) if geographic
|
|
130
|
+
|
|
131
|
+
wkb = RGeo::WKRep::WKBGenerator.new(
|
|
132
|
+
hex_format: true,
|
|
133
|
+
little_endian: true,
|
|
134
|
+
type_format: :wkb11,
|
|
135
|
+
emit_ewkb_srid: false
|
|
136
|
+
).generate(geometry).upcase
|
|
137
|
+
return wkb unless geographic
|
|
138
|
+
|
|
139
|
+
srid_hex = [srid.to_i].pack("V").unpack1("H*").upcase
|
|
140
|
+
"#{srid_hex}#{wkb}"
|
|
141
|
+
end
|
|
40
142
|
end
|
|
41
143
|
end
|
|
42
144
|
end
|
|
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
|
29
29
|
config = config.symbolize_keys
|
|
30
30
|
config[:flags] ||= 0
|
|
31
31
|
|
|
32
|
-
if config[:flags].
|
|
32
|
+
if config[:flags].is_a? Array
|
|
33
33
|
config[:flags].push "FOUND_ROWS"
|
|
34
34
|
else
|
|
35
35
|
config[:flags] |= Mysql2::Client::FOUND_ROWS
|
|
@@ -39,7 +39,7 @@ module ActiveRecord
|
|
|
39
39
|
ConnectionAdapters::Mysql2RgeoAdapter.new_client(config),
|
|
40
40
|
logger,
|
|
41
41
|
nil,
|
|
42
|
-
config
|
|
42
|
+
config
|
|
43
43
|
)
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -47,27 +47,37 @@ module ActiveRecord
|
|
|
47
47
|
module ConnectionAdapters
|
|
48
48
|
class Mysql2RgeoAdapter < Mysql2Adapter
|
|
49
49
|
ADAPTER_NAME = "Mysql2Rgeo"
|
|
50
|
+
MINIMUM_SUPPORTED_VERSION = "8.0.0"
|
|
50
51
|
|
|
51
52
|
include Mysql2Rgeo::SchemaStatements
|
|
52
53
|
|
|
53
54
|
SPATIAL_COLUMN_OPTIONS =
|
|
54
55
|
{
|
|
56
|
+
geography: { type: "geometry", geographic: true },
|
|
55
57
|
geometry: {},
|
|
56
58
|
geometrycollection: {},
|
|
59
|
+
geometry_collection: { type: "geometrycollection" },
|
|
57
60
|
linestring: {},
|
|
61
|
+
line_string: { type: "linestring" },
|
|
58
62
|
multilinestring: {},
|
|
63
|
+
multi_line_string: { type: "multilinestring" },
|
|
59
64
|
multipoint: {},
|
|
65
|
+
multi_point: { type: "multipoint" },
|
|
60
66
|
multipolygon: {},
|
|
67
|
+
multi_polygon: { type: "multipolygon" },
|
|
61
68
|
spatial: { type: "geometry" },
|
|
62
69
|
point: {},
|
|
63
|
-
polygon: {}
|
|
70
|
+
polygon: {},
|
|
71
|
+
st_point: { type: "point" },
|
|
72
|
+
st_polygon: { type: "polygon" }
|
|
64
73
|
}.freeze
|
|
65
74
|
|
|
66
|
-
#
|
|
75
|
+
# MySQL uses SRID 0 when a spatial column does not declare one explicitly.
|
|
67
76
|
DEFAULT_SRID = 0
|
|
68
77
|
|
|
69
|
-
def initialize(
|
|
78
|
+
def initialize(connection, logger, connection_options, config)
|
|
70
79
|
super
|
|
80
|
+
verify_supported_database_version!
|
|
71
81
|
|
|
72
82
|
@visitor = Arel::Visitors::Mysql2Rgeo.new(self)
|
|
73
83
|
end
|
|
@@ -86,7 +96,10 @@ module ActiveRecord
|
|
|
86
96
|
super.merge(
|
|
87
97
|
geometry: { name: "geometry" },
|
|
88
98
|
geometrycollection: { name: "geometrycollection" },
|
|
99
|
+
line_string: { name: "linestring" },
|
|
89
100
|
linestring: { name: "linestring" },
|
|
101
|
+
st_point: { name: "point" },
|
|
102
|
+
st_polygon: { name: "polygon" },
|
|
90
103
|
multi_line_string: { name: "multilinestring" },
|
|
91
104
|
multi_point: { name: "multipoint" },
|
|
92
105
|
multi_polygon: { name: "multipolygon" },
|
|
@@ -97,54 +110,110 @@ module ActiveRecord
|
|
|
97
110
|
end
|
|
98
111
|
|
|
99
112
|
class << self
|
|
100
|
-
|
|
101
113
|
private
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
|
|
115
|
+
def initialize_type_map(m)
|
|
116
|
+
super
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
"geography" => "geometry",
|
|
120
|
+
"geometry" => "geometry",
|
|
121
|
+
"geometry_collection" => "geometrycollection",
|
|
122
|
+
"line_string" => "linestring",
|
|
123
|
+
"multi_line_string" => "multilinestring",
|
|
124
|
+
"multi_point" => "multipoint",
|
|
125
|
+
"multi_polygon" => "multipolygon",
|
|
126
|
+
"st_point" => "point",
|
|
127
|
+
"st_polygon" => "polygon",
|
|
128
|
+
}.each do |registered_type, geo_type|
|
|
129
|
+
m.register_type(registered_type) do |sql_type|
|
|
130
|
+
Type::Spatial.new(sql_type.to_s, geo_type: geo_type)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
[
|
|
135
|
+
/\Ageometry(?:\(.*\))?\z/i,
|
|
136
|
+
/\Ageography(?:\(.*\))?\z/i,
|
|
137
|
+
/\Apoint(?:\s.*)?\z/i,
|
|
138
|
+
/\Alinestring(?:\s.*)?\z/i,
|
|
139
|
+
/\Apolygon(?:\s.*)?\z/i,
|
|
140
|
+
/\Amultipoint(?:\s.*)?\z/i,
|
|
141
|
+
/\Amultilinestring(?:\s.*)?\z/i,
|
|
142
|
+
/\Amultipolygon(?:\s.*)?\z/i,
|
|
143
|
+
/\Ageometrycollection(?:\s.*)?\z/i,
|
|
144
|
+
].each do |pattern|
|
|
145
|
+
m.register_type(pattern) do |sql_type|
|
|
146
|
+
Type::Spatial.new(sql_type.to_s)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
st_point: "point",
|
|
152
|
+
st_polygon: "polygon",
|
|
153
|
+
line_string: "linestring",
|
|
154
|
+
multi_line_string: "multilinestring",
|
|
155
|
+
multi_point: "multipoint",
|
|
156
|
+
multi_polygon: "multipolygon",
|
|
157
|
+
}.each do |alias_type, geo_type|
|
|
158
|
+
ActiveRecord::Type.register(alias_type) do |_, **kwargs|
|
|
159
|
+
Type::Spatial.new(geo_type, geo_type: geo_type, **kwargs)
|
|
118
160
|
end
|
|
119
161
|
end
|
|
162
|
+
end
|
|
120
163
|
end
|
|
121
164
|
|
|
122
165
|
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
|
123
166
|
TYPE_MAP_WITH_BOOLEAN = Type::TypeMap.new(TYPE_MAP).tap do |m|
|
|
124
|
-
m.register_type
|
|
167
|
+
m.register_type(/^tinyint\(1\)/i, Type::Boolean.new)
|
|
125
168
|
end
|
|
126
169
|
|
|
127
170
|
def supports_spatial?
|
|
128
|
-
!mariadb? &&
|
|
171
|
+
!mariadb? && database_version >= MINIMUM_SUPPORTED_VERSION
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def adapter_name
|
|
175
|
+
ADAPTER_NAME
|
|
129
176
|
end
|
|
130
177
|
|
|
131
178
|
def quote(value)
|
|
132
179
|
dbval = value.try(:value_for_database) || value
|
|
133
180
|
if RGeo::Feature::Geometry.check_type(dbval)
|
|
134
|
-
|
|
181
|
+
wkt = RGeo::WKRep::WKTGenerator.new(tag_format: :wkt11, emit_ewkt_srid: false).generate(dbval)
|
|
182
|
+
if dbval.srid == 4326
|
|
183
|
+
"ST_GeomFromText(#{super(wkt)}, #{dbval.srid}, 'axis-order=long-lat')"
|
|
184
|
+
else
|
|
185
|
+
"ST_GeomFromText(#{super(wkt)}, #{dbval.srid})"
|
|
186
|
+
end
|
|
135
187
|
else
|
|
136
188
|
super
|
|
137
189
|
end
|
|
138
190
|
end
|
|
139
191
|
|
|
140
|
-
def
|
|
141
|
-
|
|
192
|
+
def quote_default_expression(value, column) # :nodoc:
|
|
193
|
+
return super unless column.respond_to?(:spatial?) && column.spatial?
|
|
194
|
+
|
|
195
|
+
value = lookup_cast_type(column.sql_type).serialize(value)
|
|
196
|
+
hex = RGeo::WKRep::WKBGenerator.new(
|
|
197
|
+
hex_format: true,
|
|
198
|
+
little_endian: true,
|
|
199
|
+
type_format: :wkb11,
|
|
200
|
+
emit_ewkb_srid: false
|
|
201
|
+
).generate(value).upcase
|
|
202
|
+
"(ST_GeomFromWKB(x'#{hex}', #{value.srid}))"
|
|
142
203
|
end
|
|
143
204
|
|
|
144
205
|
private
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
206
|
+
|
|
207
|
+
def verify_supported_database_version!
|
|
208
|
+
return if database_version >= MINIMUM_SUPPORTED_VERSION
|
|
209
|
+
|
|
210
|
+
raise ActiveRecord::ConnectionNotEstablished,
|
|
211
|
+
"#{ADAPTER_NAME} supports MySQL #{MINIMUM_SUPPORTED_VERSION}+ only (detected #{database_version})"
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def type_map
|
|
215
|
+
emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
|
|
216
|
+
end
|
|
148
217
|
end
|
|
149
218
|
end
|
|
150
219
|
end
|
|
@@ -9,9 +9,14 @@ module ActiveRecord
|
|
|
9
9
|
# "geography"
|
|
10
10
|
# "geometry NOT NULL"
|
|
11
11
|
# "geometry"
|
|
12
|
-
def initialize(sql_type = "geometry")
|
|
13
|
-
@sql_type = sql_type
|
|
14
|
-
|
|
12
|
+
def initialize(sql_type = "geometry", geo_type: nil, srid: nil, geographic: false, has_z: false, has_m: false, **_options)
|
|
13
|
+
@sql_type = geographic ? "geography" : sql_type
|
|
14
|
+
parsed_geo_type, parsed_srid, parsed_has_z, parsed_has_m, parsed_geographic = self.class.parse_sql_type(@sql_type)
|
|
15
|
+
@geo_type = self.class.normalize_geo_type(geo_type || parsed_geo_type)
|
|
16
|
+
@srid = srid || parsed_srid
|
|
17
|
+
@has_z = has_z || parsed_has_z
|
|
18
|
+
@has_m = has_m || parsed_has_m
|
|
19
|
+
@geographic = geographic || parsed_geographic
|
|
15
20
|
end
|
|
16
21
|
|
|
17
22
|
# sql_type: geometry, geometry(Point), geometry(Point,4326), ...
|
|
@@ -20,23 +25,49 @@ module ActiveRecord
|
|
|
20
25
|
# geo_type: geography, geometry, point, line_string, polygon, ...
|
|
21
26
|
# srid: 1234
|
|
22
27
|
def self.parse_sql_type(sql_type)
|
|
23
|
-
geo_type
|
|
28
|
+
geo_type = nil
|
|
29
|
+
srid = 0
|
|
30
|
+
has_z = false
|
|
31
|
+
has_m = false
|
|
32
|
+
geographic = false
|
|
33
|
+
|
|
24
34
|
if sql_type =~ /(geography|geometry)\((.*)\)$/i
|
|
25
35
|
# geometry(Point)
|
|
26
36
|
# geometry(Point,4326)
|
|
37
|
+
geographic = Regexp.last_match(1).casecmp("geography").zero?
|
|
27
38
|
params = Regexp.last_match(2).split(",")
|
|
28
39
|
if params.first =~ /([a-z]+[^zm])(z?)(m?)/i
|
|
29
40
|
geo_type = Regexp.last_match(1)
|
|
41
|
+
has_z = Regexp.last_match(2).casecmp("z").zero?
|
|
42
|
+
has_m = Regexp.last_match(3).casecmp("m").zero?
|
|
30
43
|
end
|
|
31
|
-
if params.last =~ /(\d+)/
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
srid = Regexp.last_match(1).to_i if params.last =~ /(\d+)/
|
|
45
|
+
elsif sql_type =~ /\A(geography|geometry)\z/i
|
|
46
|
+
geographic = Regexp.last_match(1).casecmp("geography").zero?
|
|
47
|
+
geo_type = Regexp.last_match(1)
|
|
34
48
|
else
|
|
35
49
|
# geometry
|
|
36
50
|
# otherType(a,b)
|
|
37
51
|
geo_type = sql_type
|
|
38
52
|
end
|
|
39
|
-
[geo_type, srid]
|
|
53
|
+
[geo_type, srid, has_z, has_m, geographic]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.normalize_geo_type(geo_type)
|
|
57
|
+
case geo_type.to_s.underscore.delete("_")
|
|
58
|
+
when "geometrycollection"
|
|
59
|
+
"geometry_collection"
|
|
60
|
+
when "linestring"
|
|
61
|
+
"line_string"
|
|
62
|
+
when "multilinestring"
|
|
63
|
+
"multi_line_string"
|
|
64
|
+
when "multipoint"
|
|
65
|
+
"multi_point"
|
|
66
|
+
when "multipolygon"
|
|
67
|
+
"multi_polygon"
|
|
68
|
+
else
|
|
69
|
+
geo_type.to_s.underscore.presence
|
|
70
|
+
end
|
|
40
71
|
end
|
|
41
72
|
|
|
42
73
|
def spatial_factory
|
|
@@ -45,9 +76,11 @@ module ActiveRecord
|
|
|
45
76
|
@spatial_factories[@srid] ||=
|
|
46
77
|
RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
|
|
47
78
|
geo_type: @geo_type,
|
|
48
|
-
|
|
79
|
+
has_m: @has_m,
|
|
80
|
+
has_z: @has_z,
|
|
81
|
+
sql_type: @geographic ? "geography" : "geometry",
|
|
49
82
|
srid: @srid
|
|
50
|
-
|
|
83
|
+
)
|
|
51
84
|
end
|
|
52
85
|
|
|
53
86
|
def klass
|
|
@@ -67,6 +100,9 @@ module ActiveRecord
|
|
|
67
100
|
return if value.nil?
|
|
68
101
|
|
|
69
102
|
geo_value = cast_value(value)
|
|
103
|
+
if geo_value && !@geographic && @srid.to_i.zero? && geo_value.srid != @srid
|
|
104
|
+
geo_value = RGeo::Feature.cast(geo_value, factory: spatial_factory, project: true)
|
|
105
|
+
end
|
|
70
106
|
|
|
71
107
|
# TODO: - only valid types should be allowed
|
|
72
108
|
# e.g. linestring is not valid for point column
|
|
@@ -80,7 +116,7 @@ module ActiveRecord
|
|
|
80
116
|
def cast_value(value)
|
|
81
117
|
return if value.nil?
|
|
82
118
|
|
|
83
|
-
::String
|
|
119
|
+
value.is_a?(::String) ? parse_wkt(value) : value
|
|
84
120
|
end
|
|
85
121
|
|
|
86
122
|
# convert WKT string into RGeo object
|
|
@@ -88,13 +124,28 @@ module ActiveRecord
|
|
|
88
124
|
marker = string[4, 1]
|
|
89
125
|
if ["\x00", "\x01"].include?(marker)
|
|
90
126
|
@srid = string[0, 4].unpack1(marker == "\x01" ? "V" : "N")
|
|
91
|
-
RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid).parse(string[4
|
|
92
|
-
elsif string[0
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
127
|
+
RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid).parse(string[4..])
|
|
128
|
+
elsif string.match?(/\A[0-9a-fA-F]+\z/)
|
|
129
|
+
original_srid = @srid
|
|
130
|
+
parser = RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid)
|
|
131
|
+
|
|
132
|
+
begin
|
|
133
|
+
return parser.parse([string].pack("H*"))
|
|
134
|
+
rescue RGeo::Error::ParseError, RGeo::Error::InvalidGeometry
|
|
135
|
+
@srid = original_srid
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if string[0, 10] =~ /[0-9a-fA-F]{8}0[01]/
|
|
139
|
+
@srid = string[0, 8].to_i(16)
|
|
140
|
+
@srid = [@srid].pack("V").unpack("N").first if string[9, 1] == "1"
|
|
141
|
+
parser = RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid)
|
|
142
|
+
return parser.parse([string[8..]].pack("H*"))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
parser.parse([string].pack("H*"))
|
|
96
146
|
else
|
|
97
|
-
string,
|
|
147
|
+
string, srid = Arel::Visitors::Mysql2Rgeo.parse_node(string)
|
|
148
|
+
@srid = srid.zero? ? @srid : srid
|
|
98
149
|
RGeo::WKRep::WKTParser.new(spatial_factory, support_ewkt: true, default_srid: @srid).parse(string)
|
|
99
150
|
end
|
|
100
151
|
rescue RGeo::Error::ParseError, RGeo::Error::InvalidGeometry
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-mysql2rgeo-adapter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.2
|
|
4
|
+
version: 7.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yongdae Hwang
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 7.
|
|
18
|
+
version: 7.1.0
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 7.
|
|
25
|
+
version: 7.1.0
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: rgeo-activerecord
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,78 +38,79 @@ dependencies:
|
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: 7.0.0
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: rake
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - "~>"
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
48
|
-
type: :
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
52
51
|
- - "~>"
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
53
|
+
version: '13.0'
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
55
|
+
name: minitest
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
58
|
- - "~>"
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
60
|
+
version: '5.4'
|
|
62
61
|
type: :development
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
65
|
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
67
|
+
version: '5.4'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
69
|
+
name: mocha
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
72
71
|
requirements:
|
|
73
72
|
- - "~>"
|
|
74
73
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
74
|
+
version: '1.1'
|
|
76
75
|
type: :development
|
|
77
76
|
prerelease: false
|
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
78
|
requirements:
|
|
80
79
|
- - "~>"
|
|
81
80
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
81
|
+
version: '1.1'
|
|
83
82
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
83
|
+
name: benchmark-ips
|
|
85
84
|
requirement: !ruby/object:Gem::Requirement
|
|
86
85
|
requirements:
|
|
87
86
|
- - "~>"
|
|
88
87
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '2.
|
|
88
|
+
version: '2.12'
|
|
90
89
|
type: :development
|
|
91
90
|
prerelease: false
|
|
92
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
92
|
requirements:
|
|
94
93
|
- - "~>"
|
|
95
94
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '2.
|
|
95
|
+
version: '2.12'
|
|
97
96
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
97
|
+
name: rubocop
|
|
99
98
|
requirement: !ruby/object:Gem::Requirement
|
|
100
99
|
requirements:
|
|
101
100
|
- - "~>"
|
|
102
101
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '
|
|
102
|
+
version: '1.50'
|
|
104
103
|
type: :development
|
|
105
104
|
prerelease: false
|
|
106
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
106
|
requirements:
|
|
108
107
|
- - "~>"
|
|
109
108
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '
|
|
109
|
+
version: '1.50'
|
|
111
110
|
description: ActiveRecord connection adapter for MySQL. It is based on the stock MySQL
|
|
112
111
|
adapter, and adds built-in support for the spatial extensions provided by MySQL.
|
|
113
|
-
It uses the RGeo library to represent spatial data in Ruby.
|
|
112
|
+
It uses the RGeo library to represent spatial data in Ruby. This gem is maintained
|
|
113
|
+
for MySQL 8.0 and 8.4.
|
|
114
114
|
email: stadia@gmail.com
|
|
115
115
|
executables: []
|
|
116
116
|
extensions: []
|
|
@@ -132,8 +132,10 @@ files:
|
|
|
132
132
|
homepage: http://github.com/stadia/activerecord-mysql2rgeo-adapter
|
|
133
133
|
licenses:
|
|
134
134
|
- BSD-3-Clause
|
|
135
|
-
metadata:
|
|
136
|
-
|
|
135
|
+
metadata:
|
|
136
|
+
source_code_uri: https://github.com/stadia/activerecord-mysql2rgeo-adapter
|
|
137
|
+
documentation_uri: https://github.com/stadia/activerecord-mysql2rgeo-adapter/blob/main/README.md
|
|
138
|
+
rubygems_mfa_required: 'true'
|
|
137
139
|
rdoc_options: []
|
|
138
140
|
require_paths:
|
|
139
141
|
- lib
|
|
@@ -141,15 +143,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
141
143
|
requirements:
|
|
142
144
|
- - ">="
|
|
143
145
|
- !ruby/object:Gem::Version
|
|
144
|
-
version:
|
|
146
|
+
version: 3.0.0
|
|
145
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
148
|
requirements:
|
|
147
149
|
- - ">="
|
|
148
150
|
- !ruby/object:Gem::Version
|
|
149
151
|
version: '0'
|
|
150
152
|
requirements: []
|
|
151
|
-
rubygems_version:
|
|
152
|
-
signing_key:
|
|
153
|
+
rubygems_version: 4.0.20
|
|
153
154
|
specification_version: 4
|
|
154
155
|
summary: ActiveRecord adapter for MySQL, based on RGeo.
|
|
155
156
|
test_files: []
|