activerecord-trilogis-adapter 7.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1d6b6ce09a2e73348f084b7b7e31377e967eae10b488f54f596bdc5f403a8426
4
+ data.tar.gz: 39b183fe7a3760381975f41902b4d863b90475877a3b3bee1afc0aaa24fb2ea7
5
+ SHA512:
6
+ metadata.gz: a0626d4c083466e776be97d2b4c009837ae33e74be2ca64a6bf2b216717e0f740f1397554b27c064d0158dc69d4f9a5eac47c82afac78fdaf0655861c38275cb
7
+ data.tar.gz: 14bb624d74ecb71c9b898a4ecf9e3c518433ff462041902e171400a5dabff6773289b4a707617263c6bbe43585e78b5003d11c9e208d22b580a19d3ffb16ee55
data/LICENSE.txt ADDED
@@ -0,0 +1,29 @@
1
+ # -----------------------------------------------------------------------------
2
+ # Copyright 2012 Daniel Azuma
3
+ #
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # * Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ # * Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ # * Neither the name of the copyright holder, nor the names of any other
15
+ # contributors to this software, may be used to endorse or promote products
16
+ # derived from this software without specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ # POSSIBILITY OF SUCH DAMAGE.
29
+ # -----------------------------------------------------------------------------
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc:
4
+ module Visitors # :nodoc:
5
+ class Trilogis < MySQL # :nodoc:
6
+
7
+ include RGeo::ActiveRecord::SpatialToSql
8
+
9
+ if ::Arel::Visitors.const_defined?(:BindVisitor)
10
+ include ::Arel::Visitors::BindVisitor
11
+ end
12
+
13
+ FUNC_MAP = {
14
+ "st_wkttosql" => "ST_GeomFromText",
15
+ "st_wkbtosql" => "ST_GeomFromWKB",
16
+ "st_length" => "ST_Length"
17
+ }.freeze
18
+
19
+ def st_func(standard_name)
20
+ FUNC_MAP[standard_name.downcase] || standard_name
21
+ end
22
+
23
+ def visit_String(node, collector)
24
+ node, srid = Trilogis.parse_node(node)
25
+ collector << wkttosql_statement(node, srid)
26
+ end
27
+
28
+ def visit_RGeo_ActiveRecord_SpatialNamedFunction(node, collector)
29
+ aggregate(st_func(node.name), node, collector)
30
+ end
31
+
32
+ def visit_in_spatial_context(node, collector)
33
+ case node
34
+ when String
35
+ node, srid = Trilogis.parse_node(node)
36
+ collector << wkttosql_statement(node, srid)
37
+ when RGeo::Feature::Instance
38
+ collector << visit_RGeo_Feature_Instance(node, collector)
39
+ when RGeo::Cartesian::BoundingBox
40
+ collector << visit_RGeo_Cartesian_BoundingBox(node, collector)
41
+ else
42
+ visit(node, collector)
43
+ end
44
+ end
45
+
46
+ def self.parse_node(node)
47
+ value, srid = nil, 0
48
+ if node =~ /.*;.*$/i
49
+ params = Regexp.last_match(0).split(";")
50
+ if params.first =~ /(srid|SRID)=\d*/
51
+ srid = params.first.split("=").last.to_i
52
+ else
53
+ value = params.first
54
+ end
55
+ if params.last =~ /(srid|SRID)=\d*/
56
+ srid = params.last.split("=").last.to_i
57
+ else
58
+ value = params.last
59
+ end
60
+ else
61
+ value = node
62
+ end
63
+ [value, srid]
64
+ end
65
+
66
+ private
67
+
68
+ def wkttosql_statement(node, srid)
69
+ func_name = st_func("ST_WKTToSQL")
70
+
71
+ args = [quote(node)]
72
+ args << srid unless srid.zero?
73
+ args << ActiveRecord::ConnectionAdapters::TrilogisAdapter::AXIS_ORDER_LONG_LAT
74
+
75
+ "#{func_name}(#{args.join(', ')})"
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Trilogis
6
+ module ColumnMethods
7
+ def spatial(name, options = {})
8
+ raise "You must set a type. For example: 't.spatial type: :st_point'" unless options[:type]
9
+
10
+ column(name, options[:type], **options)
11
+ end
12
+
13
+ def geometry(name, options = {})
14
+ column(name, :geometry, **options)
15
+ end
16
+
17
+ def geometry_collection(name, options = {})
18
+ column(name, :geometrycollection, **options)
19
+ end
20
+ alias geometrycollection geometry_collection
21
+
22
+ def line_string(name, options = {})
23
+ column(name, :linestring, **options)
24
+ end
25
+ alias linestring line_string
26
+
27
+ def multi_line_string(name, options = {})
28
+ column(name, :multilinestring, **options)
29
+ end
30
+ alias multilinestring multi_line_string
31
+
32
+ def multi_point(name, options = {})
33
+ column(name, :multipoint, **options)
34
+ end
35
+ alias multipoint multi_point
36
+
37
+ def multi_polygon(name, options = {})
38
+ column(name, :multipolygon, **options)
39
+ end
40
+ alias multipolygon multi_polygon
41
+
42
+ def point(name, options = {})
43
+ column(name, :point, **options)
44
+ end
45
+
46
+ def polygon(name, options = {})
47
+ column(name, :polygon, **options)
48
+ end
49
+ end
50
+ end
51
+
52
+ MySQL::Table.include Trilogis::ColumnMethods
53
+ end
54
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trilogy"
4
+ require "active_record/connection_adapters/trilogy_adapter"
5
+ require "active_record/connection_adapters/trilogis_adapter"
6
+
7
+ module ActiveRecord
8
+ module ConnectionAdapters
9
+ class TrilogisAdapter
10
+ module Connection
11
+ def trilogy_adapter_class
12
+ ActiveRecord::ConnectionAdapters::TrilogisAdapter
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class TrilogisAdapter
6
+ module Rails
7
+ module DBConsole
8
+ class AdapterAdapter < SimpleDelegator
9
+ def adapter
10
+ "mysql"
11
+ end
12
+ end
13
+
14
+ def db_config
15
+ if super.adapter == "trilogis"
16
+ AdapterAdapter.new(super)
17
+ else
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ module Rails
28
+ class DBConsole
29
+ # require "rails/commands/dbconsole/dbconsole_command"
30
+ if ActiveRecord.version < ::Gem::Version.new('6.1.a')
31
+ alias _brick_start start
32
+
33
+ def start
34
+ ENV["RAILS_ENV"] ||= @options[:environment] || environment
35
+
36
+ if config["adapter"] == "trilogis"
37
+ begin
38
+ ::ActiveRecord::ConnectionAdapters::TrilogisAdapter.dbconsole(config, @options)
39
+ rescue NotImplementedError
40
+ abort "Unknown command-line client for #{db_config.database}."
41
+ end
42
+ else
43
+ _brick_start
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Rails)
4
+ require "rails/railtie"
5
+
6
+ module ActiveRecord
7
+ module ConnectionAdapters
8
+ class TrilogisAdapter
9
+ class Railtie < ::Rails::Railtie
10
+ ActiveSupport.on_load(:active_record) do
11
+ require "active_record/connection_adapters/trilogis/connection"
12
+ ActiveRecord::Base.public_send :extend, ActiveRecord::ConnectionAdapters::TrilogisAdapter::Connection
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ if defined?(Rails::DBConsole)
21
+ require "trilogy_adapter/rails/dbconsole"
22
+ Rails::DBConsole.prepend(ActiveRecord::ConnectionAdapters::TrilogisAdapter::Rails::DBConsole)
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Trilogis
6
+ class SchemaCreation < MySQL::SchemaCreation # :nodoc:
7
+ private
8
+
9
+ def add_column_options!(sql, options)
10
+ if options[:srid]
11
+ sql << " /*!80003 SRID #{options[:srid]} */"
12
+ end
13
+
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Trilogis
6
+ module SchemaStatements
7
+ # super: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
8
+
9
+ # override
10
+ def indexes(table_name) #:nodoc:
11
+ indexes = super
12
+ # HACK(aleks, 06/15/18): MySQL 5 does not support prefix lengths for spatial indexes
13
+ # https://dev.mysql.com/doc/refman/5.6/en/create-index.html
14
+ indexes.select do |idx|
15
+ idx.type == :spatial
16
+ end.each { |idx| idx.is_a?(Struct) ? idx.lengths = {} : idx.instance_variable_set(:@lengths, {}) }
17
+ indexes
18
+ end
19
+
20
+ # override
21
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
22
+ if (info = RGeo::ActiveRecord.geometric_type_from_name(type.to_s.delete("_")))
23
+ type = limit[:type] || type if limit.is_a?(::Hash)
24
+ type = type.to_s.delete("_").upcase
25
+ end
26
+ super
27
+ end
28
+
29
+ private
30
+
31
+ # override
32
+ def schema_creation
33
+ Trilogis::SchemaCreation.new(self)
34
+ end
35
+
36
+ # override
37
+ def create_table_definition(*args, **options)
38
+ Trilogis::TableDefinition.new(self, *args, **options)
39
+ end
40
+
41
+ # override
42
+ def new_column_from_field(table_name, field)
43
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
44
+ default, default_function = field[:Default], nil
45
+
46
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
47
+ default, default_function = nil, default
48
+ elsif type_metadata.extra == "DEFAULT_GENERATED"
49
+ default = +"(#{default})" unless default.start_with?("(")
50
+ default, default_function = nil, default
51
+ end
52
+
53
+ # {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
54
+ spatial = spatial_column_info(table_name).get(field[:Field], type_metadata.sql_type)
55
+
56
+ SpatialColumn.new(
57
+ field[:Field],
58
+ default,
59
+ type_metadata,
60
+ field[:Null] == "YES",
61
+ default_function,
62
+ collation: field[:Collation],
63
+ comment: field[:Comment].presence,
64
+ spatial: spatial
65
+ )
66
+ end
67
+
68
+ # memoize hash of column infos for tables
69
+ def spatial_column_info(table_name)
70
+ @spatial_column_info ||= {}
71
+ @spatial_column_info[table_name.to_sym] = SpatialColumnInfo.new(self, table_name.to_s)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ module Trilogis # :nodoc:
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, **)
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
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
24
+ end
25
+
26
+ attr_reader :geometric_type, :srid
27
+
28
+ def has_z
29
+ false
30
+ end
31
+
32
+ def has_m
33
+ false
34
+ end
35
+
36
+ alias has_z? has_z
37
+ alias has_m? has_m
38
+
39
+ def multi?
40
+ /^(geometrycollection|multi)/i.match?(sql_type)
41
+ end
42
+
43
+ def limit
44
+ spatial? ? @limit : super
45
+ end
46
+
47
+ def spatial?
48
+ %i[geometry].include?(@sql_type_metadata.type)
49
+ end
50
+
51
+ private
52
+
53
+ def set_geometric_type_from_name(name)
54
+ @geometric_type = RGeo::ActiveRecord.geometric_type_from_name(name) || RGeo::Feature::Geometry
55
+ end
56
+
57
+ def build_from_sql_type(sql_type)
58
+ geo_type, @srid = Type::Spatial.parse_sql_type(sql_type)
59
+ set_geometric_type_from_name(geo_type)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
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.
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 = @adapter.query(
15
+ "SELECT column_name, srs_id, column_type FROM INFORMATION_SCHEMA.Columns WHERE table_name='#{@table_name}'"
16
+ )
17
+
18
+ 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,
27
+ }
28
+ end
29
+ result
30
+ end
31
+
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)
35
+
36
+ @spatial_column_info ||= all
37
+ @spatial_column_info[column_name]
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGeo
4
+ module ActiveRecord
5
+ module Trilogis
6
+ module SpatialExpressions
7
+ def st_distance_sphere(rhs, units = nil)
8
+ args = [self, rhs]
9
+ args << units.to_s if units
10
+ SpatialNamedFunction.new("ST_Distance_Sphere", args, [false, true, true, false])
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
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
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
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
14
+
15
+ geo_type = ColumnDefinitionUtils.geo_type(options[:type] || type || info[:type])
16
+
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
22
+
23
+ column
24
+ end
25
+ end
26
+
27
+ module ColumnDefinitionUtils
28
+ class << self
29
+ def geo_type(type = "GEOMETRY")
30
+ type.to_s.delete("_").upcase
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Trilogis
6
+ VERSION = "7.0.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The activerecord-trilogis-adapter gem installs the *trilogis*
4
+ # connection adapter into ActiveRecord.
5
+
6
+ # :stopdoc:
7
+
8
+ require "rgeo/active_record"
9
+
10
+ require "active_record/connection_adapters"
11
+ require "active_record/connection_adapters/trilogy_adapter"
12
+ require "active_record/connection_adapters/trilogis/version"
13
+ require "active_record/connection_adapters/trilogis/column_methods"
14
+ require "active_record/connection_adapters/trilogis/schema_creation"
15
+ require "active_record/connection_adapters/trilogis/schema_statements"
16
+ require "active_record/connection_adapters/trilogis/spatial_table_definition"
17
+ require "active_record/connection_adapters/trilogis/spatial_column"
18
+ require "active_record/connection_adapters/trilogis/spatial_column_info"
19
+ require "active_record/connection_adapters/trilogis/spatial_expressions"
20
+ require "active_record/connection_adapters/trilogis/arel_tosql"
21
+ require "active_record/tasks/trilogis_database_tasks"
22
+ require "active_record/type/spatial"
23
+
24
+ # :startdoc:
25
+
26
+ module ActiveRecord
27
+ module ConnectionHandling # :nodoc:
28
+ # Establishes a connection to the database that's used by all Active Record objects.
29
+ def trilogis_connection(config)
30
+ configuration = config.dup
31
+
32
+ # Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
33
+ # matched rather than number of rows updated.
34
+ configuration[:found_rows] = true
35
+
36
+ options = [
37
+ configuration[:host],
38
+ configuration[:port],
39
+ configuration[:database],
40
+ configuration[:username],
41
+ configuration[:password],
42
+ configuration[:socket],
43
+ 0
44
+ ]
45
+
46
+ ActiveRecord::ConnectionAdapters::TrilogisAdapter.new nil, logger, options, configuration
47
+ end
48
+ end
49
+
50
+ module ConnectionAdapters
51
+ class TrilogisAdapter < TrilogyAdapter
52
+ ADAPTER_NAME = "Trilogis"
53
+ AXIS_ORDER_LONG_LAT = "'axis-order=long-lat'".freeze
54
+
55
+ include Trilogis::SchemaStatements
56
+
57
+ SPATIAL_COLUMN_OPTIONS =
58
+ {
59
+ geometry: {},
60
+ geometrycollection: {},
61
+ linestring: {},
62
+ multilinestring: {},
63
+ multipoint: {},
64
+ multipolygon: {},
65
+ spatial: { type: "geometry" },
66
+ point: {},
67
+ polygon: {}
68
+ }.freeze
69
+
70
+ # http://postgis.17.x6.nabble.com/Default-SRID-td5001115.html
71
+ DEFAULT_SRID = 0
72
+ GEOGRAPHIC_SRID = 4326
73
+
74
+ %w[
75
+ geometry
76
+ geometrycollection
77
+ point
78
+ linestring
79
+ polygon
80
+ multipoint
81
+ multilinestring
82
+ multipolygon
83
+ ].each do |geo_type|
84
+ ActiveRecord::Type.register(geo_type.to_sym, adapter: :trilogis) do |sql_type|
85
+ Type::Spatial.new(sql_type.to_s)
86
+ end
87
+ end
88
+
89
+ def initialize(connection, logger, connection_options, config)
90
+ super
91
+
92
+ @visitor = Arel::Visitors::Trilogis.new(self)
93
+ end
94
+
95
+ def self.spatial_column_options(key)
96
+ SPATIAL_COLUMN_OPTIONS[key]
97
+ end
98
+
99
+ def default_srid
100
+ DEFAULT_SRID
101
+ end
102
+
103
+ def native_database_types
104
+ # Add spatial types
105
+ # Reference: https://dev.mysql.com/doc/refman/5.6/en/spatial-type-overview.html
106
+ super.merge(
107
+ geometry: { name: "geometry" },
108
+ geometrycollection: { name: "geometrycollection" },
109
+ linestring: { name: "linestring" },
110
+ multi_line_string: { name: "multilinestring" },
111
+ multi_point: { name: "multipoint" },
112
+ multi_polygon: { name: "multipolygon" },
113
+ spatial: { name: "geometry" },
114
+ point: { name: "point" },
115
+ polygon: { name: "polygon" }
116
+ )
117
+ end
118
+
119
+ class << self
120
+
121
+ private
122
+ def initialize_type_map(m)
123
+ super
124
+
125
+ %w[
126
+ geometry
127
+ geometrycollection
128
+ point
129
+ linestring
130
+ polygon
131
+ multipoint
132
+ multilinestring
133
+ multipolygon
134
+ ].each do |geo_type|
135
+ m.register_type(geo_type,Type.lookup(geo_type.to_sym, adapter: :trilogis))
136
+ end
137
+ end
138
+ end
139
+
140
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
141
+ TYPE_MAP_WITH_BOOLEAN = Type::TypeMap.new(TYPE_MAP).tap do |m|
142
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
143
+ end
144
+
145
+ def supports_spatial?
146
+ !mariadb? && version >= "5.7.6"
147
+ end
148
+
149
+ def quote(value)
150
+ dbval = value.try(:value_for_database) || value
151
+ if RGeo::Feature::Geometry.check_type(dbval)
152
+ "ST_GeomFromWKB(0x#{RGeo::WKRep::WKBGenerator.new(hex_format: true, little_endian: true).generate(dbval)},#{dbval.srid}, #{AXIS_ORDER_LONG_LAT})"
153
+ else
154
+ super
155
+ end
156
+ end
157
+
158
+ private
159
+ def type_map
160
+ emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Use MySQLDatabaseTasks for Trilogy
4
+ ActiveRecord::Tasks::DatabaseTasks.register_task(
5
+ "trilogis",
6
+ "ActiveRecord::Tasks::MySQLDatabaseTasks"
7
+ )
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Type
5
+ class Spatial < Binary # :nodoc:
6
+ # sql_type is a string that comes from the database definition
7
+ # examples:
8
+ # "geometry"
9
+ # "geometry NOT NULL"
10
+ # "geometry"
11
+ def initialize(sql_type = "geometry")
12
+ @sql_type = sql_type
13
+ @geo_type, @srid = self.class.parse_sql_type(sql_type)
14
+ end
15
+
16
+ # sql_type: geometry, geometry(Point), geometry(Point,4326), ...
17
+ #
18
+ # returns [geo_type, srid]
19
+ # geo_type: geometry, point, line_string, polygon, ...
20
+ # srid: 1234
21
+ def self.parse_sql_type(sql_type)
22
+ geo_type, srid = nil, 0
23
+ if sql_type =~ /(geometry)\((.*)\)$/i
24
+ # geometry(Point)
25
+ # geometry(Point,4326)
26
+ params = Regexp.last_match(2).split(",")
27
+ if params.first =~ /([a-z]+[^zm])(z?)(m?)/i
28
+ geo_type = Regexp.last_match(1)
29
+ end
30
+ if params.last =~ /(\d+)/
31
+ srid = Regexp.last_match(1).to_i
32
+ end
33
+ else
34
+ # geometry
35
+ # otherType(a,b)
36
+ geo_type = sql_type
37
+ end
38
+ [geo_type, srid]
39
+ end
40
+
41
+ def spatial_factory
42
+ @spatial_factories ||= {}
43
+
44
+ @spatial_factories[@srid] ||= if @srid == ConnectionAdapters::TrilogisAdapter::GEOGRAPHIC_SRID
45
+ RGeo::Geographic.spherical_factory(srid: ConnectionAdapters::TrilogisAdapter::GEOGRAPHIC_SRID)
46
+ else
47
+ RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
48
+ geo_type: @geo_type,
49
+ sql_type: @sql_type,
50
+ srid: @srid
51
+ )
52
+ end
53
+ end
54
+
55
+ def klass
56
+ type == :geometry ? RGeo::Feature::Geometry : super
57
+ end
58
+
59
+ def spatial?
60
+ true
61
+ end
62
+
63
+ def type
64
+ :geometry
65
+ end
66
+
67
+ # support setting an RGeo object or a WKT string
68
+ def serialize(value)
69
+ return if value.nil?
70
+
71
+ geo_value = cast_value(value)
72
+
73
+ # TODO: - only valid types should be allowed
74
+ # e.g. linestring is not valid for point column
75
+ raise "maybe should raise" unless RGeo::Feature::Geometry.check_type(geo_value)
76
+
77
+ geo_value
78
+ end
79
+
80
+ private
81
+
82
+ def cast_value(value)
83
+ return if value.nil?
84
+
85
+ ::String === value ? parse_wkt(value) : value
86
+ end
87
+
88
+ # convert WKT string into RGeo object
89
+ def parse_wkt(string)
90
+ marker = string[4, 1]
91
+ if ["\x00", "\x01"].include?(marker)
92
+ @srid = string[0, 4].unpack1(marker == "\x01" ? "V" : "N")
93
+ RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid).parse(string[4..-1])
94
+ elsif string[0, 10] =~ /[0-9a-fA-F]{8}0[01]/
95
+ @srid = string[0, 8].to_i(16)
96
+ @srid = [@srid].pack("V").unpack("N").first if string[9, 1] == "1"
97
+ RGeo::WKRep::WKBParser.new(spatial_factory, support_ewkb: true, default_srid: @srid).parse(string[8..-1])
98
+ else
99
+ string, @srid = Arel::Visitors::Trilogis.parse_node(string)
100
+ RGeo::WKRep::WKTParser.new(spatial_factory, support_ewkt: true, default_srid: @srid).parse(string)
101
+ end
102
+ rescue RGeo::Error::ParseError, RGeo::Error::InvalidGeometry
103
+ nil
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/trilogis_adapter"
4
+ require "active_record/connection_adapters/trilogis/railtie"
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-trilogis-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 7.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ether Moon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-03-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rgeo-activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 7.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 7.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mocha
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: appraisal
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ description: ActiveRecord connection adapter for MySQL. It is based on the stock MySQL
98
+ adapter, and adds built-in support for the spatial extensions provided by MySQL.
99
+ It uses the RGeo library to represent spatial data in Ruby.
100
+ email: ether.moon@kakao.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - LICENSE.txt
106
+ - lib/active_record/connection_adapters/trilogis/arel_tosql.rb
107
+ - lib/active_record/connection_adapters/trilogis/column_methods.rb
108
+ - lib/active_record/connection_adapters/trilogis/connection.rb
109
+ - lib/active_record/connection_adapters/trilogis/rails/dbconsole.rb
110
+ - lib/active_record/connection_adapters/trilogis/railtie.rb
111
+ - lib/active_record/connection_adapters/trilogis/schema_creation.rb
112
+ - lib/active_record/connection_adapters/trilogis/schema_statements.rb
113
+ - lib/active_record/connection_adapters/trilogis/spatial_column.rb
114
+ - lib/active_record/connection_adapters/trilogis/spatial_column_info.rb
115
+ - lib/active_record/connection_adapters/trilogis/spatial_expressions.rb
116
+ - lib/active_record/connection_adapters/trilogis/spatial_table_definition.rb
117
+ - lib/active_record/connection_adapters/trilogis/version.rb
118
+ - lib/active_record/connection_adapters/trilogis_adapter.rb
119
+ - lib/active_record/tasks/trilogis_database_tasks.rb
120
+ - lib/active_record/type/spatial.rb
121
+ - lib/activerecord-trilogis-adapter.rb
122
+ homepage: http://github.com/ether-moon/activerecord-trilogis-adapter
123
+ licenses:
124
+ - BSD-3-Clause
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: 2.7.0
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubygems_version: 3.5.6
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: ActiveRecord adapter for MySQL, based on RGeo.
145
+ test_files: []