activerecord-trilogis-adapter 7.0.2 → 8.0.1
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 +4 -4
- data/LICENSE +21 -0
- data/lib/active_record/connection_adapters/trilogis/arel_tosql.rb +120 -45
- data/lib/active_record/connection_adapters/trilogis/railtie.rb +21 -14
- data/lib/active_record/connection_adapters/trilogis/schema_creation.rb +10 -7
- data/lib/active_record/connection_adapters/trilogis/schema_statements.rb +171 -40
- data/lib/active_record/connection_adapters/trilogis/spatial_column.rb +67 -42
- data/lib/active_record/connection_adapters/trilogis/spatial_column_info.rb +39 -20
- data/lib/active_record/connection_adapters/trilogis/spatial_expressions.rb +78 -4
- data/lib/active_record/connection_adapters/trilogis/spatial_table_definition.rb +79 -22
- data/lib/active_record/connection_adapters/trilogis/version.rb +1 -1
- data/lib/active_record/connection_adapters/trilogis_adapter.rb +220 -111
- data/lib/active_record/dependency_loader.rb +38 -0
- data/lib/active_record/tasks/trilogis_database_tasks.rb +2 -1
- data/lib/active_record/type/spatial.rb +217 -63
- data/lib/activerecord-trilogis-adapter.rb +15 -2
- metadata +104 -35
- data/LICENSE.txt +0 -29
- data/lib/active_record/connection_adapters/trilogis/column_methods.rb +0 -54
- data/lib/active_record/connection_adapters/trilogis/connection.rb +0 -17
- data/lib/active_record/connection_adapters/trilogis/rails/dbconsole.rb +0 -48
|
@@ -1,62 +1,87 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module ActiveRecord
|
|
4
|
-
module ConnectionAdapters
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
14
|
-
build_from_sql_type(sql_type_metadata.sql_type)
|
|
15
|
-
elsif sql_type_metadata.sql_type =~ /geometry|point|linestring|polygon/i
|
|
16
|
-
# A geometry column with no geometry_columns entry.
|
|
17
|
-
# @geometric_type = geo_type_from_sql_type(sql_type)
|
|
18
|
-
build_from_sql_type(sql_type_metadata.sql_type)
|
|
19
|
-
end
|
|
20
|
-
super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
|
|
21
|
-
if spatial? && @srid
|
|
22
|
-
@limit = {type: geometric_type.type_name.underscore, srid: @srid}
|
|
23
|
-
end
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
# Add spatial? method to MySQL::Column for compatibility
|
|
6
|
+
module MySQL
|
|
7
|
+
class Column
|
|
8
|
+
def spatial?
|
|
9
|
+
false
|
|
24
10
|
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
25
13
|
|
|
26
|
-
|
|
14
|
+
module Trilogis
|
|
15
|
+
class SpatialColumn < ActiveRecord::ConnectionAdapters::MySQL::Column
|
|
16
|
+
attr_reader :geometric_type, :srid, :geo_type_name
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
# Map SQL type strings to RGeo type classes
|
|
19
|
+
GEOMETRIC_TYPES = {
|
|
20
|
+
"geometry" => RGeo::Feature::Geometry,
|
|
21
|
+
"point" => RGeo::Feature::Point,
|
|
22
|
+
"linestring" => RGeo::Feature::LineString,
|
|
23
|
+
"polygon" => RGeo::Feature::Polygon,
|
|
24
|
+
"multipoint" => RGeo::Feature::MultiPoint,
|
|
25
|
+
"multilinestring" => RGeo::Feature::MultiLineString,
|
|
26
|
+
"multipolygon" => RGeo::Feature::MultiPolygon,
|
|
27
|
+
"geometrycollection" => RGeo::Feature::GeometryCollection
|
|
28
|
+
}.freeze
|
|
31
29
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
def initialize(name, default, sql_type_metadata = nil, null = true,
|
|
31
|
+
default_function = nil, collation: nil, comment: nil, spatial_info: nil, **)
|
|
32
|
+
super(name, default, sql_type_metadata, null, default_function, collation: collation, comment: comment)
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
alias has_m? has_m
|
|
34
|
+
return unless spatial?
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
if spatial_info
|
|
37
|
+
# Use spatial info from INFORMATION_SCHEMA if available
|
|
38
|
+
@geo_type_name = spatial_info[:type].to_s.downcase
|
|
39
|
+
@geometric_type = GEOMETRIC_TYPES[@geo_type_name] || RGeo::Feature::Geometry
|
|
40
|
+
@srid = spatial_info[:srid] || 0
|
|
41
|
+
@has_z = spatial_info[:has_z] || false
|
|
42
|
+
@has_m = spatial_info[:has_m] || false
|
|
43
|
+
else
|
|
44
|
+
# Fallback to extracting from SQL type
|
|
45
|
+
@geo_type_name = sql_type_metadata.sql_type.to_s.downcase
|
|
46
|
+
type_info = Type::Spatial.new(@geo_type_name)
|
|
47
|
+
@geometric_type = GEOMETRIC_TYPES[@geo_type_name] || RGeo::Feature::Geometry
|
|
48
|
+
@srid = type_info.srid || 0
|
|
49
|
+
@has_z = false
|
|
50
|
+
@has_m = false
|
|
51
|
+
end
|
|
41
52
|
end
|
|
42
53
|
|
|
43
|
-
def
|
|
44
|
-
|
|
54
|
+
def spatial?
|
|
55
|
+
TrilogisAdapter::SPATIAL_COLUMN_TYPES.include?(sql_type_metadata.sql_type.downcase)
|
|
45
56
|
end
|
|
46
57
|
|
|
47
|
-
def
|
|
48
|
-
|
|
58
|
+
def has_z?
|
|
59
|
+
@has_z || false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def has_m?
|
|
63
|
+
@has_m || false
|
|
49
64
|
end
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
# Return Rails type for schema dumper
|
|
67
|
+
# Returns the actual geometric type (point, linestring, etc.) as symbol
|
|
68
|
+
# This allows schema dumper to generate t.point, t.linestring, etc.
|
|
69
|
+
def type
|
|
70
|
+
return super unless spatial?
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
# Return actual geometric type as symbol
|
|
73
|
+
# This matches PostGIS approach and enables proper schema dumping
|
|
74
|
+
@geo_type_name&.to_sym || :geometry
|
|
55
75
|
end
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
# Return limit as hash with spatial metadata for schema dumper
|
|
78
|
+
# Only includes SRID (type is already in column type)
|
|
79
|
+
def limit
|
|
80
|
+
return super unless spatial?
|
|
81
|
+
|
|
82
|
+
# Only include SRID in limit
|
|
83
|
+
# Type information is in the type() method
|
|
84
|
+
{ srid: @srid }.compact
|
|
60
85
|
end
|
|
61
86
|
end
|
|
62
87
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module ActiveRecord
|
|
4
|
-
module ConnectionAdapters
|
|
5
|
-
module Trilogis
|
|
6
|
-
#
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
20
|
-
RGeo
|
|
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
|
|
4
|
-
module ConnectionAdapters
|
|
5
|
-
module Trilogis
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
11
|
+
column(name, options[:type], **options)
|
|
12
|
+
end
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
48
|
+
def polygon(name, options = {})
|
|
49
|
+
column(name, :polygon, **options)
|
|
24
50
|
end
|
|
25
51
|
end
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|