activerecord-jdbc-adapter 70.2-java → 71.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +18 -18
- data/.gitignore +8 -0
- data/Gemfile +17 -4
- data/README.md +8 -3
- data/RUNNING_TESTS.md +36 -0
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/lib/arjdbc/abstract/connection_management.rb +25 -10
- data/lib/arjdbc/abstract/core.rb +5 -12
- data/lib/arjdbc/abstract/database_statements.rb +35 -35
- data/lib/arjdbc/abstract/relation_query_attribute_monkey_patch.rb +24 -0
- data/lib/arjdbc/abstract/statement_cache.rb +2 -7
- data/lib/arjdbc/abstract/transaction_support.rb +37 -22
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/column.rb +0 -34
- data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
- data/lib/arjdbc/mysql/adapter.rb +106 -27
- data/lib/arjdbc/postgresql/adapter.rb +252 -105
- data/lib/arjdbc/postgresql/database_statements.rb +20 -0
- data/lib/arjdbc/postgresql/oid_types.rb +8 -27
- data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
- data/lib/arjdbc/sqlite3/adapter.rb +213 -145
- data/lib/arjdbc/sqlite3/column.rb +103 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/02-test.rake +1 -1
- data/rakelib/rails.rake +2 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +11 -0
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +11 -10
@@ -12,11 +12,11 @@ module ArJdbc
|
|
12
12
|
# @since 1.3.0
|
13
13
|
# @override
|
14
14
|
def supports_savepoints?
|
15
|
-
@
|
15
|
+
@raw_connection.supports_savepoints?
|
16
16
|
end
|
17
17
|
|
18
18
|
def supports_transaction_isolation?
|
19
|
-
@
|
19
|
+
@raw_connection.supports_transaction_isolation?
|
20
20
|
end
|
21
21
|
|
22
22
|
########################## Transaction Interface ##########################
|
@@ -24,26 +24,42 @@ module ArJdbc
|
|
24
24
|
# Starts a database transaction.
|
25
25
|
# @override
|
26
26
|
def begin_db_transaction
|
27
|
-
log('BEGIN', 'TRANSACTION')
|
27
|
+
log('BEGIN', 'TRANSACTION') do
|
28
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
29
|
+
conn.begin
|
30
|
+
end
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
# Starts a database transaction.
|
31
35
|
# @param isolation the transaction isolation to use
|
32
36
|
def begin_isolated_db_transaction(isolation)
|
33
|
-
log("BEGIN ISOLATED - #{isolation}", 'TRANSACTION')
|
37
|
+
log("BEGIN ISOLATED - #{isolation}", 'TRANSACTION') do
|
38
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
39
|
+
conn.begin(isolation)
|
40
|
+
end
|
41
|
+
end
|
34
42
|
end
|
35
43
|
|
36
44
|
# Commits the current database transaction.
|
37
45
|
# @override
|
38
46
|
def commit_db_transaction
|
39
|
-
log('COMMIT', 'TRANSACTION')
|
47
|
+
log('COMMIT', 'TRANSACTION') do
|
48
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
49
|
+
conn.commit
|
50
|
+
end
|
51
|
+
end
|
40
52
|
end
|
41
53
|
|
42
54
|
# Rolls back the current database transaction.
|
43
55
|
# Called from 'rollback_db_transaction' in the AbstractAdapter
|
44
56
|
# @override
|
45
57
|
def exec_rollback_db_transaction
|
46
|
-
log('ROLLBACK', 'TRANSACTION')
|
58
|
+
log('ROLLBACK', 'TRANSACTION') do
|
59
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
60
|
+
conn.rollback
|
61
|
+
end
|
62
|
+
end
|
47
63
|
end
|
48
64
|
|
49
65
|
########################## Savepoint Interface ############################
|
@@ -55,7 +71,11 @@ module ArJdbc
|
|
55
71
|
# @since 1.3.0
|
56
72
|
# @extension added optional name parameter
|
57
73
|
def create_savepoint(name = current_savepoint_name)
|
58
|
-
log("SAVEPOINT #{name}", 'TRANSACTION')
|
74
|
+
log("SAVEPOINT #{name}", 'TRANSACTION') do
|
75
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
76
|
+
conn.create_savepoint(name)
|
77
|
+
end
|
78
|
+
end
|
59
79
|
end
|
60
80
|
|
61
81
|
# Transaction rollback to a given (previously created) save-point.
|
@@ -64,7 +84,11 @@ module ArJdbc
|
|
64
84
|
# @param name the save-point name
|
65
85
|
# @extension added optional name parameter
|
66
86
|
def exec_rollback_to_savepoint(name = current_savepoint_name)
|
67
|
-
log("ROLLBACK TO SAVEPOINT #{name}", 'TRANSACTION')
|
87
|
+
log("ROLLBACK TO SAVEPOINT #{name}", 'TRANSACTION') do
|
88
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
89
|
+
conn.rollback_savepoint(name)
|
90
|
+
end
|
91
|
+
end
|
68
92
|
end
|
69
93
|
|
70
94
|
# Release a previously created save-point.
|
@@ -73,22 +97,13 @@ module ArJdbc
|
|
73
97
|
# @param name the save-point name
|
74
98
|
# @extension added optional name parameter
|
75
99
|
def release_savepoint(name = current_savepoint_name)
|
76
|
-
log("RELEASE SAVEPOINT #{name}", 'TRANSACTION')
|
100
|
+
log("RELEASE SAVEPOINT #{name}", 'TRANSACTION') do
|
101
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
|
102
|
+
conn.release_savepoint(name)
|
103
|
+
end
|
104
|
+
end
|
77
105
|
end
|
78
106
|
|
79
107
|
end
|
80
108
|
end
|
81
109
|
end
|
82
|
-
|
83
|
-
# patch to avoid the usage of WeakMap
|
84
|
-
require 'active_record/connection_adapters/abstract/transaction'
|
85
|
-
module ActiveRecord
|
86
|
-
module ConnectionAdapters
|
87
|
-
class Transaction
|
88
|
-
def add_record(record, ensure_finalize = true)
|
89
|
-
@records ||= []
|
90
|
-
@records << record
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
Binary file
|
data/lib/arjdbc/jdbc/column.rb
CHANGED
@@ -12,45 +12,11 @@ module ActiveRecord
|
|
12
12
|
# specific type.
|
13
13
|
# @see JdbcAdapter#jdbc_column_class
|
14
14
|
class JdbcColumn < Column
|
15
|
-
# @deprecated attribute writers will be removed in 1.4
|
16
|
-
attr_writer :limit, :precision # unless ArJdbc::AR42
|
17
|
-
|
18
|
-
def initialize(config, name, *args)
|
19
|
-
if self.class == JdbcColumn
|
20
|
-
# NOTE: extending classes do not want this if they do they shall call
|
21
|
-
call_discovered_column_callbacks(config) if config
|
22
|
-
default = args.shift
|
23
|
-
else # for extending classes allow ignoring first argument :
|
24
|
-
if ! config.nil? && ! config.is_a?(Hash)
|
25
|
-
default = name; name = config # initialize(name, default, *args)
|
26
|
-
else
|
27
|
-
default = args.shift
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
super(name, default, *args)
|
32
|
-
init_column(name, default, *args)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Additional column initialization for sub-classes.
|
36
|
-
def init_column(*args); end
|
37
15
|
|
38
16
|
# Similar to `ActiveRecord`'s `extract_value_from_default(default)`.
|
39
17
|
# @return default value for a column (possibly extracted from driver value)
|
40
18
|
def default_value(value); value; end
|
41
19
|
|
42
|
-
protected
|
43
|
-
|
44
|
-
# @private
|
45
|
-
def call_discovered_column_callbacks(config)
|
46
|
-
dialect = (config[:dialect] || config[:driver]).to_s
|
47
|
-
for matcher, block in self.class.column_types
|
48
|
-
block.call(config, self) if matcher === dialect
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
public
|
53
|
-
|
54
20
|
# Returns the available column types
|
55
21
|
# @return [Hash] of (matcher, block) pairs
|
56
22
|
def self.column_types
|
@@ -7,7 +7,7 @@ module ArJdbc
|
|
7
7
|
|
8
8
|
def jdbc_connection(config)
|
9
9
|
adapter_class = config[:adapter_class] || ::ActiveRecord::ConnectionAdapters::JdbcAdapter
|
10
|
-
adapter_class.new(
|
10
|
+
adapter_class.new(config)
|
11
11
|
end
|
12
12
|
|
13
13
|
def jndi_connection(config); jdbc_connection(config) end
|
data/lib/arjdbc/mysql/adapter.rb
CHANGED
@@ -11,6 +11,8 @@ require 'arjdbc/abstract/database_statements'
|
|
11
11
|
require 'arjdbc/abstract/statement_cache'
|
12
12
|
require 'arjdbc/abstract/transaction_support'
|
13
13
|
|
14
|
+
require "arjdbc/abstract/relation_query_attribute_monkey_patch"
|
15
|
+
|
14
16
|
module ActiveRecord
|
15
17
|
module ConnectionAdapters
|
16
18
|
AbstractMysqlAdapter.class_eval do
|
@@ -23,7 +25,7 @@ module ActiveRecord
|
|
23
25
|
class Mysql2Adapter < AbstractMysqlAdapter
|
24
26
|
ADAPTER_NAME = 'Mysql2'
|
25
27
|
|
26
|
-
include Jdbc::ConnectionPoolCallbacks
|
28
|
+
# include Jdbc::ConnectionPoolCallbacks
|
27
29
|
|
28
30
|
include ArJdbc::Abstract::ConnectionManagement
|
29
31
|
include ArJdbc::Abstract::DatabaseStatements
|
@@ -33,11 +35,47 @@ module ActiveRecord
|
|
33
35
|
|
34
36
|
include ArJdbc::MySQL
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
class << self
|
39
|
+
def jdbc_connection_class
|
40
|
+
::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
|
41
|
+
end
|
42
|
+
|
43
|
+
def new_client(conn_params, adapter_instance)
|
44
|
+
jdbc_connection_class.new(conn_params, adapter_instance)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def initialize_type_map(m)
|
49
|
+
super
|
50
|
+
|
51
|
+
m.register_type(%r(char)i) do |sql_type|
|
52
|
+
limit = extract_limit(sql_type)
|
53
|
+
Type.lookup(:string, adapter: :mysql2, limit: limit)
|
54
|
+
end
|
39
55
|
|
40
|
-
|
56
|
+
m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
|
57
|
+
m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# NOTE: redefines constant defined in abstract class however this time
|
62
|
+
# will use methods defined in the mysql abstract class and map properly
|
63
|
+
# mysql types.
|
64
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
65
|
+
|
66
|
+
def initialize(...)
|
67
|
+
super
|
68
|
+
|
69
|
+
@config[:flags] ||= 0
|
70
|
+
|
71
|
+
# JDBC mysql appears to use found rows by default: https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-connection.html
|
72
|
+
# if @config[:flags].kind_of? Array
|
73
|
+
# @config[:flags].push "FOUND_ROWS"
|
74
|
+
# else
|
75
|
+
# @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
|
76
|
+
# end
|
77
|
+
|
78
|
+
@connection_parameters ||= @config
|
41
79
|
end
|
42
80
|
|
43
81
|
def self.database_exists?(config)
|
@@ -49,13 +87,6 @@ module ActiveRecord
|
|
49
87
|
conn.disconnect! if conn
|
50
88
|
end
|
51
89
|
|
52
|
-
def check_version
|
53
|
-
# for JNDI, don't check version as the whole connection should be lazy
|
54
|
-
return if ::ActiveRecord::ConnectionAdapters::JdbcConnection.jndi_config?(config)
|
55
|
-
|
56
|
-
super
|
57
|
-
end
|
58
|
-
|
59
90
|
def supports_json?
|
60
91
|
!mariadb? && database_version >= '5.7.8'
|
61
92
|
end
|
@@ -96,20 +127,25 @@ module ActiveRecord
|
|
96
127
|
!READ_QUERY.match?(sql)
|
97
128
|
end
|
98
129
|
|
99
|
-
def explain(arel, binds = [])
|
100
|
-
sql = "
|
101
|
-
start =
|
102
|
-
result =
|
103
|
-
elapsed =
|
130
|
+
def explain(arel, binds = [], options = [])
|
131
|
+
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
132
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
133
|
+
result = internal_exec_query(sql, "EXPLAIN", binds)
|
134
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
104
135
|
|
105
136
|
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
106
137
|
end
|
107
138
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
#
|
112
|
-
|
139
|
+
def build_explain_clause(options = [])
|
140
|
+
return "EXPLAIN" if options.empty?
|
141
|
+
|
142
|
+
explain_clause = "EXPLAIN #{options.join(" ").upcase}"
|
143
|
+
|
144
|
+
if analyze_without_explain? && explain_clause.include?("ANALYZE")
|
145
|
+
explain_clause.sub("EXPLAIN ", "")
|
146
|
+
else
|
147
|
+
explain_clause
|
148
|
+
end
|
113
149
|
end
|
114
150
|
|
115
151
|
def each_hash(result) # :nodoc:
|
@@ -164,11 +200,47 @@ module ActiveRecord
|
|
164
200
|
# CONNECTION MANAGEMENT ====================================
|
165
201
|
#++
|
166
202
|
|
203
|
+
def active?
|
204
|
+
!(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
|
205
|
+
end
|
206
|
+
|
167
207
|
alias :reset! :reconnect!
|
168
208
|
|
209
|
+
# Disconnects from the database if already connected.
|
210
|
+
# Otherwise, this method does nothing.
|
211
|
+
def disconnect!
|
212
|
+
@lock.synchronize do
|
213
|
+
super
|
214
|
+
@raw_connection&.close
|
215
|
+
@raw_connection = nil
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def discard! # :nodoc:
|
220
|
+
@lock.synchronize do
|
221
|
+
super
|
222
|
+
@raw_connection&.automatic_close = false
|
223
|
+
@raw_connection = nil
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
169
227
|
#
|
170
228
|
|
171
229
|
private
|
230
|
+
# https://mariadb.com/kb/en/analyze-statement/
|
231
|
+
def analyze_without_explain?
|
232
|
+
mariadb? && database_version >= "10.1.0"
|
233
|
+
end
|
234
|
+
|
235
|
+
def text_type?(type)
|
236
|
+
TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
|
237
|
+
end
|
238
|
+
|
239
|
+
def configure_connection
|
240
|
+
# @raw_connection.query_options[:as] = :array
|
241
|
+
# @raw_connection.query_options[:database_timezone] = default_timezone
|
242
|
+
super
|
243
|
+
end
|
172
244
|
|
173
245
|
# e.g. "5.7.20-0ubuntu0.16.04.1"
|
174
246
|
def full_version
|
@@ -176,17 +248,24 @@ module ActiveRecord
|
|
176
248
|
end
|
177
249
|
|
178
250
|
def get_full_version
|
179
|
-
@full_version ||=
|
180
|
-
end
|
181
|
-
|
182
|
-
def jdbc_connection_class(spec)
|
183
|
-
::ActiveRecord::ConnectionAdapters::MySQLJdbcConnection
|
251
|
+
@full_version ||= any_raw_connection.full_version
|
184
252
|
end
|
185
253
|
|
186
254
|
def jdbc_column_class
|
187
255
|
::ActiveRecord::ConnectionAdapters::MySQL::Column
|
188
256
|
end
|
189
257
|
|
258
|
+
def translate_exception(exception, message:, sql:, binds:)
|
259
|
+
case message
|
260
|
+
when /Table .* doesn't exist/i
|
261
|
+
StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
262
|
+
when /BLOB, TEXT, GEOMETRY or JSON column .* can't have a default value/i
|
263
|
+
StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
264
|
+
else
|
265
|
+
super
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
190
269
|
# defined in MySQL::DatabaseStatements which is not included
|
191
270
|
def default_insert_value(column)
|
192
271
|
super unless column.auto_increment?
|