activerecord-mysql2rgeo-adapter 6.0.1 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
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