activerecord-mysql2rgeo-adapter 6.0.1 → 6.0.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: 46398dbc631a7df55cb3a6364227d7d7aff3bed23e1a442bed6a72394e5f86a6
4
- data.tar.gz: 0dcdf66fd2fcaba12c67c2fc6611c935251f131b183df82c25cd5b246e7b89a8
3
+ metadata.gz: 58d333c713e0bd75f678bd8fb11812edeeccab83beded3b36bc14a25cd0c4f20
4
+ data.tar.gz: 49365cad5609ac644e6d2e9ae1727576c2aa321caf350ca95a2e05bd3689cbdb
5
5
  SHA512:
6
- metadata.gz: 072f382d883353b35f83a9b3463d554696ee6732131aceebfc34aa837d25b0ed04efa38f65427c8bb51132cfc27e74d0e5cbf5992582ada3f2e15a8543d83f34
7
- data.tar.gz: a72920ef741fa5c5220fec549a9ce393aee044e81611a70f062fb76011a264d4c8bc5029e20d0d927293c60fe5c7956a2f0ed6bbfb654fd62da94f5cebda94fe
6
+ metadata.gz: fd02b75e6df3f66ecb3ad82ea531c8ea3d25e5a4d0747a0d8154dc157060330ff9e62ce9da2ef36ee195ce45a9c30d39c6026e07eac7cd0bfb8ec0bba8be297f
7
+ data.tar.gz: 4a5ac24de947e2877bbd1c7bbd105cd80aef155a4759867b31639150294a81236fcfd119d1bc8d8752e27fe2ffff3f34459a745afc9e27a36897a4acb6eacfe6
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Arel # :nodoc:
4
- module Visitors # :nodoc:
3
+ module Arel # :nodoc:
4
+ module Visitors # :nodoc:
5
5
  # Different super-class under JRuby JDBC adapter.
6
6
  MySQLSuperclass = if defined?(::ArJdbc::MySQL::BindSubstitution)
7
7
  ::ArJdbc::MySQL::BindSubstitution
@@ -27,12 +27,55 @@ module Arel # :nodoc:
27
27
  end
28
28
 
29
29
  def visit_String(node, collector)
30
- collector << "#{st_func('ST_WKTToSQL')}(#{quote(node)})"
30
+ 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
31
36
  end
32
37
 
33
38
  def visit_RGeo_ActiveRecord_SpatialNamedFunction(node, collector)
34
39
  aggregate(st_func(node.name), node, collector)
35
40
  end
41
+
42
+ def visit_in_spatial_context(node, collector)
43
+ case node
44
+ when String
45
+ 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
51
+ when RGeo::Feature::Instance
52
+ collector << visit_RGeo_Feature_Instance(node, collector)
53
+ when RGeo::Cartesian::BoundingBox
54
+ collector << visit_RGeo_Cartesian_BoundingBox(node, collector)
55
+ else
56
+ visit(node, collector)
57
+ end
58
+ end
59
+
60
+ def self.parse_node(node)
61
+ value, srid = nil, 0
62
+ if node =~ /.*;.*$/i
63
+ params = Regexp.last_match(0).split(";")
64
+ if params.first =~ /(srid|SRID)=\d*/
65
+ srid = params.first.split("=").last.to_i
66
+ else
67
+ value = params.first
68
+ end
69
+ if params.last =~ /(srid|SRID)=\d*/
70
+ srid = params.last.split("=").last.to_i
71
+ else
72
+ value = params.last
73
+ end
74
+ else
75
+ value = node
76
+ end
77
+ [value, srid]
78
+ end
36
79
  end
37
80
  end
38
81
  end
@@ -15,7 +15,7 @@ module ActiveRecord # :nodoc:
15
15
  mysql2_connection(config)
16
16
  end
17
17
 
18
- alias_method :jdbcmysql2rgeo_connection, :mysql2rgeo_connection
18
+ alias jdbcmysql2rgeo_connection mysql2rgeo_connection
19
19
 
20
20
  else
21
21
 
@@ -39,8 +39,8 @@ module ActiveRecord # :nodoc:
39
39
 
40
40
  client = Mysql2::Client.new(config)
41
41
  ConnectionAdapters::Mysql2RgeoAdapter.new(client, logger, nil, config)
42
- rescue Mysql2::Error => error
43
- if error.message.include?("Unknown database")
42
+ rescue Mysql2::Error => e
43
+ if e.message.include?("Unknown database")
44
44
  raise ActiveRecord::NoDatabaseError
45
45
  else
46
46
  raise
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Mysql2Rgeo
6
+ class SchemaCreation < MySQL::SchemaCreation # :nodoc:
7
+ delegate :supports_expression_index?, to: :@conn, private: true
8
+
9
+ private
10
+
11
+ def add_column_options!(sql, options)
12
+ # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
13
+ # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
14
+ # column to contain NULL, explicitly declare it with the NULL attribute.
15
+ # See https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
16
+ if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key]
17
+ sql << " NULL" unless options[:null] == false || options_include_default?(options)
18
+ end
19
+
20
+ if options[:srid]
21
+ sql << " /*!80003 SRID #{options[:srid]} */"
22
+ end
23
+
24
+ if charset = options[:charset]
25
+ sql << " CHARACTER SET #{charset}"
26
+ end
27
+
28
+ if collation = options[:collation]
29
+ sql << " COLLATE #{collation}"
30
+ end
31
+
32
+ if as = options[:as]
33
+ sql << " AS (#{as})"
34
+ if options[:stored]
35
+ sql << (mariadb? ? " PERSISTENT" : " STORED")
36
+ end
37
+ end
38
+
39
+ add_sql_comment!(super, options[:comment])
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -11,10 +11,13 @@ module ActiveRecord
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
- indexes.select { |idx| idx.type == :spatial }.each { |idx| idx.is_a?(Struct) ? idx.lengths = {} : idx.instance_variable_set(:@lengths, {}) }
14
+ indexes.select do |idx|
15
+ idx.type == :spatial
16
+ end.each { |idx| idx.is_a?(Struct) ? idx.lengths = {} : idx.instance_variable_set(:@lengths, {}) }
15
17
  indexes
16
18
  end
17
19
 
20
+ # override
18
21
  def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
19
22
  if (info = RGeo::ActiveRecord.geometric_type_from_name(type.to_s.delete("_")))
20
23
  type = limit[:type] || type if limit.is_a?(::Hash)
@@ -42,7 +45,8 @@ module ActiveRecord
42
45
 
43
46
  def initialize_type_map(m = type_map)
44
47
  super
45
- %w(
48
+
49
+ %w[
46
50
  geometry
47
51
  geometrycollection
48
52
  point
@@ -51,12 +55,20 @@ module ActiveRecord
51
55
  multipoint
52
56
  multilinestring
53
57
  multipolygon
54
- ).each do |geo_type|
55
- m.register_type(geo_type, Type::Spatial.new(geo_type))
58
+ ].each do |geo_type|
59
+ m.register_type(geo_type) do |sql_type|
60
+ Type::Spatial.new(sql_type)
61
+ end
56
62
  end
57
63
  end
58
64
 
59
65
  private
66
+
67
+ # override
68
+ def schema_creation
69
+ Mysql2Rgeo::SchemaCreation.new(self)
70
+ end
71
+
60
72
  # override
61
73
  def create_table_definition(*args, **options)
62
74
  Mysql2Rgeo::TableDefinition.new(self, *args, **options)
@@ -65,22 +77,35 @@ module ActiveRecord
65
77
  # override
66
78
  def new_column_from_field(table_name, field)
67
79
  type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
68
- if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default])
69
- default, default_function = nil, field[:Default]
70
- else
71
- default, default_function = field[:Default], nil
80
+ default, default_function = field[:Default], nil
81
+
82
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
83
+ default, default_function = nil, default
84
+ elsif type_metadata.extra == "DEFAULT_GENERATED"
85
+ default = +"(#{default})" unless default.start_with?("(")
86
+ default, default_function = nil, default
72
87
  end
73
88
 
89
+ # {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
90
+ spatial = spatial_column_info(table_name).get(field[:Field], type_metadata.sql_type)
91
+
74
92
  SpatialColumn.new(
75
- field[:Field],
76
- default,
77
- type_metadata,
78
- field[:Null] == "YES",
79
- default_function,
80
- collation: field[:Collation],
81
- comment: field[:Comment].presence
93
+ field[:Field],
94
+ default,
95
+ type_metadata,
96
+ field[:Null] == "YES",
97
+ default_function,
98
+ collation: field[:Collation],
99
+ comment: field[:Comment].presence,
100
+ spatial: spatial
82
101
  )
83
102
  end
103
+
104
+ # memoize hash of column infos for tables
105
+ def spatial_column_info(table_name)
106
+ @spatial_column_info ||= {}
107
+ @spatial_column_info[table_name.to_sym] = SpatialColumnInfo.new(self, table_name.to_s)
108
+ end
84
109
  end
85
110
  end
86
111
  end
@@ -4,10 +4,13 @@ module ActiveRecord # :nodoc:
4
4
  module ConnectionAdapters # :nodoc:
5
5
  module Mysql2Rgeo # :nodoc:
6
6
  class SpatialColumn < ConnectionAdapters::MySQL::Column # :nodoc:
7
-
8
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
9
- @geometric_type = nil
10
- if sql_type =~ /geometry|point|linestring|polygon/i
7
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, spatial: nil, **)
8
+ @sql_type_metadata = sql_type_metadata
9
+ if spatial
10
+ # This case comes from an entry in the geometry_columns table
11
+ set_geometric_type_from_name(spatial[:type])
12
+ @srid = spatial[:srid].to_i
13
+ elsif sql_type =~ /geometry|point|linestring|polygon/i
11
14
  build_from_sql_type(sql_type_metadata.sql_type)
12
15
  elsif sql_type_metadata.sql_type =~ /geometry|point|linestring|polygon/i
13
16
  # A geometry column with no geometry_columns entry.
@@ -17,8 +20,7 @@ module ActiveRecord # :nodoc:
17
20
  super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
18
21
  if spatial?
19
22
  if @srid
20
- @limit = { type: @geometric_type.type_name.underscore }
21
- @limit[:srid] = @srid if @srid
23
+ @limit = { type: @geometric_type.type_name.underscore, srid: @srid }
22
24
  end
23
25
  end
24
26
  end
@@ -37,9 +39,9 @@ module ActiveRecord # :nodoc:
37
39
  false
38
40
  end
39
41
 
40
- alias :geographic? :geographic
41
- alias :has_z? :has_z
42
- alias :has_m? :has_m
42
+ alias geographic? geographic
43
+ alias has_z? has_z
44
+ alias has_m? has_m
43
45
 
44
46
  def limit
45
47
  if spatial?
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ module Mysql2Rgeo # :nodoc:
6
+ # Do spatial sql queries for column info and memoize that info.
7
+ class SpatialColumnInfo
8
+ def initialize(adapter, table_name)
9
+ @adapter = adapter
10
+ @table_name = table_name
11
+ end
12
+
13
+ def all
14
+ info = if @adapter.supports_expression_index?
15
+ @adapter.query(
16
+ "SELECT column_name, srs_id, column_type FROM INFORMATION_SCHEMA.Columns WHERE table_name='#{@table_name}'"
17
+ )
18
+ else
19
+ @adapter.query(
20
+ "SELECT column_name, 0, column_type FROM INFORMATION_SCHEMA.Columns WHERE table_name='#{@table_name}'"
21
+ )
22
+ end
23
+
24
+ result = {}
25
+ info.each do |row|
26
+ name = row[0]
27
+ type = row[2]
28
+ type.sub!(/m$/, "")
29
+ result[name] = {
30
+ name: name,
31
+ srid: row[1].to_i,
32
+ type: type,
33
+ }
34
+ end
35
+ result
36
+ end
37
+
38
+ # do not query the database for non-spatial columns/tables
39
+ def get(column_name, type)
40
+ return unless Mysql2RgeoAdapter.spatial_column_options(type.to_sym)
41
+
42
+ @spatial_column_info ||= all
43
+ @spatial_column_info[column_name]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -15,6 +15,6 @@ module RGeo
15
15
  end
16
16
 
17
17
  # Allow chaining of spatial expressions from attributes
18
- Arel::Attribute.send :include, RGeo::ActiveRecord::Mysql2Rgeo::SpatialExpressions
19
- RGeo::ActiveRecord::SpatialConstantNode.send :include, RGeo::ActiveRecord::Mysql2Rgeo::SpatialExpressions
20
- RGeo::ActiveRecord::SpatialNamedFunction.send :include, RGeo::ActiveRecord::Mysql2Rgeo::SpatialExpressions
18
+ Arel::Attribute.include RGeo::ActiveRecord::Mysql2Rgeo::SpatialExpressions
19
+ RGeo::ActiveRecord::SpatialConstantNode.include RGeo::ActiveRecord::Mysql2Rgeo::SpatialExpressions
20
+ RGeo::ActiveRecord::SpatialNamedFunction.include RGeo::ActiveRecord::Mysql2Rgeo::SpatialExpressions
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecord # :nodoc:
4
- module ConnectionAdapters # :nodoc:
5
- module Mysql2Rgeo # :nodoc:
6
- class TableDefinition < MySQL::TableDefinition # :nodoc:
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ module Mysql2Rgeo # :nodoc:
6
+ class TableDefinition < MySQL::TableDefinition # :nodoc:
7
7
  include ColumnMethods
8
8
  # super: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
9
9
  def new_column_definition(name, type, **options)
10
10
  if (info = Mysql2RgeoAdapter.spatial_column_options(type.to_sym))
11
- if (limit = options.delete(:limit))
12
- options.merge!(limit) if limit.is_a?(::Hash)
11
+ if (limit = options.delete(:limit)) && limit.is_a?(::Hash)
12
+ options.merge!(limit)
13
13
  end
14
14
 
15
15
  geo_type = ColumnDefinitionUtils.geo_type(options[:type] || type || info[:type])
@@ -27,7 +27,7 @@ module ActiveRecord # :nodoc:
27
27
  module ColumnDefinitionUtils
28
28
  class << self
29
29
  def geo_type(type = "GEOMETRY")
30
- type.to_s.delete('_').upcase
30
+ type.to_s.delete("_").upcase
31
31
  end
32
32
 
33
33
  def default_srid(options)
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Mysql2Rgeo
6
- VERSION = "6.0.1"
6
+ VERSION = "6.0.2"
7
7
  end
8
8
  end
9
- end
9
+ end
@@ -17,9 +17,11 @@ end
17
17
  require "active_record/connection_adapters/mysql2_adapter"
18
18
  require "active_record/connection_adapters/mysql2rgeo/version"
19
19
  require "active_record/connection_adapters/mysql2rgeo/column_methods"
20
+ require "active_record/connection_adapters/mysql2rgeo/schema_creation"
20
21
  require "active_record/connection_adapters/mysql2rgeo/schema_statements"
21
22
  require "active_record/connection_adapters/mysql2rgeo/spatial_table_definition"
22
23
  require "active_record/connection_adapters/mysql2rgeo/spatial_column"
24
+ require "active_record/connection_adapters/mysql2rgeo/spatial_column_info"
23
25
  require "active_record/connection_adapters/mysql2rgeo/spatial_expressions"
24
26
  require "active_record/connection_adapters/mysql2rgeo/arel_tosql"
25
27
  require "active_record/type/spatial"
@@ -43,7 +45,7 @@ module ActiveRecord
43
45
  spatial: { type: "geometry" },
44
46
  point: {},
45
47
  polygon: {}
46
- }
48
+ }.freeze
47
49
 
48
50
  # http://postgis.17.x6.nabble.com/Default-SRID-td5001115.html
49
51
  DEFAULT_SRID = 0
@@ -21,7 +21,6 @@ module ActiveRecord
21
21
  # srid: 1234
22
22
  def self.parse_sql_type(sql_type)
23
23
  geo_type, srid = nil, 0, false, false
24
-
25
24
  if sql_type =~ /(geography|geometry)\((.*)\)$/i
26
25
  # geometry(Point)
27
26
  # geometry(Point,4326)
@@ -67,7 +66,7 @@ module ActiveRecord
67
66
 
68
67
  geo_value = cast_value(value)
69
68
 
70
- # TODO - only valid types should be allowed
69
+ # TODO: - only valid types should be allowed
71
70
  # e.g. linestring is not valid for point column
72
71
  raise "maybe should raise" unless RGeo::Feature::Geometry.check_type(geo_value)
73
72
 
@@ -82,16 +81,18 @@ module ActiveRecord
82
81
  ::String === value ? parse_wkt(value) : value
83
82
  end
84
83
 
84
+ # convert WKT string into RGeo object
85
85
  def parse_wkt(string)
86
86
  marker = string[4, 1]
87
- if marker == "\x00" || marker == "\x01"
88
- srid = string[0, 4].unpack(marker == "\x01" ? "V" : "N").first
89
- RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: srid).parse(string[4..-1])
87
+ if ["\x00", "\x01"].include?(marker)
88
+ @srid = string[0, 4].unpack1(marker == "\x01" ? "V" : "N")
89
+ RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid).parse(string[4..-1])
90
90
  elsif string[0, 10] =~ /[0-9a-fA-F]{8}0[01]/
91
- srid = string[0, 8].to_i(16)
92
- srid = [srid].pack("V").unpack("N").first if string[9, 1] == "1"
91
+ @srid = string[0, 8].to_i(16)
92
+ @srid = [@srid].pack("V").unpack("N").first if string[9, 1] == "1"
93
93
  RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: srid).parse(string[8..-1])
94
94
  else
95
+ string, @srid = Arel::Visitors::Mysql2Rgeo.parse_node(string)
95
96
  RGeo::WKRep::WKTParser.new(spatial_factory, support_ewkt: true, default_srid: @srid).parse(string)
96
97
  end
97
98
  rescue RGeo::Error::ParseError, RGeo::Error::InvalidGeometry
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-mysql2rgeo-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.1
4
+ version: 6.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yongdae Hwang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-12 00:00:00.000000000 Z
11
+ date: 2021-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -106,8 +106,10 @@ files:
106
106
  - lib/active_record/connection_adapters/mysql2rgeo/arel_tosql.rb
107
107
  - lib/active_record/connection_adapters/mysql2rgeo/column_methods.rb
108
108
  - lib/active_record/connection_adapters/mysql2rgeo/create_connection.rb
109
+ - lib/active_record/connection_adapters/mysql2rgeo/schema_creation.rb
109
110
  - lib/active_record/connection_adapters/mysql2rgeo/schema_statements.rb
110
111
  - lib/active_record/connection_adapters/mysql2rgeo/spatial_column.rb
112
+ - lib/active_record/connection_adapters/mysql2rgeo/spatial_column_info.rb
111
113
  - lib/active_record/connection_adapters/mysql2rgeo/spatial_expressions.rb
112
114
  - lib/active_record/connection_adapters/mysql2rgeo/spatial_table_definition.rb
113
115
  - lib/active_record/connection_adapters/mysql2rgeo/version.rb