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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3cea6a82ae6d953228236a5977edc5959e9c42f1bfb22551e65c9750a68b09b
4
- data.tar.gz: ec5fe7209c439daf1a6c6c0acad0ce456ea88c92e811f17d777a5f6f927e8ad7
3
+ metadata.gz: 816dbf40bdc492a42c20ded6aef4f247a76dedbbdd73bc6d705a400f8b8cfdc7
4
+ data.tar.gz: 4a04ba14c239948df20572c8b29a13f4c6296dfc11edb3beba35b1b76c26124f
5
5
  SHA512:
6
- metadata.gz: b8525a4fbcb8ca8bf509c1b079aed654f10785403df3eab43dc7ef0f93d5dd1742794da811915b6415bd35b7d7c58dc4d9addcfdeac4a2a54f1581e91c8417d4
7
- data.tar.gz: 392f931518541a7a0f827749393f1cf14064bd31b50ccebe9f236d251035089102b929a8d9bf104dfe1fb08b31f46d229806d857dcd96daa8f32bad0485cc214
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 # :nodoc:
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 << if srid == 0
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 << if srid == 0
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
- collector << visit_RGeo_Feature_Instance(node, collector)
44
+ visit_RGeo_Feature_Instance(node, collector)
53
45
  when RGeo::Cartesian::BoundingBox
54
- collector << visit_RGeo_Cartesian_BoundingBox(node, collector)
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
- def add_column_options!(sql, options)
10
- if options[:srid]
11
- sql << " /*!80003 SRID #{options[:srid]} */"
12
- end
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
- super
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) #:nodoc:
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 { |idx| idx.is_a?(Struct) ? idx.lengths = {} : idx.instance_variable_set(:@lengths, {}) }
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
- if (info = RGeo::ActiveRecord.geometric_type_from_name(type.to_s.delete("_")))
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 = +"(#{default})" unless default.start_with?("(")
50
- default, default_function = nil, default
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: field[:Comment].presence,
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, spatial: 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
- if spatial?
22
- if @srid
23
- @limit = { type: geometric_type.type_name.underscore, srid: @srid }
24
- end
25
- end
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
- type.sub!(/m$/, "")
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
- if (info = Mysql2RgeoAdapter.spatial_column_options(type.to_sym))
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 = ColumnDefinitionUtils.geo_type(options[:type] || type || info[: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 = super(name, geo_type.downcase.to_sym, **options)
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
- column = super(name, type, **options)
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
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Mysql2Rgeo
6
- VERSION = "7.2.0"
6
+ VERSION = "7.3.2"
7
7
  end
8
8
  end
9
9
  end
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  config = config.symbolize_keys
30
30
  config[:flags] ||= 0
31
31
 
32
- if config[:flags].kind_of? Array
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
- # http://postgis.17.x6.nabble.com/Default-SRID-td5001115.html
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
- def initialize_type_map(m)
103
- super
104
-
105
- %w[
106
- geometry
107
- geometrycollection
108
- point
109
- linestring
110
- polygon
111
- multipoint
112
- multilinestring
113
- multipolygon
114
- ].each do |geo_type|
115
- m.register_type(geo_type) do |sql_type|
116
- Type::Spatial.new(sql_type.to_s)
117
- end
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 %r(^tinyint\(1\))i, Type::Boolean.new
167
+ m.register_type(/^tinyint\(1\)/i, Type::Boolean.new)
125
168
  end
126
169
 
127
170
  def supports_spatial?
128
- !mariadb? && version >= "5.7.6"
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
- "ST_GeomFromWKB(0x#{RGeo::WKRep::WKBGenerator.new(hex_format: true, little_endian: true).generate(dbval)},#{dbval.srid})"
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 with_connection
141
- yield self
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
- def type_map
146
- emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
147
- end
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
- @geo_type, @srid = self.class.parse_sql_type(sql_type)
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, srid = nil, 0, false, false
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
- srid = Regexp.last_match(1).to_i
33
- end
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
- sql_type: @sql_type,
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 === value ? parse_wkt(value) : value
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..-1])
92
- elsif string[0, 10] =~ /[0-9a-fA-F]{8}0[01]/
93
- @srid = string[0, 8].to_i(16)
94
- @srid = [@srid].pack("V").unpack("N").first if string[9, 1] == "1"
95
- RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: srid).parse(string[8..-1])
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, @srid = Arel::Visitors::Mysql2Rgeo.parse_node(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
@@ -1,5 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record/connection_adapters/mysql2rgeo_adapter"
4
-
5
- ActiveRecord::ConnectionAdapters.register('mysql2rgeo', 'ActiveRecord::ConnectionAdapters::Mysql2RgeoAdapter', 'active_record/connection_adapters/mysql2rgeo_adapter')
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.0
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: 2025-02-28 00:00:00.000000000 Z
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.2.0
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.2.0
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: rgeo
41
+ name: rake
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '3.0'
48
- type: :runtime
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: '3.0'
53
+ version: '13.0'
55
54
  - !ruby/object:Gem::Dependency
56
- name: rake
55
+ name: minitest
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - "~>"
60
59
  - !ruby/object:Gem::Version
61
- version: '12.0'
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: '12.0'
67
+ version: '5.4'
69
68
  - !ruby/object:Gem::Dependency
70
- name: minitest
69
+ name: mocha
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
72
  - - "~>"
74
73
  - !ruby/object:Gem::Version
75
- version: '5.4'
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: '5.4'
81
+ version: '1.1'
83
82
  - !ruby/object:Gem::Dependency
84
- name: mocha
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.1'
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.1'
95
+ version: '2.12'
97
96
  - !ruby/object:Gem::Dependency
98
- name: appraisal
97
+ name: rubocop
99
98
  requirement: !ruby/object:Gem::Requirement
100
99
  requirements:
101
100
  - - "~>"
102
101
  - !ruby/object:Gem::Version
103
- version: '2.0'
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: '2.0'
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
- post_install_message:
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: 2.7.0
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: 3.3.27
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: []