activerecord-jdbc-adapter 70.1-java → 71.0-java
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/.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/mysql/connection_methods.rb +43 -42
- 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 +12 -11
@@ -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?
|
@@ -126,51 +126,52 @@ ArJdbc::ConnectionMethods.module_eval do
|
|
126
126
|
|
127
127
|
private
|
128
128
|
|
129
|
-
|
129
|
+
MYSQL_ENCODINGS = {
|
130
|
+
"big5" => "Big5",
|
131
|
+
"dec8" => nil,
|
132
|
+
#"cp850" => "Cp850",
|
133
|
+
"hp8" => nil,
|
134
|
+
#"koi8r" => "KOI8-R",
|
135
|
+
"latin1" => "Cp1252",
|
136
|
+
"latin2" => "ISO8859_2",
|
137
|
+
"swe7" => nil,
|
138
|
+
"ascii" => "US-ASCII",
|
139
|
+
"ujis" => "EUC_JP",
|
140
|
+
"sjis" => "SJIS",
|
141
|
+
"hebrew" => "ISO8859_8",
|
142
|
+
"tis620" => "TIS620",
|
143
|
+
"euckr" => "EUC_KR",
|
144
|
+
#"koi8u" => "KOI8-R",
|
145
|
+
"gb2312" => "EUC_CN",
|
146
|
+
"greek" => "ISO8859_7",
|
147
|
+
"cp1250" => "Cp1250",
|
148
|
+
"gbk" => "GBK",
|
149
|
+
#"latin5" => "ISO-8859-9",
|
150
|
+
"armscii8" => nil,
|
151
|
+
"ucs2" => "UnicodeBig",
|
152
|
+
"cp866" => "Cp866",
|
153
|
+
"keybcs2" => nil,
|
154
|
+
"macce" => "MacCentralEurope",
|
155
|
+
"macroman" => "MacRoman",
|
156
|
+
#"cp852" => "CP852",
|
157
|
+
#"latin7" => "ISO-8859-13",
|
158
|
+
"cp1251" => "Cp1251",
|
159
|
+
"cp1256" => "Cp1256",
|
160
|
+
"cp1257" => "Cp1257",
|
161
|
+
"binary" => false,
|
162
|
+
"geostd8" => nil,
|
163
|
+
"cp932" => "Cp932",
|
164
|
+
#"eucjpms" => "eucJP-ms"
|
165
|
+
"utf8" => "UTF-8",
|
166
|
+
"utf8mb4" => false,
|
167
|
+
"utf16" => false,
|
168
|
+
"utf32" => false,
|
169
|
+
}
|
170
|
+
|
130
171
|
|
131
172
|
# @see https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-charsets.html
|
132
173
|
def convert_mysql_encoding(encoding) # to charset-name (characterEncoding=...)
|
133
|
-
|
134
|
-
"big5" => "Big5",
|
135
|
-
"dec8" => nil,
|
136
|
-
#"cp850" => "Cp850",
|
137
|
-
"hp8" => nil,
|
138
|
-
#"koi8r" => "KOI8-R",
|
139
|
-
"latin1" => "Cp1252",
|
140
|
-
"latin2" => "ISO8859_2",
|
141
|
-
"swe7" => nil,
|
142
|
-
"ascii" => "US-ASCII",
|
143
|
-
"ujis" => "EUC_JP",
|
144
|
-
"sjis" => "SJIS",
|
145
|
-
"hebrew" => "ISO8859_8",
|
146
|
-
"tis620" => "TIS620",
|
147
|
-
"euckr" => "EUC_KR",
|
148
|
-
#"koi8u" => "KOI8-R",
|
149
|
-
"gb2312" => "EUC_CN",
|
150
|
-
"greek" => "ISO8859_7",
|
151
|
-
"cp1250" => "Cp1250",
|
152
|
-
"gbk" => "GBK",
|
153
|
-
#"latin5" => "ISO-8859-9",
|
154
|
-
"armscii8" => nil,
|
155
|
-
"ucs2" => "UnicodeBig",
|
156
|
-
"cp866" => "Cp866",
|
157
|
-
"keybcs2" => nil,
|
158
|
-
"macce" => "MacCentralEurope",
|
159
|
-
"macroman" => "MacRoman",
|
160
|
-
#"cp852" => "CP852",
|
161
|
-
#"latin7" => "ISO-8859-13",
|
162
|
-
"cp1251" => "Cp1251",
|
163
|
-
"cp1256" => "Cp1256",
|
164
|
-
"cp1257" => "Cp1257",
|
165
|
-
"binary" => false,
|
166
|
-
"geostd8" => nil,
|
167
|
-
"cp932" => "Cp932",
|
168
|
-
#"eucjpms" => "eucJP-ms"
|
169
|
-
"utf8" => "UTF-8",
|
170
|
-
"utf8mb4" => false,
|
171
|
-
"utf16" => false,
|
172
|
-
"utf32" => false,
|
173
|
-
} )[ encoding ]
|
174
|
+
MYSQL_ENCODINGS[ encoding ]
|
174
175
|
end
|
175
176
|
|
176
177
|
end
|