activerecord-trilogy-adapter 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -12
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +191 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +226 -144
- data/lib/activerecord-trilogy-adapter.rb +0 -1
- data/lib/trilogy_adapter/rails/dbconsole.rb +43 -0
- data/lib/trilogy_adapter/version.rb +1 -1
- metadata +8 -8
- data/lib/trilogy_adapter/errors.rb +0 -45
- data/lib/trilogy_adapter/lost_connection_exception_translator.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a3a2190bc8818bad95cf334d7e4b0c9108a9520f37a899ff356f36b2c067ef1
|
4
|
+
data.tar.gz: bf58ccfed24d5a7ecd17f6670d4a6fb623e27f862cbcbb87b408470b46fa44c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b0991df8962bdfbe7d917aac03f224cbe919a3cef9113c39f1eab150d207c04b4a7db457404d492d3bb0e5b0e459d353f7511a1e82d7ff92c804d02353a8c34
|
7
|
+
data.tar.gz: 44a43a09ad1230250f29da21e941bc59a8f2d606f0716c7fc045ffba0b457e029fe01b34fade384b33277f06f2667cb8498c9b895fc7fce3461f7f07afacf332
|
data/README.md
CHANGED
@@ -1,27 +1,34 @@
|
|
1
1
|
# Trilogy Adapter
|
2
2
|
|
3
|
-
Active Record database adapter for [Trilogy](https://github.com/trilogy-libraries/trilogy)
|
3
|
+
Ruby on Rails Active Record database adapter for [Trilogy](https://github.com/trilogy-libraries/trilogy), a client library for MySQL-compatible database servers, designed for performance, flexibility, and ease of embedding.
|
4
4
|
|
5
|
-
This gem offers Trilogy support for versions of
|
5
|
+
This gem offers Trilogy support for versions of Active Record prior to v7.1. Currently supports:
|
6
6
|
|
7
|
-
- Rails v7.
|
7
|
+
- ⚠️ Rails v7.1+ includes Trilogy support by default making this gem unnecessary
|
8
|
+
- ✅ Rails v7.0.x
|
9
|
+
- ✅ Rails v6.1.x
|
10
|
+
- ✅ Rails v6.0.x
|
8
11
|
|
9
12
|
## Requirements
|
10
13
|
|
11
|
-
- [Ruby](https://www.ruby-lang.org)
|
12
|
-
- [Active Record](https://github.com/rails/rails)
|
13
|
-
- [Trilogy](https://github.com/trilogy-libraries/trilogy)
|
14
|
+
- [Ruby](https://www.ruby-lang.org) v2.7 or higher
|
15
|
+
- [Active Record](https://github.com/rails/rails) v6.0.x or higher
|
16
|
+
- [Trilogy](https://github.com/trilogy-libraries/trilogy) v2.4.0 or higher, which is included as a dependency of this gem.
|
14
17
|
|
15
18
|
## Setup
|
16
19
|
|
17
|
-
|
20
|
+
1. Add the following to your `Gemfile` and run `bundle install`:
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
```rb
|
23
|
+
# Gemfile
|
24
|
+
gem "activerecord-trilogy-adapter"
|
25
|
+
```
|
26
|
+
2. Update your application's database configuration to use `trilogy` as the adapter:
|
22
27
|
|
23
|
-
|
24
|
-
|
28
|
+
```yaml
|
29
|
+
# config/database.yml
|
30
|
+
adapter: trilogy
|
31
|
+
```
|
25
32
|
|
26
33
|
## Versioning
|
27
34
|
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Trilogy
|
6
|
+
module DatabaseStatements
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
8
|
+
:desc, :describe, :set, :show, :use
|
9
|
+
) # :nodoc:
|
10
|
+
private_constant :READ_QUERY
|
11
|
+
|
12
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
13
|
+
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
14
|
+
|
15
|
+
def execute(sql, name = nil, **kwargs)
|
16
|
+
sql = transform_query(sql)
|
17
|
+
check_if_write_query(sql)
|
18
|
+
mark_transaction_written_if_write(sql)
|
19
|
+
|
20
|
+
result = raw_execute(sql, name, **kwargs)
|
21
|
+
ActiveRecord::Result.new(result.fields, result.to_a)
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_query?(sql) # :nodoc:
|
25
|
+
!READ_QUERY.match?(sql)
|
26
|
+
rescue ArgumentError # Invalid encoding
|
27
|
+
!READ_QUERY.match?(sql.b)
|
28
|
+
end
|
29
|
+
|
30
|
+
def explain(arel, binds = [])
|
31
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
32
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
33
|
+
result = internal_exec_query(sql, "EXPLAIN", binds)
|
34
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
35
|
+
|
36
|
+
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
37
|
+
end
|
38
|
+
|
39
|
+
def select_all(*, **) # :nodoc:
|
40
|
+
result = super
|
41
|
+
with_trilogy_connection do |conn|
|
42
|
+
conn.next_result while conn.more_results_exist?
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_query?(sql) # :nodoc:
|
48
|
+
!READ_QUERY.match?(sql)
|
49
|
+
rescue ArgumentError # Invalid encoding
|
50
|
+
!READ_QUERY.match?(sql.b)
|
51
|
+
end
|
52
|
+
|
53
|
+
def explain(arel, binds = [], options = [])
|
54
|
+
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
55
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
56
|
+
result = internal_exec_query(sql, "EXPLAIN", binds)
|
57
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
58
|
+
|
59
|
+
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
60
|
+
end
|
61
|
+
|
62
|
+
def exec_query(sql, name = "SQL", binds = [], prepare: false, **kwargs)
|
63
|
+
internal_exec_query(sql, name, binds, prepare: prepare, **kwargs)
|
64
|
+
end
|
65
|
+
|
66
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
67
|
+
sql = transform_query(sql)
|
68
|
+
check_if_write_query(sql)
|
69
|
+
mark_transaction_written_if_write(sql)
|
70
|
+
|
71
|
+
result = raw_execute(sql, name, async: async)
|
72
|
+
ActiveRecord::Result.new(result.fields, result.to_a)
|
73
|
+
end
|
74
|
+
|
75
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
76
|
+
sql = transform_query(sql)
|
77
|
+
check_if_write_query(sql)
|
78
|
+
mark_transaction_written_if_write(sql)
|
79
|
+
|
80
|
+
raw_execute(to_sql(sql, binds), name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
84
|
+
sql = transform_query(sql)
|
85
|
+
check_if_write_query(sql)
|
86
|
+
mark_transaction_written_if_write(sql)
|
87
|
+
|
88
|
+
result = raw_execute(to_sql(sql, binds), name)
|
89
|
+
result.affected_rows
|
90
|
+
end
|
91
|
+
|
92
|
+
alias :exec_update :exec_delete # :nodoc:
|
93
|
+
|
94
|
+
def high_precision_current_timestamp
|
95
|
+
HIGH_PRECISION_CURRENT_TIMESTAMP
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_explain_clause(options = [])
|
99
|
+
return "EXPLAIN" if options.empty?
|
100
|
+
|
101
|
+
explain_clause = "EXPLAIN #{options.join(" ").upcase}"
|
102
|
+
|
103
|
+
if analyze_without_explain? && explain_clause.include?("ANALYZE")
|
104
|
+
explain_clause.sub("EXPLAIN ", "")
|
105
|
+
else
|
106
|
+
explain_clause
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
if ActiveRecord.version < ::Gem::Version.new('7.0.a') # ActiveRecord <= 6.1 support
|
112
|
+
def transform_query(sql); sql; end
|
113
|
+
def check_if_write_query(*args); end
|
114
|
+
|
115
|
+
if ActiveRecord.version < ::Gem::Version.new('6.1.a') # ActiveRecord <= 6.0 support
|
116
|
+
def mark_transaction_written_if_write(sql)
|
117
|
+
transaction = current_transaction
|
118
|
+
if transaction.respond_to?(:written) && transaction.open?
|
119
|
+
transaction.written ||= write_query?(sql)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def raw_execute(sql, name, async: false, materialize_transactions: true)
|
126
|
+
log_kwargs = {}
|
127
|
+
log_kwargs[:async] = async if ActiveRecord.version >= ::Gem::Version.new('7.0.a')
|
128
|
+
log(sql, name, **log_kwargs) do
|
129
|
+
with_trilogy_connection(materialize_transactions: materialize_transactions) do |conn|
|
130
|
+
sync_timezone_changes(conn)
|
131
|
+
conn.query(sql)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def last_inserted_id(result)
|
137
|
+
result.last_insert_id
|
138
|
+
end
|
139
|
+
|
140
|
+
def sync_timezone_changes(conn)
|
141
|
+
# Sync any changes since connection last established.
|
142
|
+
if default_timezone == :local
|
143
|
+
conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
144
|
+
else
|
145
|
+
conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def execute_batch(statements, name = nil)
|
150
|
+
statements = statements.map { |sql| transform_query(sql) } if respond_to?(:transform_query)
|
151
|
+
combine_multi_statements(statements).each do |statement|
|
152
|
+
with_trilogy_connection do |conn|
|
153
|
+
raw_execute(statement, name)
|
154
|
+
conn.next_result while conn.more_results_exist?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def multi_statements_enabled?
|
160
|
+
!!@config[:multi_statement]
|
161
|
+
end
|
162
|
+
|
163
|
+
def with_multi_statements
|
164
|
+
if multi_statements_enabled?
|
165
|
+
return yield
|
166
|
+
end
|
167
|
+
|
168
|
+
with_trilogy_connection do |conn|
|
169
|
+
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
170
|
+
|
171
|
+
yield
|
172
|
+
ensure
|
173
|
+
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def combine_multi_statements(total_sql)
|
178
|
+
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
179
|
+
previous_packet = total_sql_chunks.last
|
180
|
+
if max_allowed_packet_reached?(sql, previous_packet)
|
181
|
+
total_sql_chunks << +sql
|
182
|
+
else
|
183
|
+
previous_packet << ";\n"
|
184
|
+
previous_packet << sql
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -4,83 +4,83 @@ require "trilogy"
|
|
4
4
|
require "active_record/connection_adapters/abstract_mysql_adapter"
|
5
5
|
|
6
6
|
require "active_record/tasks/trilogy_database_tasks"
|
7
|
-
require "
|
7
|
+
require "active_record/connection_adapters/trilogy/database_statements"
|
8
8
|
|
9
9
|
module ActiveRecord
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
private_constant :READ_QUERY
|
17
|
-
|
18
|
-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
19
|
-
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
20
|
-
|
21
|
-
def write_query?(sql) # :nodoc:
|
22
|
-
!READ_QUERY.match?(sql)
|
23
|
-
rescue ArgumentError # Invalid encoding
|
24
|
-
!READ_QUERY.match?(sql.b)
|
25
|
-
end
|
26
|
-
|
27
|
-
def explain(arel, binds = [])
|
28
|
-
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
29
|
-
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
30
|
-
result = exec_query(sql, "EXPLAIN", binds)
|
31
|
-
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
10
|
+
# ActiveRecord <= 6.1 support
|
11
|
+
if ActiveRecord.version < ::Gem::Version.new('7.0.a')
|
12
|
+
class DatabaseConnectionError < ConnectionNotEstablished
|
13
|
+
def initialize(message = nil)
|
14
|
+
super(message || "Database connection error")
|
15
|
+
end
|
32
16
|
|
33
|
-
|
17
|
+
class << self
|
18
|
+
def hostname_error(hostname)
|
19
|
+
DatabaseConnectionError.new(<<~MSG)
|
20
|
+
There is an issue connecting with your hostname: #{hostname}.\n
|
21
|
+
Please check your database configuration and ensure there is a valid connection to your database.
|
22
|
+
MSG
|
34
23
|
end
|
35
24
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
25
|
+
def username_error(username)
|
26
|
+
DatabaseConnectionError.new(<<~MSG)
|
27
|
+
There is an issue connecting to your database with your username/password, username: #{username}.\n
|
28
|
+
Please check your database configuration to ensure the username/password are valid.
|
29
|
+
MSG
|
39
30
|
end
|
31
|
+
end
|
32
|
+
end
|
40
33
|
|
41
|
-
|
34
|
+
NoDatabaseError.class_exec do
|
35
|
+
def self.db_error(db_name)
|
36
|
+
NoDatabaseError.new(<<~MSG)
|
37
|
+
We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml file.
|
42
38
|
|
43
|
-
|
44
|
-
execute(to_sql(sql, binds), name)
|
45
|
-
end
|
39
|
+
To resolve this error:
|
46
40
|
|
47
|
-
|
48
|
-
|
49
|
-
result.affected_rows
|
50
|
-
end
|
51
|
-
|
52
|
-
alias :exec_update :exec_delete
|
41
|
+
- Did you create the database for this app, or delete it? You may need to create your database.
|
42
|
+
- Has the database name changed? Check your database.yml config has the correct database name.
|
53
43
|
|
54
|
-
|
55
|
-
|
56
|
-
|
44
|
+
To create your database, run:\n\n bin/rails db:create
|
45
|
+
MSG
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
57
49
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
50
|
+
if ActiveRecord.version < ::Gem::Version.new('6.1.a') # ActiveRecord <= 6.0 support
|
51
|
+
require "active_record/database_configurations"
|
52
|
+
DatabaseConfigurations.class_exec do
|
53
|
+
def resolve(config) # :nodoc:
|
54
|
+
@resolver ||= ::ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(::ActiveRecord::Base.configurations)
|
55
|
+
@resolver.resolve(config)
|
62
56
|
end
|
57
|
+
end
|
58
|
+
end
|
63
59
|
|
60
|
+
module ConnectionAdapters
|
61
|
+
class TrilogyAdapter < ::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
|
64
62
|
ER_BAD_DB_ERROR = 1049
|
63
|
+
ER_DBACCESS_DENIED_ERROR = 1044
|
65
64
|
ER_ACCESS_DENIED_ERROR = 1045
|
65
|
+
ER_SERVER_SHUTDOWN = 1053
|
66
66
|
|
67
67
|
ADAPTER_NAME = "Trilogy"
|
68
68
|
|
69
|
-
include DatabaseStatements
|
69
|
+
include Trilogy::DatabaseStatements
|
70
70
|
|
71
71
|
SSL_MODES = {
|
72
|
-
SSL_MODE_DISABLED: Trilogy::SSL_DISABLED,
|
73
|
-
SSL_MODE_PREFERRED: Trilogy::SSL_PREFERRED_NOVERIFY,
|
74
|
-
SSL_MODE_REQUIRED: Trilogy::SSL_REQUIRED_NOVERIFY,
|
75
|
-
SSL_MODE_VERIFY_CA: Trilogy::SSL_VERIFY_CA,
|
76
|
-
SSL_MODE_VERIFY_IDENTITY: Trilogy::SSL_VERIFY_IDENTITY
|
72
|
+
SSL_MODE_DISABLED: ::Trilogy::SSL_DISABLED,
|
73
|
+
SSL_MODE_PREFERRED: ::Trilogy::SSL_PREFERRED_NOVERIFY,
|
74
|
+
SSL_MODE_REQUIRED: ::Trilogy::SSL_REQUIRED_NOVERIFY,
|
75
|
+
SSL_MODE_VERIFY_CA: ::Trilogy::SSL_VERIFY_CA,
|
76
|
+
SSL_MODE_VERIFY_IDENTITY: ::Trilogy::SSL_VERIFY_IDENTITY
|
77
77
|
}.freeze
|
78
78
|
|
79
79
|
class << self
|
80
80
|
def new_client(config)
|
81
81
|
config[:ssl_mode] = parse_ssl_mode(config[:ssl_mode]) if config[:ssl_mode]
|
82
82
|
::Trilogy.new(config)
|
83
|
-
rescue Trilogy::ConnectionError, Trilogy::ProtocolError => error
|
83
|
+
rescue ::Trilogy::ConnectionError, ::Trilogy::ProtocolError => error
|
84
84
|
raise translate_connect_error(config, error)
|
85
85
|
end
|
86
86
|
|
@@ -88,7 +88,6 @@ module ActiveRecord
|
|
88
88
|
return mode if mode.is_a? Integer
|
89
89
|
|
90
90
|
m = mode.to_s.upcase
|
91
|
-
# enable Mysql2 client compatibility
|
92
91
|
m = "SSL_MODE_#{m}" unless m.start_with? "SSL_MODE_"
|
93
92
|
|
94
93
|
SSL_MODES.fetch(m.to_sym, mode)
|
@@ -101,13 +100,58 @@ module ActiveRecord
|
|
101
100
|
when ER_ACCESS_DENIED_ERROR
|
102
101
|
ActiveRecord::DatabaseConnectionError.username_error(config[:username])
|
103
102
|
else
|
104
|
-
if error.message.
|
103
|
+
if error.message.include?("TRILOGY_DNS_ERROR")
|
105
104
|
ActiveRecord::DatabaseConnectionError.hostname_error(config[:host])
|
106
105
|
else
|
107
106
|
ActiveRecord::ConnectionNotEstablished.new(error.message)
|
108
107
|
end
|
109
108
|
end
|
110
109
|
end
|
110
|
+
|
111
|
+
def dbconsole(config, options = {})
|
112
|
+
mysql_config = if ActiveRecord.version < ::Gem::Version.new('6.1.a')
|
113
|
+
config.config
|
114
|
+
else
|
115
|
+
config.configuration_hash
|
116
|
+
end
|
117
|
+
|
118
|
+
args = {
|
119
|
+
host: "--host",
|
120
|
+
port: "--port",
|
121
|
+
socket: "--socket",
|
122
|
+
username: "--user",
|
123
|
+
encoding: "--default-character-set",
|
124
|
+
sslca: "--ssl-ca",
|
125
|
+
sslcert: "--ssl-cert",
|
126
|
+
sslcapath: "--ssl-capath",
|
127
|
+
sslcipher: "--ssl-cipher",
|
128
|
+
sslkey: "--ssl-key",
|
129
|
+
ssl_mode: "--ssl-mode"
|
130
|
+
}.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] }
|
131
|
+
|
132
|
+
if mysql_config[:password] && options[:include_password]
|
133
|
+
args << "--password=#{mysql_config[:password]}"
|
134
|
+
elsif mysql_config[:password] && !mysql_config[:password].to_s.empty?
|
135
|
+
args << "-p"
|
136
|
+
end
|
137
|
+
|
138
|
+
args << mysql_config[:database]
|
139
|
+
|
140
|
+
find_cmd_and_exec(["mysql", "mysql5"], *args)
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def initialize_type_map(m)
|
145
|
+
super if ActiveRecord.version >= ::Gem::Version.new('7.0.a')
|
146
|
+
|
147
|
+
m.register_type(%r(char)i) do |sql_type|
|
148
|
+
limit = extract_limit(sql_type)
|
149
|
+
Type.lookup(:string, adapter: :trilogy, limit: limit)
|
150
|
+
end
|
151
|
+
|
152
|
+
m.register_type %r(^enum)i, Type.lookup(:string, adapter: :trilogy)
|
153
|
+
m.register_type %r(^set)i, Type.lookup(:string, adapter: :trilogy)
|
154
|
+
end
|
111
155
|
end
|
112
156
|
|
113
157
|
def initialize(connection, logger, connection_options, config)
|
@@ -118,6 +162,8 @@ module ActiveRecord
|
|
118
162
|
)
|
119
163
|
end
|
120
164
|
|
165
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
166
|
+
|
121
167
|
def supports_json?
|
122
168
|
!mariadb? && database_version >= "5.7.8"
|
123
169
|
end
|
@@ -143,7 +189,7 @@ module ActiveRecord
|
|
143
189
|
end
|
144
190
|
|
145
191
|
def quote_string(string)
|
146
|
-
with_trilogy_connection(allow_retry: true,
|
192
|
+
with_trilogy_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
147
193
|
conn.escape(string)
|
148
194
|
end
|
149
195
|
end
|
@@ -157,36 +203,17 @@ module ActiveRecord
|
|
157
203
|
@lock.synchronize do
|
158
204
|
disconnect!
|
159
205
|
connect
|
160
|
-
rescue StandardError => original_exception
|
161
|
-
raise translate_exception_class(original_exception, nil, nil)
|
162
206
|
end
|
163
207
|
end
|
164
208
|
|
165
|
-
def with_trilogy_connection(
|
209
|
+
def with_trilogy_connection(materialize_transactions: true, **_kwargs)
|
166
210
|
@lock.synchronize do
|
167
211
|
verify!
|
168
|
-
materialize_transactions if
|
212
|
+
materialize_transactions if materialize_transactions
|
169
213
|
yield connection
|
170
214
|
end
|
171
215
|
end
|
172
216
|
|
173
|
-
def raw_execute(sql, name, async: false, allow_retry: false, uses_transaction: true)
|
174
|
-
mark_transaction_written_if_write(sql)
|
175
|
-
|
176
|
-
log(sql, name, async: async) do
|
177
|
-
with_trilogy_connection(allow_retry: allow_retry, uses_transaction: uses_transaction) do |conn|
|
178
|
-
sync_timezone_changes(conn)
|
179
|
-
conn.query(sql)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def execute(sql, name = nil, **kwargs)
|
185
|
-
sql = transform_query(sql)
|
186
|
-
check_if_write_query(sql)
|
187
|
-
super
|
188
|
-
end
|
189
|
-
|
190
217
|
def active?
|
191
218
|
return false if connection&.closed?
|
192
219
|
|
@@ -206,90 +233,78 @@ module ActiveRecord
|
|
206
233
|
end
|
207
234
|
|
208
235
|
def discard!
|
209
|
-
|
236
|
+
super
|
237
|
+
unless connection.nil?
|
238
|
+
connection.discard!
|
239
|
+
self.connection = nil
|
240
|
+
end
|
210
241
|
end
|
211
242
|
|
212
|
-
def
|
213
|
-
|
243
|
+
def self.find_cmd_and_exec(commands, *args) # :doc:
|
244
|
+
commands = Array(commands)
|
214
245
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
idx = 0
|
219
|
-
row.each do |value|
|
220
|
-
hash[keys[idx]] = value
|
221
|
-
idx += 1
|
222
|
-
end
|
223
|
-
yield hash
|
246
|
+
dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR)
|
247
|
+
unless (ext = RbConfig::CONFIG["EXEEXT"]).empty?
|
248
|
+
commands = commands.map { |cmd| "#{cmd}#{ext}" }
|
224
249
|
end
|
225
250
|
|
226
|
-
nil
|
227
|
-
|
251
|
+
full_path_command = nil
|
252
|
+
found = commands.detect do |cmd|
|
253
|
+
dirs_on_path.detect do |path|
|
254
|
+
full_path_command = File.join(path, cmd)
|
255
|
+
begin
|
256
|
+
stat = File.stat(full_path_command)
|
257
|
+
rescue SystemCallError
|
258
|
+
else
|
259
|
+
stat.file? && stat.executable?
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
228
263
|
|
229
|
-
|
230
|
-
|
264
|
+
if found
|
265
|
+
exec full_path_command, *args
|
266
|
+
else
|
267
|
+
abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
|
268
|
+
end
|
231
269
|
end
|
232
270
|
|
233
271
|
private
|
234
|
-
|
235
|
-
|
236
|
-
def connect
|
237
|
-
self.connection = self.class.new_client(@config)
|
238
|
-
end
|
239
|
-
|
240
|
-
def reconnect
|
241
|
-
connection&.close
|
242
|
-
self.connection = nil
|
243
|
-
connect
|
272
|
+
def text_type?(type)
|
273
|
+
TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
|
244
274
|
end
|
245
275
|
|
246
|
-
def
|
247
|
-
|
248
|
-
if ActiveRecord.default_timezone == :local
|
249
|
-
conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
250
|
-
else
|
251
|
-
conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
252
|
-
end
|
253
|
-
end
|
276
|
+
def each_hash(result)
|
277
|
+
return to_enum(:each_hash, result) unless block_given?
|
254
278
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
279
|
+
keys = result.columns.map(&:to_sym)
|
280
|
+
result.rows.each do |row|
|
281
|
+
hash = {}
|
282
|
+
idx = 0
|
283
|
+
row.each do |value|
|
284
|
+
hash[keys[idx]] = value
|
285
|
+
idx += 1
|
261
286
|
end
|
287
|
+
yield hash
|
262
288
|
end
|
263
|
-
end
|
264
289
|
|
265
|
-
|
266
|
-
!!@config[:multi_statement]
|
290
|
+
nil
|
267
291
|
end
|
268
292
|
|
269
|
-
def
|
270
|
-
if
|
271
|
-
|
272
|
-
end
|
293
|
+
def error_number(exception)
|
294
|
+
exception.error_code if exception.respond_to?(:error_code)
|
295
|
+
end
|
273
296
|
|
274
|
-
|
275
|
-
conn.set_server_option(Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
297
|
+
attr_accessor :connection
|
276
298
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
end
|
299
|
+
def connect
|
300
|
+
self.connection = self.class.new_client(@config)
|
301
|
+
configure_connection
|
281
302
|
end
|
282
303
|
|
283
|
-
def
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
total_sql_chunks << +sql
|
288
|
-
else
|
289
|
-
previous_packet << ";\n"
|
290
|
-
previous_packet << sql
|
291
|
-
end
|
292
|
-
end
|
304
|
+
def reconnect
|
305
|
+
connection&.close
|
306
|
+
self.connection = nil
|
307
|
+
connect
|
293
308
|
end
|
294
309
|
|
295
310
|
def max_allowed_packet_reached?(current_packet, previous_packet)
|
@@ -312,21 +327,88 @@ module ActiveRecord
|
|
312
327
|
end
|
313
328
|
|
314
329
|
def get_full_version
|
315
|
-
with_trilogy_connection(allow_retry: true,
|
330
|
+
with_trilogy_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
316
331
|
conn.server_info[:version]
|
317
332
|
end
|
318
333
|
end
|
319
334
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
335
|
+
if ActiveRecord.version < ::Gem::Version.new('7.0.a') # For ActiveRecord <= 6.1
|
336
|
+
def default_timezone
|
337
|
+
ActiveRecord::Base.default_timezone
|
338
|
+
end
|
339
|
+
else # For ActiveRecord 7.0
|
340
|
+
def default_timezone
|
341
|
+
ActiveRecord.default_timezone
|
342
|
+
end
|
325
343
|
end
|
326
344
|
|
327
345
|
def default_prepared_statements
|
328
346
|
false
|
329
347
|
end
|
348
|
+
|
349
|
+
if ActiveRecord.version < ::Gem::Version.new('6.1.a') # For ActiveRecord <= 6.0
|
350
|
+
def prepared_statements?
|
351
|
+
@prepared_statements && !prepared_statements_disabled_cache.include?(object_id)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def default_insert_value(column)
|
356
|
+
super unless column.auto_increment?
|
357
|
+
end
|
358
|
+
|
359
|
+
# https://mariadb.com/kb/en/analyze-statement/
|
360
|
+
def analyze_without_explain?
|
361
|
+
mariadb? && database_version >= "10.1.0"
|
362
|
+
end
|
363
|
+
|
364
|
+
ActiveRecord::Type.register(:immutable_string, adapter: :trilogy) do |_, **args|
|
365
|
+
Type::ImmutableString.new(true: "1", false: "0", **args)
|
366
|
+
end
|
367
|
+
|
368
|
+
ActiveRecord::Type.register(:string, adapter: :trilogy) do |_, **args|
|
369
|
+
Type::String.new(true: "1", false: "0", **args)
|
370
|
+
end
|
371
|
+
|
372
|
+
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :trilogy)
|
373
|
+
end
|
374
|
+
|
375
|
+
if ActiveRecord.version < ::Gem::Version.new('6.1.a') # For ActiveRecord <= 6.0
|
376
|
+
class PoolConfig < ConnectionSpecification
|
377
|
+
def initialize(connection_class, db_config, *args)
|
378
|
+
super("primary", db_config, "#{db_config[:adapter]}_connection")
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
SchemaCache.class_exec do
|
383
|
+
def self.load_from(filename)
|
384
|
+
return unless File.file?(filename)
|
385
|
+
|
386
|
+
read(filename) do |file|
|
387
|
+
if filename.include?(".dump")
|
388
|
+
Marshal.load(file)
|
389
|
+
else
|
390
|
+
if YAML.respond_to?(:unsafe_load)
|
391
|
+
YAML.unsafe_load(file)
|
392
|
+
else
|
393
|
+
YAML.load(file)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def self.read(filename, &block)
|
400
|
+
if File.extname(filename) == ".gz"
|
401
|
+
Zlib::GzipReader.open(filename) { |gz|
|
402
|
+
yield gz.read
|
403
|
+
}
|
404
|
+
else
|
405
|
+
yield File.read(filename)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
private_class_method :read
|
409
|
+
end
|
330
410
|
end
|
411
|
+
|
412
|
+
ActiveSupport.run_load_hooks(:active_record_trilogyadapter, TrilogyAdapter)
|
331
413
|
end
|
332
414
|
end
|
@@ -0,0 +1,43 @@
|
|
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
|
22
|
+
|
23
|
+
module Rails
|
24
|
+
class DBConsole
|
25
|
+
# require "rails/commands/dbconsole/dbconsole_command"
|
26
|
+
if ActiveRecord.version < ::Gem::Version.new('6.1.a')
|
27
|
+
alias _brick_start start
|
28
|
+
def start
|
29
|
+
ENV["RAILS_ENV"] ||= @options[:environment] || environment
|
30
|
+
|
31
|
+
if config["adapter"] == "trilogy"
|
32
|
+
begin
|
33
|
+
::ActiveRecord::ConnectionAdapters::TrilogyAdapter.dbconsole(config, @options)
|
34
|
+
rescue NotImplementedError
|
35
|
+
abort "Unknown command-line client for #{db_config.database}."
|
36
|
+
end
|
37
|
+
else
|
38
|
+
_brick_start
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-trilogy-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trilogy
|
@@ -30,20 +30,20 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 6.0.a
|
34
34
|
- - "<"
|
35
35
|
- !ruby/object:Gem::Version
|
36
|
-
version: 7.
|
36
|
+
version: 7.1.a
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
43
|
+
version: 6.0.a
|
44
44
|
- - "<"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 7.
|
46
|
+
version: 7.1.a
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: minitest
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,12 +111,12 @@ extra_rdoc_files:
|
|
111
111
|
files:
|
112
112
|
- LICENSE.md
|
113
113
|
- README.md
|
114
|
+
- lib/active_record/connection_adapters/trilogy/database_statements.rb
|
114
115
|
- lib/active_record/connection_adapters/trilogy_adapter.rb
|
115
116
|
- lib/active_record/tasks/trilogy_database_tasks.rb
|
116
117
|
- lib/activerecord-trilogy-adapter.rb
|
117
118
|
- lib/trilogy_adapter/connection.rb
|
118
|
-
- lib/trilogy_adapter/
|
119
|
-
- lib/trilogy_adapter/lost_connection_exception_translator.rb
|
119
|
+
- lib/trilogy_adapter/rails/dbconsole.rb
|
120
120
|
- lib/trilogy_adapter/railtie.rb
|
121
121
|
- lib/trilogy_adapter/version.rb
|
122
122
|
homepage: https://github.com/trilogy-libraries/activerecord-trilogy-adapter
|
@@ -1,45 +0,0 @@
|
|
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::QueryAborted
|
7
|
-
end
|
8
|
-
|
9
|
-
# ServerLost will be raised when the database connection was lost.
|
10
|
-
class ServerLost < ::ActiveRecord::QueryAborted
|
11
|
-
end
|
12
|
-
|
13
|
-
# ServerGone will be raised when the database connection is gone.
|
14
|
-
class ServerGone < ::ActiveRecord::QueryAborted
|
15
|
-
end
|
16
|
-
|
17
|
-
# BrokenPipe will be raised when a system process connection fails.
|
18
|
-
class BrokenPipe < ::ActiveRecord::QueryAborted
|
19
|
-
end
|
20
|
-
|
21
|
-
# SocketError will be raised when Ruby encounters a network error.
|
22
|
-
class SocketError < ::ActiveRecord::QueryAborted
|
23
|
-
end
|
24
|
-
|
25
|
-
# ConnectionResetByPeer will be raised when a network connection is closed
|
26
|
-
# outside the sytstem process.
|
27
|
-
class ConnectionResetByPeer < ::ActiveRecord::QueryAborted
|
28
|
-
end
|
29
|
-
|
30
|
-
# ClosedConnection will be raised when the Trilogy encounters a closed
|
31
|
-
# connection.
|
32
|
-
class ClosedConnection < ::ActiveRecord::QueryAborted
|
33
|
-
end
|
34
|
-
|
35
|
-
# InvalidSequenceId will be raised when Trilogy ecounters an invalid sequence
|
36
|
-
# id.
|
37
|
-
class InvalidSequenceId < ::ActiveRecord::QueryAborted
|
38
|
-
end
|
39
|
-
|
40
|
-
# UnexpectedPacket will be raised when Trilogy ecounters an unexpected
|
41
|
-
# response packet.
|
42
|
-
class UnexpectedPacket < ::ActiveRecord::QueryAborted
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,60 +0,0 @@
|
|
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, IOError
|
39
|
-
Errors::SocketError.new(message)
|
40
|
-
when Trilogy::ConnectionError
|
41
|
-
if message.match?(/Connection reset by peer/)
|
42
|
-
Errors::ConnectionResetByPeer.new(message)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def translate_trilogy_exception
|
48
|
-
return unless exception.is_a?(Trilogy::Error)
|
49
|
-
|
50
|
-
case message
|
51
|
-
when /TRILOGY_CLOSED_CONNECTION/
|
52
|
-
Errors::ClosedConnection.new(message)
|
53
|
-
when /TRILOGY_INVALID_SEQUENCE_ID/
|
54
|
-
Errors::InvalidSequenceId.new(message)
|
55
|
-
when /TRILOGY_UNEXPECTED_PACKET/
|
56
|
-
Errors::UnexpectedPacket.new(message)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|