activerecord-trilogy-adapter 2.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: 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: []