activerecord-trilogis-adapter 7.0.0

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