activerecord-mysql2rgeo-adapter 7.3.1 → 9.0.0

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: 22e12f955f45c958cdf1d7482072dea06de6c23a55df0863d0e454cb3b2b4814
4
- data.tar.gz: 594de2ab90c4ec8cc2ea126ad286eddbf7c44b3ff46a08b204ff92add1baefde
3
+ metadata.gz: 3f886ffec9c1cc39dd75d324940ebb386a49b5a8b23d65f3e2016e9610dd0c23
4
+ data.tar.gz: ba5b9e5e18655b6a52f0e73a612679efd55f5a097e8eeaf6d13736631c9dcaf9
5
5
  SHA512:
6
- metadata.gz: 5f41a95d660e6c21be4a2b3d653e0d47758d74432a36f1ac68075f2a2b079ec4d0da35e30fd83e6e2d7484abd8d9e8a664752aa397d0024690ae01b4afd84047
7
- data.tar.gz: 2ff34f00366a8e5861ba4394f1b42d827f1b502e258defe09bd156b28eb9e13f53b7e4b617d8c283fcbe09909885f3002b8a04f8d101723d8c1b7c206975505a
6
+ metadata.gz: cd759c023b7736b538039efbb1ebe0e289f77532758df317b6c7273269cc22d5888a662365cc5dafcfb5eade82732f6e531cba88df7df652c15e085b9d839a9d
7
+ data.tar.gz: 6e6b9e64f3662ed48fd16a18fa9fcb4741d70748c306a2ad07abf8e72eddb8819c4f0a3c47f185c743c8b5a336aac8910d0affff618a94258c8eac23887d97a6
@@ -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",
@@ -26,16 +24,11 @@ module Arel # :nodoc:
26
24
  FUNC_MAP[standard_name.downcase] || standard_name
27
25
  end
28
26
 
29
- def visit_String(node, collector)
27
+ def visit_RGeo_Feature_Instance(node, collector)
30
28
  node, srid = Mysql2Rgeo.parse_node(node)
31
29
  collector << spatial_constant_sql(node, srid)
32
30
  end
33
- alias visit_RGeo_Feature_Instance visit_String
34
- alias visit_RGeo_Cartesian_BoundingBox visit_String
35
-
36
- def visit_RGeo_ActiveRecord_SpatialNamedFunction(node, collector)
37
- aggregate(st_func(node.name), node, collector)
38
- end
31
+ alias visit_RGeo_Cartesian_BoundingBox visit_RGeo_Feature_Instance
39
32
 
40
33
  def visit_in_spatial_context(node, collector)
41
34
  case node
@@ -52,18 +45,19 @@ module Arel # :nodoc:
52
45
  end
53
46
 
54
47
  def self.parse_node(node)
55
- if RGeo::Feature::Instance === node
48
+ if node.is_a?(RGeo::Feature::Instance)
56
49
  wkt = RGeo::WKRep::WKTGenerator.new(tag_format: :wkt11, emit_ewkt_srid: false).generate(node)
57
50
  return [wkt, node.srid]
58
51
  end
59
52
 
60
- if RGeo::Cartesian::BoundingBox === node
53
+ if node.is_a?(RGeo::Cartesian::BoundingBox)
61
54
  geometry = node.to_geometry
62
55
  wkt = RGeo::WKRep::WKTGenerator.new(tag_format: :wkt11, emit_ewkt_srid: false).generate(geometry)
63
56
  return [wkt, geometry.srid]
64
57
  end
65
58
 
66
- value, srid = nil, 0
59
+ value = nil
60
+ srid = 0
67
61
  if node =~ /.*;.*$/i
68
62
  params = Regexp.last_match(0).split(";")
69
63
  if params.first =~ /(srid|SRID)=\d*/
@@ -5,34 +5,33 @@ module ActiveRecord
5
5
  module Mysql2Rgeo
6
6
  class SchemaCreation < MySQL::SchemaCreation # :nodoc:
7
7
  private
8
- def visit_IndexDefinition(o, create = false)
9
- if o.using&.to_sym == :gist
10
- sql = create ? ["CREATE SPATIAL INDEX"] : ["SPATIAL INDEX"]
11
- sql << quote_column_name(o.name)
12
- sql << "ON #{quote_table_name(o.table)}" if create
13
- sql << "(#{quoted_columns(o)})"
14
- return sql.join(" ")
15
- end
16
8
 
17
- super
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(" ")
18
16
  end
19
17
 
20
- def add_column_options!(sql, options)
21
- if options[:srid]
22
- sql << " /*!80003 SRID #{options[:srid]} */"
23
- options = options.except(:srid)
24
- end
18
+ super
19
+ end
25
20
 
26
- if options_include_default?(options) && spatial_column_definition?(options[:column])
27
- options = options.except(:default)
28
- end
29
-
30
- super
21
+ def add_column_options!(sql, options)
22
+ if options[:srid]
23
+ sql << " /*!80003 SRID #{options[:srid]} */"
24
+ options = options.except(:srid)
31
25
  end
32
26
 
33
- def spatial_column_definition?(column)
34
- column.respond_to?(:type) && column.type && @conn.class.spatial_column_options(column.type.to_sym)
35
- end
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
36
35
  end
37
36
  end
38
37
  end
@@ -7,7 +7,7 @@ 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
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
 
34
34
  column_options = column.limit.is_a?(Hash) ? column.limit.symbolize_keys.except(:type) : {}
35
35
  column_options[:comment] = column.comment if column.comment.present?
36
- change_column(table_name, name, column.limit[:type].to_sym, **column_options.merge(null: false))
36
+ change_column(table_name, name, column.limit[:type].to_sym, **column_options, null: false)
37
37
  end
38
38
  end
39
39
 
@@ -42,17 +42,19 @@ module ActiveRecord
42
42
 
43
43
  # override
44
44
  def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
45
- if type.to_sym == :geometry && limit.is_a?(String)
46
- return "geometry(#{limit})"
47
- end
45
+ return "geometry(#{limit})" if type.to_sym == :geometry && limit.is_a?(String)
48
46
 
49
- if (info = RGeo::ActiveRecord.geometric_type_from_name(type.to_s.delete("_")))
47
+ if RGeo::ActiveRecord.geometric_type_from_name(type.to_s.delete("_"))
50
48
  type = limit[:type] || type if limit.is_a?(::Hash)
51
49
  type = type.to_s.delete("_").upcase
52
50
  end
53
51
  super
54
52
  end
55
53
 
54
+ def update_table_definition(table_name, base)
55
+ Mysql2Rgeo::Table.new(table_name, base)
56
+ end
57
+
56
58
  private
57
59
 
58
60
  # override
@@ -61,43 +63,44 @@ module ActiveRecord
61
63
  end
62
64
 
63
65
  # override
64
- def create_table_definition(*args, **options)
65
- Mysql2Rgeo::TableDefinition.new(self, *args, **options)
66
- end
67
-
68
- def update_table_definition(table_name, base)
69
- Mysql2Rgeo::Table.new(table_name, base)
66
+ def create_table_definition(*, **)
67
+ Mysql2Rgeo::TableDefinition.new(self, *, **)
70
68
  end
71
69
 
72
70
  # override
73
71
  def new_column_from_field(table_name, field, _definitions)
74
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
75
- default, default_function = field[:Default], nil
76
- metadata = Mysql2Rgeo::ColumnDefinitionUtils.extract_metadata(field[:Comment])
77
- comment = Mysql2Rgeo::ColumnDefinitionUtils.strip_metadata_comment(field[:Comment])
72
+ field_name = field_value(field, "Field")
73
+ type_metadata = fetch_type_metadata(field_value(field, "Type"), field_value(field, "Extra"))
74
+ default = field_value(field, "Default")
75
+ default_function = nil
76
+ metadata_comment = field_value(field, "Comment")
77
+ metadata = Mysql2Rgeo::ColumnDefinitionUtils.extract_metadata(metadata_comment)
78
+ comment = Mysql2Rgeo::ColumnDefinitionUtils.strip_metadata_comment(metadata_comment)
78
79
  default = metadata[:default_hex] if default.nil? && metadata[:default_hex].present?
79
80
 
80
81
  if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
81
- default, default_function = nil, default
82
+ default_function = default
83
+ default = nil
82
84
  elsif type_metadata.extra == "DEFAULT_GENERATED"
83
85
  if default == "NULL" && metadata[:default_hex].present?
84
86
  default = metadata[:default_hex]
85
87
  elsif default == "NULL"
86
- default = generated_default_for(table_name, field[:Field])
88
+ default = generated_default_for(table_name, field_name)
87
89
  end
88
90
 
89
91
  if default&.match?(/\Ast_geomfromtext\(/i)
90
- default = +"(#{default})" unless default.start_with?("(")
91
- default, default_function = nil, default
92
+ default = "(#{default})" unless default.start_with?("(")
93
+ default_function = default
94
+ default = nil
92
95
  end
93
96
  end
94
97
 
95
98
  if type_metadata.extra.to_s.match?(/(?:VIRTUAL|STORED|PERSISTENT)\s+GENERATED/i)
96
- default_function = generation_expression_for(table_name, field[:Field])
99
+ default_function = generation_expression_for(table_name, field_name)
97
100
  end
98
101
 
99
102
  # {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
100
- spatial = spatial_column_info(table_name).get(field[:Field], type_metadata.sql_type)
103
+ spatial = spatial_column_info(table_name).get(field_name, type_metadata.sql_type)
101
104
  if spatial
102
105
  spatial[:has_z] ||= metadata[:has_z]
103
106
  spatial[:has_m] ||= metadata[:has_m]
@@ -116,9 +119,9 @@ module ActiveRecord
116
119
  type: :geometry,
117
120
  limit: nil,
118
121
  precision: type_metadata.precision,
119
- scale: type_metadata.scale,
122
+ scale: type_metadata.scale
120
123
  ),
121
- extra: type_metadata.extra,
124
+ extra: type_metadata.extra
122
125
  )
123
126
 
124
127
  if default_function&.match?(/\A\(?st_geomfromtext\(/i)
@@ -128,12 +131,13 @@ module ActiveRecord
128
131
  end
129
132
 
130
133
  SpatialColumn.new(
131
- field[:Field],
134
+ field_name,
135
+ lookup_cast_type(type_metadata.sql_type),
132
136
  default,
133
137
  type_metadata,
134
- field[:Null] == "YES",
138
+ field_value(field, "Null") == "YES",
135
139
  default_function,
136
- collation: field[:Collation],
140
+ collation: field_value(field, "Collation"),
137
141
  comment: comment,
138
142
  spatial: spatial,
139
143
  array: metadata[:array]
@@ -146,6 +150,10 @@ module ActiveRecord
146
150
  @spatial_column_info[table_name.to_sym] = SpatialColumnInfo.new(self, table_name.to_s)
147
151
  end
148
152
 
153
+ def field_value(field, key)
154
+ field[key] || field[key.to_sym]
155
+ end
156
+
149
157
  def extract_spatial_default_hex(default_function, spatial)
150
158
  default_sql = default_function.delete_prefix("(").delete_suffix(")").delete("\\")
151
159
  match = default_sql.match(/\Ast_geomfromtext\(_utf8mb4'(.+?)',\s*(\d+)\)\z/i)
@@ -159,10 +167,11 @@ module ActiveRecord
159
167
  srid: srid,
160
168
  geographic: spatial[:geographic],
161
169
  has_z: spatial[:has_z],
162
- has_m: spatial[:has_m],
170
+ has_m: spatial[:has_m]
163
171
  )
164
172
  geometry = type.serialize(wkt)
165
173
  return unless geometry
174
+
166
175
  geometry = RGeo::Feature.cast(geometry, factory: type.send(:spatial_factory), project: true) if spatial[:geographic]
167
176
 
168
177
  wkb = RGeo::WKRep::WKBGenerator.new(
@@ -171,10 +180,15 @@ module ActiveRecord
171
180
  type_format: :wkb11,
172
181
  emit_ewkb_srid: false
173
182
  ).generate(geometry)
174
- return wkb.upcase unless spatial[:geographic]
175
-
176
- srid_hex = [spatial[:srid].to_i].pack("V").unpack1("H*")
177
- "#{srid_hex}#{wkb}".upcase
183
+ wkb = wkb.upcase
184
+ return wkb unless spatial[:geographic]
185
+
186
+ endian = wkb[0, 2]
187
+ type_hex = wkb[2, 8]
188
+ body = wkb[10..]
189
+ type = [type_hex].pack("H*").unpack1("V") | 0x20000000
190
+ srid_hex = [spatial[:srid].to_i].pack("V").unpack1("H*").upcase
191
+ "#{endian}#{[type].pack('V').unpack1('H*').upcase}#{srid_hex}#{body}"
178
192
  end
179
193
 
180
194
  def generation_expression_for(table_name, column_name)
@@ -185,7 +199,7 @@ module ActiveRecord
185
199
  AND table_name = #{quote(table_name)}
186
200
  AND column_name = #{quote(column_name)}
187
201
  SQL
188
- &.gsub(/st_buffer\(([^,]+),\s*(\d+)\)/i, 'st_buffer(\1, (\2)::double precision)')
202
+ &.gsub(/st_buffer\(([^,]+),\s*(\d+)\)/i, 'st_buffer(\1, (\2)::double precision)')
189
203
  end
190
204
 
191
205
  def generated_default_for(table_name, column_name)
@@ -4,16 +4,32 @@ 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, array: false, **)
7
+ # ActiveRecord 8.1 added cast_type as Column#initialize's second argument.
8
+ COLUMN_TAKES_CAST_TYPE = ActiveRecord.gem_version >= Gem::Version.new("8.1")
9
+
10
+ def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil,
11
+ spatial: nil, array: false, **)
12
+ if default.respond_to?(:sql_type) && (sql_type_metadata.nil? || sql_type_metadata == true || sql_type_metadata == false)
13
+ null = sql_type_metadata unless sql_type_metadata.nil?
14
+ sql_type_metadata = default
15
+ default = cast_type
16
+ cast_type = nil
17
+ end
18
+
8
19
  @sql_type_metadata = sql_type_metadata
9
20
  @array = array
21
+ @geographic = !!(sql_type_metadata&.sql_type =~ /geography\(/i)
10
22
  if spatial
11
23
  # This case comes from an entry in the geometry_columns table
12
24
  set_geometric_type_from_name(spatial[:type])
13
25
  @srid = spatial[:srid].to_i
14
- @has_z = spatial[:has_z]
15
- @has_m = spatial[:has_m]
16
- @geographic = spatial[:geographic]
26
+ @has_z = !!spatial[:has_z]
27
+ @has_m = !!spatial[:has_m]
28
+ @geographic = !!spatial[:geographic] || @geographic
29
+ elsif @geographic
30
+ @srid = 4326
31
+ @has_z = @has_m = false
32
+ build_from_sql_type(sql_type_metadata.sql_type)
17
33
  elsif sql_type =~ /geometry|point|linestring|polygon/i
18
34
  build_from_sql_type(sql_type_metadata.sql_type)
19
35
  elsif sql_type_metadata.sql_type =~ /geometry|point|linestring|polygon/i
@@ -21,15 +37,18 @@ module ActiveRecord # :nodoc:
21
37
  # @geometric_type = geo_type_from_sql_type(sql_type)
22
38
  build_from_sql_type(sql_type_metadata.sql_type)
23
39
  end
24
- super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
25
- if spatial?
26
- if @srid
27
- @limit = { type: limit_type_name, srid: @srid }
28
- @limit[:geographic] = true if geographic?
29
- @limit[:has_z] = true if has_z?
30
- @limit[:has_m] = true if has_m?
31
- end
40
+ if COLUMN_TAKES_CAST_TYPE
41
+ super(name, cast_type, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
42
+ else
43
+ super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
32
44
  end
45
+ return unless spatial?
46
+ return unless @srid
47
+
48
+ @limit = { type: limit_type_name, srid: @srid }
49
+ @limit[:geographic] = true if geographic?
50
+ @limit[:has_z] = true if has_z?
51
+ @limit[:has_m] = true if has_m?
33
52
  end
34
53
 
35
54
  attr_reader :geometric_type, :srid
@@ -67,6 +86,30 @@ module ActiveRecord # :nodoc:
67
86
  %i[geometry geography].include?(@sql_type_metadata.type)
68
87
  end
69
88
 
89
+ SPATIAL_ATTRIBUTES = %w[geographic geometric_type has_m has_z srid limit].freeze
90
+
91
+ def init_with(coder)
92
+ SPATIAL_ATTRIBUTES.each { |attr| instance_variable_set(:"@#{attr}", coder[attr]) }
93
+ super
94
+ end
95
+
96
+ def encode_with(coder)
97
+ SPATIAL_ATTRIBUTES.each { |attr| coder[attr] = instance_variable_get(:"@#{attr}") }
98
+ super
99
+ end
100
+
101
+ def ==(other)
102
+ other.is_a?(SpatialColumn) &&
103
+ super &&
104
+ array == other.array &&
105
+ SPATIAL_ATTRIBUTES.all? { |attr| public_send(attr) == other.public_send(attr) }
106
+ end
107
+ alias eql? ==
108
+
109
+ def hash
110
+ SPATIAL_ATTRIBUTES.reduce(SpatialColumn.hash ^ super.hash ^ array.hash) { |h, attr| h ^ public_send(attr).hash }
111
+ end
112
+
70
113
  private
71
114
 
72
115
  def set_geometric_type_from_name(name)
@@ -29,11 +29,11 @@ module ActiveRecord # :nodoc:
29
29
  has_z = type.sub!(/z$/i, "").present?
30
30
  has_m = type.sub!(/m$/i, "").present?
31
31
  result[name] = {
32
- name: name,
33
- srid: row[1].to_i,
34
- type: type,
35
- has_z: has_z,
36
- has_m: has_m,
32
+ name: name,
33
+ srid: row[1].to_i,
34
+ type: type,
35
+ has_z: has_z,
36
+ has_m: has_m,
37
37
  geographic: column_comment.include?("mysql2rgeo:geographic"),
38
38
  }
39
39
  end
@@ -5,6 +5,7 @@ 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
@@ -20,7 +21,9 @@ module ActiveRecord # :nodoc:
20
21
  ColumnDefinitionUtils.geo_type(options[:type] || spatial_type || info[:type])
21
22
  end
22
23
 
23
- unless type.to_sym == :virtual
24
+ if type.to_sym == :virtual
25
+ options.delete(:srid)
26
+ else
24
27
  options[:srid] ||= ColumnDefinitionUtils.default_srid(options)
25
28
  options[:comment] = ColumnDefinitionUtils.add_metadata_comment(
26
29
  options[:comment],
@@ -32,19 +35,20 @@ module ActiveRecord # :nodoc:
32
35
  srid: options[:srid],
33
36
  geo_type: geo_type
34
37
  )
35
- else
36
- options.delete(:srid)
37
38
  end
38
39
 
39
40
  options[:spatial_type] = geo_type
40
- if type.to_sym == :virtual
41
- column = super(name, type, **options.merge(type: geo_type.downcase.to_sym))
42
- else
43
- column = super(name, geo_type.downcase.to_sym, **options)
44
- end
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
45
46
  else
46
- options[:comment] = ColumnDefinitionUtils.add_metadata_comment(options[:comment], array: options[:array]) if options[:array]
47
- 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
48
52
  end
49
53
 
50
54
  column
@@ -77,14 +81,16 @@ module ActiveRecord # :nodoc:
77
81
  options[:geographic] ? 4326 : Mysql2RgeoAdapter::DEFAULT_SRID
78
82
  end
79
83
 
80
- def add_metadata_comment(comment, geographic: false, has_m: false, has_z: false, array: false, default: nil, srid: nil, geo_type: nil)
84
+ def add_metadata_comment(comment, geographic: false, has_m: false, has_z: false, array: false, default: nil, srid: nil,
85
+ geo_type: nil)
81
86
  values = [comment]
82
87
  values << METADATA_TOKENS[:geographic] if geographic
83
88
  values << METADATA_TOKENS[:has_m] if has_m
84
89
  values << METADATA_TOKENS[:has_z] if has_z
85
90
  values << METADATA_TOKENS[:array] if array
86
91
  if default
87
- values << "#{METADATA_TOKENS[:default_prefix]}#{encode_default(default, geographic: geographic, srid: srid, geo_type: geo_type, has_m: has_m, has_z: has_z)}"
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)}"
88
94
  end
89
95
  values.compact_blank.join(" ")
90
96
  end
@@ -128,10 +134,16 @@ module ActiveRecord # :nodoc:
128
134
  type_format: :wkb11,
129
135
  emit_ewkb_srid: false
130
136
  ).generate(geometry).upcase
131
- return wkb unless geographic
137
+ geographic ? with_ewkb_srid(wkb, srid) : wkb
138
+ end
132
139
 
140
+ def with_ewkb_srid(wkb, srid)
141
+ endian = wkb[0, 2]
142
+ type_hex = wkb[2, 8]
143
+ body = wkb[10..]
144
+ type = [type_hex].pack("H*").unpack1("V") | 0x20000000
133
145
  srid_hex = [srid.to_i].pack("V").unpack1("H*").upcase
134
- "#{srid_hex}#{wkb}"
146
+ "#{endian}#{[type].pack('V').unpack1('H*').upcase}#{srid_hex}#{body}"
135
147
  end
136
148
  end
137
149
  end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Mysql2Rgeo
6
- VERSION = "7.3.1"
6
+ VERSION = "9.0.0"
7
7
  end
8
8
  end
9
9
  end
@@ -22,14 +22,21 @@ require "active_record/type/spatial"
22
22
 
23
23
  # :startdoc:
24
24
 
25
+ ActiveRecord::ConnectionAdapters.register(
26
+ "mysql2rgeo",
27
+ "ActiveRecord::ConnectionAdapters::Mysql2RgeoAdapter",
28
+ "active_record/connection_adapters/mysql2rgeo_adapter"
29
+ )
30
+
25
31
  module ActiveRecord
26
32
  module ConnectionHandling # :nodoc:
27
33
  # Establishes a connection to the database that's used by all Active Record objects.
28
34
  def mysql2rgeo_connection(config)
29
35
  config = config.symbolize_keys
30
36
  config[:flags] ||= 0
37
+ config[:database_timezone] ||= ConnectionAdapters::AbstractAdapter.validate_default_timezone(config[:default_timezone])
31
38
 
32
- if config[:flags].kind_of? Array
39
+ if config[:flags].is_a? Array
33
40
  config[:flags].push "FOUND_ROWS"
34
41
  else
35
42
  config[:flags] |= Mysql2::Client::FOUND_ROWS
@@ -39,7 +46,7 @@ module ActiveRecord
39
46
  ConnectionAdapters::Mysql2RgeoAdapter.new_client(config),
40
47
  logger,
41
48
  nil,
42
- config,
49
+ config
43
50
  )
44
51
  end
45
52
  end
@@ -47,37 +54,34 @@ module ActiveRecord
47
54
  module ConnectionAdapters
48
55
  class Mysql2RgeoAdapter < Mysql2Adapter
49
56
  ADAPTER_NAME = "Mysql2Rgeo"
50
- MINIMUM_SUPPORTED_VERSION = "8.0.0"
51
57
 
52
58
  include Mysql2Rgeo::SchemaStatements
53
59
 
54
60
  SPATIAL_COLUMN_OPTIONS =
55
61
  {
56
- geography: { type: "geometry", geographic: true },
57
- geometry: {},
58
- geometrycollection: {},
62
+ geography: { type: "geometry", geographic: true },
63
+ geometry: {},
64
+ geometrycollection: {},
59
65
  geometry_collection: { type: "geometrycollection" },
60
- linestring: {},
61
- line_string: { type: "linestring" },
62
- multilinestring: {},
63
- multi_line_string: { type: "multilinestring" },
64
- multipoint: {},
65
- multi_point: { type: "multipoint" },
66
- multipolygon: {},
67
- multi_polygon: { type: "multipolygon" },
68
- spatial: { type: "geometry" },
69
- point: {},
70
- polygon: {},
71
- st_point: { type: "point" },
72
- st_polygon: { type: "polygon" }
66
+ linestring: {},
67
+ line_string: { type: "linestring" },
68
+ multilinestring: {},
69
+ multi_line_string: { type: "multilinestring" },
70
+ multipoint: {},
71
+ multi_point: { type: "multipoint" },
72
+ multipolygon: {},
73
+ multi_polygon: { type: "multipolygon" },
74
+ spatial: { type: "geometry" },
75
+ point: {},
76
+ polygon: {},
77
+ st_point: { type: "point" },
78
+ st_polygon: { type: "polygon" }
73
79
  }.freeze
74
80
 
75
- # MySQL uses SRID 0 when a spatial column does not declare one explicitly.
76
81
  DEFAULT_SRID = 0
77
82
 
78
- def initialize(connection, logger, connection_options, config)
83
+ def initialize(...)
79
84
  super
80
- verify_supported_database_version!
81
85
 
82
86
  @visitor = Arel::Visitors::Mysql2Rgeo.new(self)
83
87
  end
@@ -93,86 +97,104 @@ module ActiveRecord
93
97
  def native_database_types
94
98
  # Add spatial types
95
99
  # Reference: https://dev.mysql.com/doc/refman/5.6/en/spatial-type-overview.html
96
- super.merge(
97
- geometry: { name: "geometry" },
98
- geometrycollection: { name: "geometrycollection" },
99
- line_string: { name: "linestring" },
100
- linestring: { name: "linestring" },
101
- st_point: { name: "point" },
102
- st_polygon: { name: "polygon" },
103
- multi_line_string: { name: "multilinestring" },
104
- multi_point: { name: "multipoint" },
105
- multi_polygon: { name: "multipolygon" },
106
- spatial: { name: "geometry" },
107
- point: { name: "point" },
108
- polygon: { name: "polygon" }
109
- )
100
+ self.class.native_database_types
110
101
  end
111
102
 
112
103
  class << self
104
+ def native_database_types
105
+ # ActiveRecord < 8.1 only has the instance method, backed by NATIVE_DATABASE_TYPES.
106
+ (defined?(super) ? super : self::NATIVE_DATABASE_TYPES).merge(
107
+ geography: { name: "geometry" },
108
+ geometry: { name: "geometry" },
109
+ geometrycollection: { name: "geometrycollection" },
110
+ line_string: { name: "linestring" },
111
+ linestring: { name: "linestring" },
112
+ st_point: { name: "point" },
113
+ st_polygon: { name: "polygon" },
114
+ multi_line_string: { name: "multilinestring" },
115
+ multi_point: { name: "multipoint" },
116
+ multi_polygon: { name: "multipolygon" },
117
+ spatial: { name: "geometry" },
118
+ point: { name: "point" },
119
+ polygon: { name: "polygon" }
120
+ )
121
+ end
122
+
123
+ def extended_type_map(emulate_booleans:, default_timezone: nil)
124
+ Type::TypeMap.new(emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP).tap do |m|
125
+ register_class_with_precision m, /\A[^(]*time/i, Type::Time, timezone: default_timezone
126
+ register_class_with_precision m, /\A[^(]*datetime/i, Type::DateTime, timezone: default_timezone
127
+ m.alias_type(/\A[^(]*timestamp/i, "datetime")
128
+ end
129
+ end
113
130
 
114
131
  private
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
+
133
+ def initialize_type_map(m)
134
+ super
135
+
136
+ {
137
+ "geography" => "geometry",
138
+ "geometry" => "geometry",
139
+ "geometry_collection" => "geometrycollection",
140
+ "line_string" => "linestring",
141
+ "multi_line_string" => "multilinestring",
142
+ "multi_point" => "multipoint",
143
+ "multi_polygon" => "multipolygon",
144
+ "st_point" => "point",
145
+ "st_polygon" => "polygon",
146
+ }.each do |registered_type, geo_type|
147
+ m.register_type(registered_type) do |sql_type|
148
+ Type::Spatial.new(sql_type.to_s, geo_type: geo_type)
132
149
  end
150
+ end
133
151
 
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
152
+ [
153
+ /\Ageometry(?:\(.*\))?\z/i,
154
+ /\Ageography(?:\(.*\))?\z/i,
155
+ /\Apoint(?:\s.*)?\z/i,
156
+ /\Alinestring(?:\s.*)?\z/i,
157
+ /\Apolygon(?:\s.*)?\z/i,
158
+ /\Amultipoint(?:\s.*)?\z/i,
159
+ /\Amultilinestring(?:\s.*)?\z/i,
160
+ /\Amultipolygon(?:\s.*)?\z/i,
161
+ /\Ageometrycollection(?:\s.*)?\z/i,
162
+ ].each do |pattern|
163
+ m.register_type(pattern) do |sql_type|
164
+ Type::Spatial.new(sql_type.to_s)
148
165
  end
166
+ end
149
167
 
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)
160
- end
168
+ {
169
+ st_point: "point",
170
+ st_polygon: "polygon",
171
+ line_string: "linestring",
172
+ multi_line_string: "multilinestring",
173
+ multi_point: "multipoint",
174
+ multi_polygon: "multipolygon",
175
+ }.each do |alias_type, geo_type|
176
+ ActiveRecord::Type.register(alias_type) do |_, **kwargs|
177
+ Type::Spatial.new(geo_type, geo_type: geo_type, **kwargs)
161
178
  end
162
179
  end
180
+ end
163
181
  end
164
182
 
165
183
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
166
184
  TYPE_MAP_WITH_BOOLEAN = Type::TypeMap.new(TYPE_MAP).tap do |m|
167
- m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
185
+ m.register_type(/^tinyint\(1\)/i, Type::Boolean.new)
168
186
  end
169
187
 
170
188
  def supports_spatial?
171
- !mariadb? && database_version >= MINIMUM_SUPPORTED_VERSION
189
+ !mariadb? && version >= "5.7.6"
190
+ end
191
+
192
+ def supports_partitioned_indexes?
193
+ false
172
194
  end
173
195
 
174
196
  def adapter_name
175
- ADAPTER_NAME
197
+ "Mysql2Rgeo"
176
198
  end
177
199
 
178
200
  def quote(value)
@@ -203,16 +225,16 @@ module ActiveRecord
203
225
  end
204
226
 
205
227
  private
206
- def verify_supported_database_version!
207
- return if database_version >= MINIMUM_SUPPORTED_VERSION
208
228
 
209
- raise ActiveRecord::ConnectionNotEstablished,
210
- "#{ADAPTER_NAME} supports MySQL #{MINIMUM_SUPPORTED_VERSION}+ only (detected #{database_version})"
211
- end
212
-
213
- def type_map
229
+ def type_map
230
+ if (key = extended_type_map_key)
231
+ self.class::EXTENDED_TYPE_MAPS.compute_if_absent(key) do
232
+ self.class.extended_type_map(**key)
233
+ end
234
+ else
214
235
  emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
215
236
  end
237
+ end
216
238
  end
217
239
  end
218
240
  end
@@ -10,13 +10,14 @@ module ActiveRecord
10
10
  # "geometry NOT NULL"
11
11
  # "geometry"
12
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
13
+ @sql_type = -(geographic ? "geography" : sql_type.to_s)
14
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)
15
+ @geo_type = -self.class.normalize_geo_type(geo_type || parsed_geo_type).to_s
16
16
  @srid = srid || parsed_srid
17
17
  @has_z = has_z || parsed_has_z
18
18
  @has_m = has_m || parsed_has_m
19
19
  @geographic = geographic || parsed_geographic
20
+ freeze
20
21
  end
21
22
 
22
23
  # sql_type: geometry, geometry(Point), geometry(Point,4326), ...
@@ -41,9 +42,7 @@ module ActiveRecord
41
42
  has_z = Regexp.last_match(2).casecmp("z").zero?
42
43
  has_m = Regexp.last_match(3).casecmp("m").zero?
43
44
  end
44
- if params.last =~ /(\d+)/
45
- srid = Regexp.last_match(1).to_i
46
- end
45
+ srid = Regexp.last_match(1).to_i if params.last =~ /(\d+)/
47
46
  elsif sql_type =~ /\A(geography|geometry)\z/i
48
47
  geographic = Regexp.last_match(1).casecmp("geography").zero?
49
48
  geo_type = Regexp.last_match(1)
@@ -72,16 +71,16 @@ module ActiveRecord
72
71
  end
73
72
  end
74
73
 
75
- def spatial_factory
76
- @spatial_factories ||= {}
77
-
78
- @spatial_factories[@srid] ||=
79
- RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
80
- geo_type: @geo_type,
81
- has_m: @has_m,
82
- has_z: @has_z,
83
- sql_type: @geographic ? "geography" : "geometry",
84
- srid: @srid
74
+ def spatial_factory(srid = @srid)
75
+ # Don't use instance variable caching since Type objects are frozen in Rails 8.1+
76
+ # The SpatialFactoryStore already caches factories internally
77
+ # Use provided srid or fall back to instance srid
78
+ RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
79
+ geo_type: @geo_type,
80
+ has_m: @has_m,
81
+ has_z: @has_z,
82
+ sql_type: @geographic ? "geography" : "geometry",
83
+ srid: srid
85
84
  )
86
85
  end
87
86
 
@@ -118,37 +117,35 @@ module ActiveRecord
118
117
  def cast_value(value)
119
118
  return if value.nil?
120
119
 
121
- ::String === value ? parse_wkt(value) : value
120
+ value.is_a?(::String) ? parse_wkt(value) : value
122
121
  end
123
122
 
124
123
  # convert WKT string into RGeo object
125
124
  def parse_wkt(string)
126
125
  marker = string[4, 1]
127
126
  if ["\x00", "\x01"].include?(marker)
128
- @srid = string[0, 4].unpack1(marker == "\x01" ? "V" : "N")
129
- RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid).parse(string[4..-1])
127
+ srid = string[0, 4].unpack1(marker == "\x01" ? "V" : "N")
128
+ RGeo::WKRep::WKBParser.new(spatial_factory(srid), support_ewkb: true, default_srid: srid).parse(string[4..])
130
129
  elsif string.match?(/\A[0-9a-fA-F]+\z/)
131
- original_srid = @srid
132
130
  parser = RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid)
133
131
 
134
132
  begin
135
133
  return parser.parse([string].pack("H*"))
136
134
  rescue RGeo::Error::ParseError, RGeo::Error::InvalidGeometry
137
- @srid = original_srid
138
135
  end
139
136
 
140
137
  if string[0, 10] =~ /[0-9a-fA-F]{8}0[01]/
141
- @srid = string[0, 8].to_i(16)
142
- @srid = [@srid].pack("V").unpack("N").first if string[9, 1] == "1"
143
- parser = RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid)
144
- return parser.parse([string[8..-1]].pack("H*"))
138
+ srid = string[0, 8].to_i(16)
139
+ srid = [srid].pack("V").unpack1("N") if string[9, 1] == "1"
140
+ parser = RGeo::WKRep::WKBParser.new(spatial_factory(srid), support_ewkb: true, default_srid: srid)
141
+ return parser.parse([string[8..]].pack("H*"))
145
142
  end
146
143
 
147
144
  parser.parse([string].pack("H*"))
148
145
  else
149
146
  string, srid = Arel::Visitors::Mysql2Rgeo.parse_node(string)
150
- @srid = srid.zero? ? @srid : srid
151
- RGeo::WKRep::WKTParser.new(spatial_factory, support_ewkt: true, default_srid: @srid).parse(string)
147
+ srid = @srid if srid.zero?
148
+ RGeo::WKRep::WKTParser.new(spatial_factory(srid), support_ewkt: true, default_srid: srid).parse(string)
152
149
  end
153
150
  rescue RGeo::Error::ParseError, RGeo::Error::InvalidGeometry
154
151
  nil
@@ -156,3 +153,13 @@ module ActiveRecord
156
153
  end
157
154
  end
158
155
  end
156
+
157
+ module ActiveRecord
158
+ module ConnectionAdapters
159
+ module Mysql2Rgeo
160
+ module OID
161
+ Spatial = ActiveRecord::Type::Spatial
162
+ end
163
+ end
164
+ end
165
+ end
@@ -1,3 +1,6 @@
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",
6
+ "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.3.1
4
+ version: 9.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yongdae Hwang
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-03-31 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,28 +15,28 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: 7.1.0
18
+ version: 8.0.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.1.0
25
+ version: 8.0.0
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: rgeo-activerecord
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: 7.0.0
32
+ version: 8.0.0
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
- version: 7.0.0
39
+ version: 8.0.0
41
40
  - !ruby/object:Gem::Dependency
42
41
  name: rake
43
42
  requirement: !ruby/object:Gem::Requirement
@@ -67,19 +66,19 @@ dependencies:
67
66
  - !ruby/object:Gem::Version
68
67
  version: '5.4'
69
68
  - !ruby/object:Gem::Dependency
70
- name: mocha
69
+ name: minitest-excludes
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
72
  - - "~>"
74
73
  - !ruby/object:Gem::Version
75
- version: '1.1'
74
+ version: '2.0'
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: '1.1'
81
+ version: '2.0'
83
82
  - !ruby/object:Gem::Dependency
84
83
  name: benchmark-ips
85
84
  requirement: !ruby/object:Gem::Requirement
@@ -134,10 +133,10 @@ homepage: http://github.com/stadia/activerecord-mysql2rgeo-adapter
134
133
  licenses:
135
134
  - BSD-3-Clause
136
135
  metadata:
136
+ funding_uri: https://opencollective.com/rgeo
137
137
  source_code_uri: https://github.com/stadia/activerecord-mysql2rgeo-adapter
138
138
  documentation_uri: https://github.com/stadia/activerecord-mysql2rgeo-adapter/blob/main/README.md
139
139
  rubygems_mfa_required: 'true'
140
- post_install_message:
141
140
  rdoc_options: []
142
141
  require_paths:
143
142
  - lib
@@ -145,15 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
145
144
  requirements:
146
145
  - - ">="
147
146
  - !ruby/object:Gem::Version
148
- version: 3.0.0
147
+ version: 3.2.0
149
148
  required_rubygems_version: !ruby/object:Gem::Requirement
150
149
  requirements:
151
150
  - - ">="
152
151
  - !ruby/object:Gem::Version
153
152
  version: '0'
154
153
  requirements: []
155
- rubygems_version: 3.3.27
156
- signing_key:
154
+ rubygems_version: 4.0.20
157
155
  specification_version: 4
158
156
  summary: ActiveRecord adapter for MySQL, based on RGeo.
159
157
  test_files: []