activerecord-trilogy-adapter 2.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: c82a7b7939a81514e90b6c5da24ab8e310a34629cce1c33bd300c1ffa1ca3176
4
+ data.tar.gz: 245b8be7fb7fa5672ce6db5598730120c7b401be3e2ec0b3b74fc3ad25592526
5
+ SHA512:
6
+ metadata.gz: aa5f9afa69163d02a8d11c75c33c17a0a22a112b7f5ec33671c92b4900b185340665ddc01036a59227ad4e9d8199f86d745ce983dd84c74a54f8e6f3eb6d7538
7
+ data.tar.gz: 50496283df9b2dd9f7c23b0131951393696512bc2bb904bfd5b708401c7d8c8e875cec5a9ef1a8beae8869d3533c070a24f9b2b577acc8ad7ec04b1b8e9c67e2
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2018-2022 GitHub, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Trilogy Adapter
2
+
3
+ Active Record database adapter for [Trilogy](https://github.com/github/trilogy)
4
+
5
+ ## Requirements
6
+
7
+ - [Ruby](https://www.ruby-lang.org) 2.7 or higher
8
+ - [Active Record](https://github.com/rails/rails) 7.1 or higher
9
+ - [Trilogy](https://github.com/github/trilogy) 2.1.1 or higher
10
+
11
+ ## Setup
12
+
13
+ * Add the following to your Gemfile:
14
+
15
+ ```rb
16
+ gem "activerecord-trilogy-adapter"
17
+ ```
18
+
19
+ * Update your database configuration (e.g. `config/database.yml`) to use
20
+ `trilogy` as the adapter.
21
+
22
+ ## Versioning
23
+
24
+ Read [Semantic Versioning](https://semver.org) for details. Briefly, it means:
25
+
26
+ - Major (X.y.z) - Incremented for any backwards incompatible public API changes.
27
+ - Minor (x.Y.z) - Incremented for new, backwards compatible, public API enhancements/fixes.
28
+ - Patch (x.y.Z) - Incremented for small, backwards compatible, bug fixes.
29
+
30
+ ## Code of Conduct
31
+
32
+ Please note that this project is released with a [CODE OF CONDUCT](CODE_OF_CONDUCT.md). By
33
+ participating in this project you agree to abide by its terms.
34
+
35
+ ## Contributions
36
+
37
+ Read [CONTRIBUTING](CONTRIBUTING.md) for details.
38
+
39
+ ## License
40
+
41
+ Released under the [MIT License](LICENSE.md).
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trilogy"
4
+ require "active_record/connection_adapters/abstract_mysql_adapter"
5
+
6
+ require "active_record/tasks/trilogy_database_tasks"
7
+ require "trilogy_adapter/lost_connection_exception_translator"
8
+
9
+ module ActiveRecord
10
+ module ConnectionAdapters
11
+ class TrilogyAdapter < ::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
12
+ module DatabaseStatements
13
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
14
+ :desc, :describe, :set, :show, :use
15
+ ) # :nodoc:
16
+ private_constant :READ_QUERY
17
+
18
+ def write_query?(sql) # :nodoc:
19
+ !READ_QUERY.match?(sql)
20
+ rescue ArgumentError # Invalid encoding
21
+ !READ_QUERY.match?(sql.b)
22
+ end
23
+
24
+ def explain(arel, binds = [])
25
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
26
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
27
+ result = exec_query(sql, "EXPLAIN", binds)
28
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
29
+
30
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
31
+ end
32
+
33
+ def execute(sql, name = nil, async: false)
34
+ sql = transform_query(sql)
35
+ check_if_write_query(sql)
36
+
37
+ raw_execute(sql, name, async: async)
38
+ end
39
+
40
+ def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false)
41
+ result = execute(sql, name, async: async)
42
+ ActiveRecord::Result.new(result.fields, result.to_a)
43
+ end
44
+
45
+ alias exec_without_stmt exec_query
46
+
47
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
48
+ execute(to_sql(sql, binds), name)
49
+ end
50
+
51
+ def exec_delete(sql, name = nil, binds = [])
52
+ result = execute(to_sql(sql, binds), name)
53
+ result.affected_rows
54
+ end
55
+
56
+ alias :exec_update :exec_delete
57
+
58
+ private
59
+ def last_inserted_id(result)
60
+ result.last_insert_id
61
+ end
62
+ end
63
+
64
+ ER_BAD_DB_ERROR = 1049
65
+ ER_ACCESS_DENIED_ERROR = 1045
66
+ ER_CONN_HOST_ERROR = 2003
67
+ ER_UNKNOWN_HOST_ERROR = 2005
68
+
69
+ ADAPTER_NAME = "Trilogy"
70
+
71
+ include DatabaseStatements
72
+
73
+ class << self
74
+ def new_client(config)
75
+ ::Trilogy.new(config)
76
+ rescue Trilogy::DatabaseError => error
77
+ raise translate_connect_error(config, error)
78
+ end
79
+
80
+ def translate_connect_error(config, error)
81
+ case error.error_code
82
+ when ER_BAD_DB_ERROR
83
+ ActiveRecord::NoDatabaseError.db_error(config[:database])
84
+ when ER_ACCESS_DENIED_ERROR
85
+ ActiveRecord::DatabaseConnectionError.username_error(config[:username])
86
+ when ER_CONN_HOST_ERROR, ER_UNKNOWN_HOST_ERROR
87
+ ActiveRecord::DatabaseConnectionError.hostname_error(config[:host])
88
+ else
89
+ ActiveRecord::ConnectionNotEstablished.new(error.message)
90
+ end
91
+ end
92
+ end
93
+
94
+ def supports_json?
95
+ !mariadb? && database_version >= "5.7.8"
96
+ end
97
+
98
+ def supports_comments?
99
+ true
100
+ end
101
+
102
+ def supports_comments_in_create?
103
+ true
104
+ end
105
+
106
+ def supports_savepoints?
107
+ true
108
+ end
109
+
110
+ def savepoint_errors_invalidate_transactions?
111
+ true
112
+ end
113
+
114
+ def supports_lazy_transactions?
115
+ true
116
+ end
117
+
118
+ def quote_string(string)
119
+ any_raw_connection.escape string
120
+ end
121
+
122
+ def active?
123
+ connection&.ping || false
124
+ rescue ::Trilogy::Error
125
+ false
126
+ end
127
+
128
+ alias reset! reconnect!
129
+
130
+ def disconnect!
131
+ unless connection.nil?
132
+ connection.close
133
+ self.connection = nil
134
+ end
135
+ end
136
+
137
+ def discard!
138
+ self.connection = nil
139
+ end
140
+
141
+ def raw_execute(sql, name, async: false, allow_retry: false, uses_transaction: true)
142
+ mark_transaction_written_if_write(sql)
143
+
144
+ log(sql, name, async: async) do
145
+ with_raw_connection(allow_retry: allow_retry, uses_transaction: uses_transaction) do |conn|
146
+ # Sync any changes since connection last established.
147
+ if default_timezone == :local
148
+ conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
149
+ else
150
+ conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
151
+ end
152
+
153
+ conn.query(sql)
154
+ end
155
+ end
156
+ end
157
+
158
+ def each_hash(result)
159
+ return to_enum(:each_hash, result) unless block_given?
160
+
161
+ keys = result.fields.map(&:to_sym)
162
+ result.rows.each do |row|
163
+ hash = {}
164
+ idx = 0
165
+ row.each do |value|
166
+ hash[keys[idx]] = value
167
+ idx += 1
168
+ end
169
+ yield hash
170
+ end
171
+
172
+ nil
173
+ end
174
+
175
+ def error_number(exception)
176
+ exception.error_code if exception.respond_to?(:error_code)
177
+ end
178
+
179
+ private
180
+ def connection
181
+ @raw_connection
182
+ end
183
+
184
+ def connection=(conn)
185
+ @raw_connection = conn
186
+ end
187
+
188
+ def connect
189
+ self.connection = self.class.new_client(@config)
190
+ end
191
+
192
+ def reconnect
193
+ connection&.close
194
+ connect
195
+ end
196
+
197
+ def full_version
198
+ schema_cache.database_version.full_version_string
199
+ end
200
+
201
+ def get_full_version
202
+ any_raw_connection.server_info[:version]
203
+ end
204
+
205
+ def translate_exception(exception, message:, sql:, binds:)
206
+ error_code = exception.error_code if exception.respond_to?(:error_code)
207
+
208
+ ::TrilogyAdapter::LostConnectionExceptionTranslator.
209
+ new(exception, message, error_code).translate || super
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Use MySQLDatabaseTasks for Trilogy
4
+ ActiveRecord::Tasks::DatabaseTasks.register_task(
5
+ "trilogy",
6
+ "ActiveRecord::Tasks::MySQLDatabaseTasks"
7
+ )
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "trilogy_adapter/errors"
5
+ require "trilogy_adapter/railtie"
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "trilogy"
4
+ require "active_record/connection_adapters/trilogy_adapter"
5
+
6
+ module TrilogyAdapter
7
+ # Necessary for enhancing ActiveRecord to recognize the Trilogy adapter. Example:
8
+ #
9
+ # ActiveRecord::Base.public_send :extend, TrilogyAdapter::Connection
10
+ #
11
+ # This will allow downstream applications to use the Trilogy adapter. Example:
12
+ #
13
+ # ActiveRecord::Base.establish_connection adapter: "trilogy",
14
+ # host: "localhost",
15
+ # database: "demo_development"
16
+ module Connection
17
+ def trilogy_connection(config)
18
+ configuration = config.dup
19
+
20
+ # Set FOUND_ROWS capability on the connection so UPDATE queries returns number of rows
21
+ # matched rather than number of rows updated.
22
+ configuration[:found_rows] = true
23
+
24
+ options = [
25
+ configuration[:host],
26
+ configuration[:port],
27
+ configuration[:database],
28
+ configuration[:username],
29
+ configuration[:password],
30
+ configuration[:socket],
31
+ 0
32
+ ]
33
+
34
+ ActiveRecord::ConnectionAdapters::TrilogyAdapter.new nil, logger, options, configuration
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrilogyAdapter
4
+ module Errors
5
+ # ServerShutdown will be raised when the database server was shutdown.
6
+ class ServerShutdown < ActiveRecord::ConnectionFailed
7
+ end
8
+
9
+ # ServerLost will be raised when the database connection was lost.
10
+ class ServerLost < ActiveRecord::ConnectionFailed
11
+ end
12
+
13
+ # ServerGone will be raised when the database connection is gone.
14
+ class ServerGone < ActiveRecord::ConnectionFailed
15
+ end
16
+
17
+ # BrokenPipe will be raised when a system process connection fails.
18
+ class BrokenPipe < ActiveRecord::ConnectionFailed
19
+ end
20
+
21
+ # SocketError will be raised when Ruby encounters a network error.
22
+ class SocketError < ActiveRecord::ConnectionFailed
23
+ end
24
+
25
+ # ConnectionResetByPeer will be raised when a network connection is closed
26
+ # outside the sytstem process.
27
+ class ConnectionResetByPeer < ActiveRecord::ConnectionFailed
28
+ end
29
+
30
+ # ClosedConnection will be raised when the Trilogy encounters a closed
31
+ # connection.
32
+ class ClosedConnection < ActiveRecord::ConnectionFailed
33
+ end
34
+
35
+ # InvalidSequenceId will be raised when Trilogy ecounters an invalid sequence
36
+ # id.
37
+ class InvalidSequenceId < ActiveRecord::ConnectionFailed
38
+ end
39
+
40
+ # UnexpectedPacket will be raised when Trilogy ecounters an unexpected
41
+ # response packet.
42
+ class UnexpectedPacket < ActiveRecord::ConnectionFailed
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrilogyAdapter
4
+ class LostConnectionExceptionTranslator
5
+ attr_reader :exception, :message, :error_number
6
+
7
+ def initialize(exception, message, error_number)
8
+ @exception = exception
9
+ @message = message
10
+ @error_number = error_number
11
+ end
12
+
13
+ def translate
14
+ translate_database_exception || translate_ruby_exception || translate_trilogy_exception
15
+ end
16
+
17
+ private
18
+ ER_SERVER_SHUTDOWN = 1053
19
+ CR_SERVER_LOST = 2013
20
+ CR_SERVER_LOST_EXTENDED = 2055
21
+ CR_SERVER_GONE_ERROR = 2006
22
+
23
+ def translate_database_exception
24
+ case error_number
25
+ when ER_SERVER_SHUTDOWN
26
+ Errors::ServerShutdown.new(message)
27
+ when CR_SERVER_LOST, CR_SERVER_LOST_EXTENDED
28
+ Errors::ServerLost.new(message)
29
+ when CR_SERVER_GONE_ERROR
30
+ Errors::ServerGone.new(message)
31
+ end
32
+ end
33
+
34
+ def translate_ruby_exception
35
+ case exception
36
+ when Errno::EPIPE
37
+ Errors::BrokenPipe.new(message)
38
+ when SocketError
39
+ Errors::SocketError.new(message)
40
+ when Errno::ECONNRESET
41
+ Errors::ConnectionResetByPeer.new(message)
42
+ end
43
+ end
44
+
45
+ def translate_trilogy_exception
46
+ return unless exception.is_a?(Trilogy::Error)
47
+
48
+ case message
49
+ when /TRILOGY_CLOSED_CONNECTION/
50
+ Errors::ClosedConnection.new(message)
51
+ when /TRILOGY_INVALID_SEQUENCE_ID/
52
+ Errors::InvalidSequenceId.new(message)
53
+ when /TRILOGY_UNEXPECTED_PACKET/
54
+ Errors::UnexpectedPacket.new(message)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrilogyAdapter
4
+ module Rails
5
+ module DBConsole
6
+ class AdapterAdapter < SimpleDelegator
7
+ def adapter
8
+ "mysql"
9
+ end
10
+ end
11
+
12
+ def db_config
13
+ if super.adapter == "trilogy"
14
+ AdapterAdapter.new(super)
15
+ else
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Rails)
4
+ require "rails/railtie"
5
+
6
+ module TrilogyAdapter
7
+ class Railtie < ::Rails::Railtie
8
+ ActiveSupport.on_load(:active_record) do
9
+ require "trilogy_adapter/connection"
10
+ ActiveRecord::Base.public_send :extend, TrilogyAdapter::Connection
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ if defined?(Rails::DBConsole)
17
+ require "trilogy_adapter/rails/dbconsole"
18
+ Rails::DBConsole.prepend(TrilogyAdapter::Rails::DBConsole)
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrilogyAdapter
4
+ VERSION = "2.0.0"
5
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-trilogy-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - GitHub Engineering
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: trilogy
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.1.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 7.1.0.alpha
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 7.1.0.alpha
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-focus
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '12.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '12.3'
97
+ description:
98
+ email:
99
+ - opensource+trilogy@github.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files:
103
+ - README.md
104
+ - LICENSE.md
105
+ files:
106
+ - LICENSE.md
107
+ - README.md
108
+ - lib/active_record/connection_adapters/trilogy_adapter.rb
109
+ - lib/active_record/tasks/trilogy_database_tasks.rb
110
+ - lib/activerecord-trilogy-adapter.rb
111
+ - lib/trilogy_adapter/connection.rb
112
+ - lib/trilogy_adapter/errors.rb
113
+ - lib/trilogy_adapter/lost_connection_exception_translator.rb
114
+ - lib/trilogy_adapter/rails/dbconsole.rb
115
+ - lib/trilogy_adapter/railtie.rb
116
+ - lib/trilogy_adapter/version.rb
117
+ homepage: https://github.com/github/activerecord-trilogy-adapter
118
+ licenses:
119
+ - MIT
120
+ metadata:
121
+ source_code_uri: https://github.com/github/activerecord-trilogy-adapter
122
+ changelog_uri: https://github.com/github/activerecord-trilogy-adapter/blob/master/CHANGELOG.md
123
+ bug_tracker_uri: https://github.com/github/activerecord-trilogy-adapter/issues
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubygems_version: 3.3.3
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Active Record adapter for https://github.com/github/trilogy.
143
+ test_files: []