activerecord-trilogis-adapter 7.0.1 → 8.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.
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecord # :nodoc:
4
- module ConnectionAdapters # :nodoc:
5
- module Trilogis # :nodoc:
6
- # Do spatial sql queries for column info and memoize that info.
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Trilogis
6
+ # Queries MySQL INFORMATION_SCHEMA for spatial column metadata
7
+ # and caches the results for performance
7
8
  class SpatialColumnInfo
8
9
  def initialize(adapter, table_name)
9
10
  @adapter = adapter
@@ -11,30 +12,48 @@ module ActiveRecord # :nodoc:
11
12
  end
12
13
 
13
14
  def all
14
- info = @adapter.query(
15
- "SELECT column_name, srs_id, column_type FROM INFORMATION_SCHEMA.Columns WHERE table_name='#{@table_name}'"
16
- )
15
+ # Query MySQL's ST_GEOMETRY_COLUMNS view for SRID info
16
+ # This view properly returns SRS_ID for spatial columns
17
+ sql = <<~SQL.squish
18
+ SELECT
19
+ gc.COLUMN_NAME as column_name,
20
+ gc.SRS_ID as srs_id,
21
+ gc.GEOMETRY_TYPE_NAME as geometry_type,
22
+ c.COLUMN_TYPE as column_type
23
+ FROM INFORMATION_SCHEMA.ST_GEOMETRY_COLUMNS gc
24
+ JOIN INFORMATION_SCHEMA.COLUMNS c
25
+ ON gc.TABLE_SCHEMA = c.TABLE_SCHEMA
26
+ AND gc.TABLE_NAME = c.TABLE_NAME
27
+ AND gc.COLUMN_NAME = c.COLUMN_NAME
28
+ WHERE gc.TABLE_SCHEMA = DATABASE()
29
+ AND gc.TABLE_NAME = #{@adapter.quote(@table_name)}
30
+ SQL
17
31
 
18
32
  result = {}
19
- info.each do |row|
20
- name = row[0]
21
- type = row[2]
22
- type.sub!(/m$/, "")
23
- result[name] = {
24
- name: name,
25
- srid: row[1].to_i,
26
- type: type,
33
+ @adapter.exec_query(sql, "SCHEMA").each do |row|
34
+ column_name = row["column_name"] || row["COLUMN_NAME"]
35
+ srs_id = row["srs_id"] || row["SRS_ID"]
36
+ row["geometry_type"] || row["GEOMETRY_TYPE"]
37
+ column_type = (row["column_type"] || row["COLUMN_TYPE"]).to_s.sub(/m$/, "")
38
+
39
+ result[column_name] = {
40
+ name: column_name,
41
+ srid: srs_id.to_i,
42
+ type: column_type
27
43
  }
28
44
  end
29
45
  result
30
46
  end
31
47
 
32
- # do not query the database for non-spatial columns/tables
33
- def get(column_name, type)
34
- return unless TrilogisAdapter.spatial_column_options(type.to_sym)
48
+ # Get spatial info for a specific column if it's spatial
49
+ # Returns nil for non-spatial columns to avoid unnecessary queries
50
+ def get(column_name, sql_type)
51
+ # Only query for known spatial types
52
+ return unless TrilogisAdapter::SPATIAL_COLUMN_TYPES.include?(sql_type.to_s.downcase)
35
53
 
36
- @spatial_column_info ||= all
37
- @spatial_column_info[column_name]
54
+ # Don't memoize - always query fresh data to avoid stale cache issues
55
+ # when columns are added during tests
56
+ all[column_name]
38
57
  end
39
58
  end
40
59
  end
@@ -3,18 +3,92 @@
3
3
  module RGeo
4
4
  module ActiveRecord
5
5
  module Trilogis
6
+ # MySQL-specific spatial expressions
6
7
  module SpatialExpressions
8
+ # MySQL ST_Distance_Sphere for geographic distance calculations
7
9
  def st_distance_sphere(rhs, units = nil)
8
10
  args = [self, rhs]
9
11
  args << units.to_s if units
10
12
  SpatialNamedFunction.new("ST_Distance_Sphere", args, [false, true, true, false])
11
13
  end
14
+
15
+ # Additional MySQL spatial functions
16
+ def st_buffer(distance)
17
+ SpatialNamedFunction.new("ST_Buffer", [self, distance], [false, true, false])
18
+ end
19
+
20
+ def st_contains(rhs)
21
+ SpatialNamedFunction.new("ST_Contains", [self, rhs], [false, true, true])
22
+ end
23
+
24
+ def st_within(rhs)
25
+ SpatialNamedFunction.new("ST_Within", [self, rhs], [false, true, true])
26
+ end
27
+
28
+ def st_intersects(rhs)
29
+ SpatialNamedFunction.new("ST_Intersects", [self, rhs], [false, true, true])
30
+ end
31
+
32
+ def st_crosses(rhs)
33
+ SpatialNamedFunction.new("ST_Crosses", [self, rhs], [false, true, true])
34
+ end
35
+
36
+ def st_touches(rhs)
37
+ SpatialNamedFunction.new("ST_Touches", [self, rhs], [false, true, true])
38
+ end
39
+
40
+ def st_overlaps(rhs)
41
+ SpatialNamedFunction.new("ST_Overlaps", [self, rhs], [false, true, true])
42
+ end
43
+
44
+ def st_equals(rhs)
45
+ SpatialNamedFunction.new("ST_Equals", [self, rhs], [false, true, true])
46
+ end
47
+
48
+ def st_disjoint(rhs)
49
+ SpatialNamedFunction.new("ST_Disjoint", [self, rhs], [false, true, true])
50
+ end
51
+
52
+ def st_area
53
+ SpatialNamedFunction.new("ST_Area", [self], [false, true])
54
+ end
55
+
56
+ def st_length
57
+ SpatialNamedFunction.new("ST_Length", [self], [false, true])
58
+ end
59
+
60
+ def st_centroid
61
+ SpatialNamedFunction.new("ST_Centroid", [self], [false, true])
62
+ end
63
+
64
+ def st_envelope
65
+ SpatialNamedFunction.new("ST_Envelope", [self], [false, true])
66
+ end
67
+
68
+ def st_astext
69
+ SpatialNamedFunction.new("ST_AsText", [self], [false, true])
70
+ end
71
+
72
+ def st_asbinary
73
+ SpatialNamedFunction.new("ST_AsBinary", [self], [false, true])
74
+ end
75
+
76
+ def st_srid
77
+ SpatialNamedFunction.new("ST_SRID", [self], [false, true])
78
+ end
12
79
  end
13
80
  end
14
81
  end
15
82
  end
16
83
 
17
- # Allow chaining of spatial expressions from attributes
18
- Arel::Attribute.include RGeo::ActiveRecord::Trilogis::SpatialExpressions
19
- RGeo::ActiveRecord::SpatialConstantNode.include RGeo::ActiveRecord::Trilogis::SpatialExpressions
20
- RGeo::ActiveRecord::SpatialNamedFunction.include RGeo::ActiveRecord::Trilogis::SpatialExpressions
84
+ # Allow chaining of spatial expressions from Arel attributes
85
+ Arel::Attribute.include RGeo::ActiveRecord::Trilogis::SpatialExpressions if defined?(Arel::Attribute)
86
+
87
+ # Include in RGeo spatial nodes if they exist
88
+ if defined?(RGeo::ActiveRecord::SpatialConstantNode)
89
+ RGeo::ActiveRecord::SpatialConstantNode.include RGeo::ActiveRecord::Trilogis::SpatialExpressions
90
+ end
91
+
92
+ if defined?(RGeo::ActiveRecord::SpatialNamedFunction)
93
+ RGeo::ActiveRecord::SpatialNamedFunction.include RGeo::ActiveRecord::Trilogis::SpatialExpressions
94
+ end
@@ -1,36 +1,93 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecord # :nodoc:
4
- module ConnectionAdapters # :nodoc:
5
- module Trilogis # :nodoc:
6
- class TableDefinition < MySQL::TableDefinition # :nodoc:
7
- include ColumnMethods
8
- # super: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
9
- def new_column_definition(name, type, **options)
10
- if (info = TrilogisAdapter.spatial_column_options(type.to_sym))
11
- if (limit = options.delete(:limit)) && limit.is_a?(::Hash)
12
- options.merge!(limit)
13
- end
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Trilogis
6
+ module ColumnMethods
7
+ # Generic spatial column method
8
+ def spatial(name, options = {})
9
+ raise "You must set a type. For example: 't.spatial :location, type: :point'" unless options[:type]
14
10
 
15
- geo_type = ColumnDefinitionUtils.geo_type(options[:type] || type || info[:type])
11
+ column(name, options[:type], **options)
12
+ end
16
13
 
17
- options[:spatial_type] = geo_type
18
- column = super(name, geo_type.downcase.to_sym, **options)
19
- else
20
- column = super(name, type, **options)
21
- end
14
+ # Define spatial column types with both underscore and non-underscore versions
15
+ def geometry(name, options = {})
16
+ column(name, :geometry, **options)
17
+ end
18
+
19
+ def geometrycollection(name, options = {})
20
+ column(name, :geometrycollection, **options)
21
+ end
22
+ alias geometry_collection geometrycollection
23
+
24
+ def linestring(name, options = {})
25
+ column(name, :linestring, **options)
26
+ end
27
+ alias line_string linestring
28
+
29
+ def multilinestring(name, options = {})
30
+ column(name, :multilinestring, **options)
31
+ end
32
+ alias multi_line_string multilinestring
33
+
34
+ def multipoint(name, options = {})
35
+ column(name, :multipoint, **options)
36
+ end
37
+ alias multi_point multipoint
38
+
39
+ def multipolygon(name, options = {})
40
+ column(name, :multipolygon, **options)
41
+ end
42
+ alias multi_polygon multipolygon
43
+
44
+ def point(name, options = {})
45
+ column(name, :point, **options)
46
+ end
22
47
 
23
- column
48
+ def polygon(name, options = {})
49
+ column(name, :polygon, **options)
24
50
  end
25
51
  end
26
52
 
27
- module ColumnDefinitionUtils
28
- class << self
29
- def geo_type(type = "GEOMETRY")
30
- type.to_s.delete("_").upcase
53
+ class TableDefinition < ActiveRecord::ConnectionAdapters::MySQL::TableDefinition
54
+ include ColumnMethods
55
+
56
+ # Override column to handle spatial types
57
+ def column(name, type, index: nil, **options)
58
+ # Store spatial-specific options before processing
59
+ spatial_options = {
60
+ srid: options.delete(:srid),
61
+ has_m: options.delete(:has_m),
62
+ has_z: options.delete(:has_z),
63
+ geographic: options.delete(:geographic)
64
+ }.compact
65
+
66
+ # Call super to create column definition
67
+ result = super
68
+
69
+ # Add spatial options back to the column definition if it's a spatial type
70
+ if spatial_type?(type) && (col = @columns_hash[name.to_s])
71
+ spatial_options.each do |key, value|
72
+ col.options[key] = value
73
+ end
31
74
  end
75
+
76
+ # Add spatial index if requested
77
+ @indexes << [name, { type: :spatial }] if index && spatial_type?(type) && [true, :spatial].include?(index)
78
+
79
+ result
80
+ end
81
+
82
+ private
83
+
84
+ def spatial_type?(type)
85
+ TrilogisAdapter::SPATIAL_COLUMN_TYPES.include?(type.to_s)
32
86
  end
33
87
  end
34
88
  end
89
+
90
+ # Include column methods in MySQL::Table for migrations
91
+ MySQL::Table.include Trilogis::ColumnMethods if defined?(MySQL::Table)
35
92
  end
36
93
  end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Trilogis
6
- VERSION = "7.0.1"
6
+ VERSION = "8.0.0"
7
7
  end
8
8
  end
9
9
  end