activerecord-jdbc-alt-adapter 72.1.0-java → 80.0.0.rc1-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/main.yml +23 -16
- data/.github/workflows/ruby.yml +123 -18
- data/.mise.toml +3 -0
- data/Gemfile +43 -48
- data/README.md +3 -2
- data/RUNNING_TESTS.md +9 -1
- data/Rakefile +63 -15
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/activerecord-jdbc-alt-adapter.gemspec +1 -1
- data/lib/arjdbc/abstract/core.rb +0 -17
- data/lib/arjdbc/abstract/database_statements.rb +21 -18
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/mssql/adapter.rb +11 -11
- data/lib/arjdbc/mssql/database_statements.rb +30 -28
- data/lib/arjdbc/mssql/explain_support.rb +6 -1
- data/lib/arjdbc/mssql.rb +1 -1
- data/lib/arjdbc/postgresql/adapter.rb +4 -1
- data/lib/arjdbc/postgresql/database_statements.rb +45 -0
- data/lib/arjdbc/postgresql/schema_statements.rb +17 -0
- data/lib/arjdbc/sqlite3/adapter.rb +51 -7
- data/lib/arjdbc/sqlite3/database_statements.rb +26 -0
- data/lib/arjdbc/version.rb +1 -1
- data/pom.xml +3 -3
- data/rakelib/02-test.rake +1 -1
- data/rakelib/db.rake +5 -3
- data/rakelib/rails.rake +0 -1
- data/src/java/arjdbc/ArJdbcModule.java +13 -10
- data/src/java/arjdbc/db2/DB2Module.java +4 -4
- data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +8 -9
- data/src/java/arjdbc/h2/H2Module.java +2 -1
- data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +8 -7
- data/src/java/arjdbc/hsqldb/HSQLDBModule.java +5 -6
- data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +4 -3
- data/src/java/arjdbc/jdbc/DriverWrapper.java +13 -8
- data/src/java/arjdbc/jdbc/JdbcResult.java +10 -7
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +168 -154
- data/src/java/arjdbc/mssql/MSSQLModule.java +3 -3
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +13 -9
- data/src/java/arjdbc/mysql/MySQLModule.java +3 -3
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +25 -23
- data/src/java/arjdbc/oracle/OracleModule.java +2 -3
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +22 -18
- data/src/java/arjdbc/postgresql/PostgreSQLModule.java +2 -3
- data/src/java/arjdbc/postgresql/PostgreSQLResult.java +24 -12
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +16 -12
- data/src/java/arjdbc/sqlite3/SQLite3Module.java +2 -3
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +78 -29
- data/src/java/arjdbc/util/DateTimeUtils.java +7 -5
- data/src/java/arjdbc/util/ObjectSupport.java +2 -2
- data/src/java/arjdbc/util/StringHelper.java +1 -1
- metadata +7 -6
- data/.travis.yml +0 -128
data/Rakefile
CHANGED
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
#
|
|
6
6
|
# Environment variables used by this Rakefile:
|
|
7
7
|
#
|
|
8
|
+
# DBS
|
|
9
|
+
# Limits the command performed to only work for one of the database
|
|
10
|
+
# types listed in this env var. You can set to a combination of mysql,
|
|
11
|
+
# postgres, or sqlite, separated by commas. For example:
|
|
12
|
+
#
|
|
13
|
+
# mysql,postgres,sqlite
|
|
14
|
+
#
|
|
15
|
+
# You may use pg or postgres as aliases for postgresql
|
|
16
|
+
# You may use sqlite3 as an alias for sqlite
|
|
17
|
+
# You may use all to mean all three
|
|
18
|
+
#
|
|
19
|
+
# NOTE: if you ever want to add a new type of database to be released,
|
|
20
|
+
# just fix up this documentation, the error string in invalid_dbs,
|
|
21
|
+
# and add it to the ALL_DBS array. Everything else should just work!
|
|
22
|
+
#
|
|
8
23
|
# INCLUDE_JAR_IN_GEM [default task - false, other taks - true]:
|
|
9
24
|
# Note: This is something you should not normally have to set.
|
|
10
25
|
# For local development we always will end up including the jar file
|
|
@@ -85,6 +100,12 @@ end
|
|
|
85
100
|
|
|
86
101
|
desc "Releasing AR-JDBC gems (use NOOP=true to disable gem pushing)"
|
|
87
102
|
task 'release:do' do
|
|
103
|
+
unless ENV["DBS"]
|
|
104
|
+
puts "you must explicitly provide a DBS env var when calling release:do. An empty one will not default to 'all' " \
|
|
105
|
+
"for this command\n\n"
|
|
106
|
+
invalid_dbs!
|
|
107
|
+
end
|
|
108
|
+
|
|
88
109
|
Rake::Task['build'].invoke
|
|
89
110
|
# Rake::Task['build:adapters'].invoke
|
|
90
111
|
|
|
@@ -107,8 +128,41 @@ task 'release:push' do
|
|
|
107
128
|
sh "for gem in `ls pkg/*-#{current_version.call}-java.gem`; do gem push $gem; done"
|
|
108
129
|
end
|
|
109
130
|
|
|
110
|
-
|
|
111
|
-
|
|
131
|
+
ALL_DBS = ["mysql", "postgresql", "sqlite3"] #NOTE: if we add a new database type to be released, just add it here!
|
|
132
|
+
DB_ALIASES = ALL_DBS.map {|db| [db, db]}.to_h.merge({
|
|
133
|
+
"pg" => "postgresql",
|
|
134
|
+
"postgres" => "postgresql",
|
|
135
|
+
"sqlite" => "sqlite3"
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
def invalid_dbs!
|
|
139
|
+
raise ArgumentError, "Invalid DBS env var\nThe DBS env var must be set to a combination of mysql, postgres, or " \
|
|
140
|
+
"sqlite, separated by commas. For example:\n\nmysql,postgres,sqlite\n\nYou may use pg or " \
|
|
141
|
+
"postgres as aliases for postgresql\nYou may use sqlite3 as an alias for sqlite\n" \
|
|
142
|
+
"You may use all as a shortcut to listing them all out"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def make_db_list
|
|
146
|
+
env_dbs = ENV["DBS"]
|
|
147
|
+
return ALL_DBS if !env_dbs || env_dbs == "all"
|
|
148
|
+
requested = env_dbs.split(",").map(&:strip).reject(&:empty?).map(&:downcase)
|
|
149
|
+
invalid_dbs! unless requested.size > 0 && requested == requested.uniq
|
|
150
|
+
|
|
151
|
+
canonical = requested.map do |name|
|
|
152
|
+
DB_ALIASES.fetch(name) { invalid_dbs! }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
invalid_dbs! unless canonical == canonical.uniq
|
|
156
|
+
|
|
157
|
+
canonical
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
db_list = make_db_list
|
|
161
|
+
ADAPTERS = db_list.map { |db| "activerecord-jdbc#{db}-adapter" }
|
|
162
|
+
|
|
163
|
+
db_list.map! {|db| db == 'postgresql' ? 'postgres' : db } #naming convention for DRIVERS
|
|
164
|
+
DRIVERS = db_list.map { |a| "jdbc-#{a}" }
|
|
165
|
+
|
|
112
166
|
TARGETS = ( ADAPTERS + DRIVERS )
|
|
113
167
|
|
|
114
168
|
ADAPTERS.each do |target|
|
|
@@ -185,7 +239,7 @@ if defined? JRUBY_VERSION
|
|
|
185
239
|
#directory classes = 'pkg/classes'; CLEAN << classes
|
|
186
240
|
|
|
187
241
|
file jar_file => FileList[ 'src/java/**/*.java' ] do
|
|
188
|
-
source = target = '
|
|
242
|
+
source = target = '21'; debug = true
|
|
189
243
|
|
|
190
244
|
get_driver_jars_local = lambda do |*args|
|
|
191
245
|
driver_deps = args.empty? ? [ :Postgres, :MySQL ] : args
|
|
@@ -230,7 +284,7 @@ if defined? JRUBY_VERSION
|
|
|
230
284
|
if match = requirement.match(/^jar\s+([\w\-\.]+):([\w\-]+),\s+?([\w\.\-]+)?/)
|
|
231
285
|
matched_jar = Jars.send :to_jar, match[1], match[2], match[3], nil
|
|
232
286
|
matched_jar = File.join( Jars.home, matched_jar )
|
|
233
|
-
matched_jars << matched_jar if File.
|
|
287
|
+
matched_jars << matched_jar if File.exist?( matched_jar )
|
|
234
288
|
end
|
|
235
289
|
end
|
|
236
290
|
matched_jars
|
|
@@ -262,18 +316,12 @@ if defined? JRUBY_VERSION
|
|
|
262
316
|
end
|
|
263
317
|
|
|
264
318
|
classpath = []
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if classpath.empty?; require 'rbconfig'
|
|
270
|
-
libdir = RbConfig::CONFIG['libdir']
|
|
271
|
-
if libdir.start_with? 'classpath:'
|
|
272
|
-
error "Cannot build activerecord-jdbc with jruby-complete"
|
|
273
|
-
end
|
|
274
|
-
classpath << File.join(libdir, 'jruby.jar')
|
|
319
|
+
require 'rbconfig'
|
|
320
|
+
libdir = RbConfig::CONFIG['libdir']
|
|
321
|
+
if libdir.start_with? 'classpath:'
|
|
322
|
+
error "Cannot build activerecord-jdbc with jruby-complete"
|
|
275
323
|
end
|
|
276
|
-
|
|
324
|
+
classpath << File.join(libdir, 'jruby.jar')
|
|
277
325
|
classpath += driver_jars
|
|
278
326
|
classpath = classpath.compact.join(File::PATH_SEPARATOR)
|
|
279
327
|
|
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
|
|
10
10
|
gem.homepage = 'https://github.com/jruby/activerecord-jdbc-adapter'
|
|
11
11
|
gem.license = 'BSD-2-Clause'
|
|
12
12
|
gem.summary = 'JDBC adapter for ActiveRecord, for use within JRuby on Rails.'
|
|
13
|
-
gem.description = "" <<
|
|
13
|
+
gem.description = +"" <<
|
|
14
14
|
"AR-JDBC is a database adapter for Rails' ActiveRecord component " <<
|
|
15
15
|
"designed to be used with JRuby built upon Java's JDBC API for " <<
|
|
16
16
|
"database access. Provides (ActiveRecord) built-in adapters: MySQL, " <<
|
|
@@ -41,7 +41,7 @@ Gem::Specification.new do |gem|
|
|
|
41
41
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
42
42
|
gem.test_files = gem.files.grep(%r{^test/})
|
|
43
43
|
|
|
44
|
-
gem.add_dependency "activerecord", "~>
|
|
44
|
+
gem.add_dependency "activerecord", "~> 8.0"
|
|
45
45
|
|
|
46
46
|
#gem.add_development_dependency 'test-unit', '2.5.4'
|
|
47
47
|
#gem.add_development_dependency 'test-unit-context', '>= 0.3.0'
|
|
@@ -43,7 +43,7 @@ Gem::Specification.new do |gem|
|
|
|
43
43
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
44
44
|
gem.test_files = gem.files.grep(%r{^test/})
|
|
45
45
|
|
|
46
|
-
gem.add_dependency "activerecord", "~>
|
|
46
|
+
gem.add_dependency "activerecord", "~> 8.0.0"
|
|
47
47
|
|
|
48
48
|
#gem.add_development_dependency 'test-unit', '2.5.4'
|
|
49
49
|
#gem.add_development_dependency 'test-unit-context', '>= 0.3.0'
|
data/lib/arjdbc/abstract/core.rb
CHANGED
|
@@ -56,23 +56,6 @@ module ArJdbc
|
|
|
56
56
|
else super
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
|
-
|
|
60
|
-
# this version of log() automatically fills type_casted_binds from binds if necessary
|
|
61
|
-
def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, async: false)
|
|
62
|
-
if binds.any? && (type_casted_binds.nil? || type_casted_binds.empty?)
|
|
63
|
-
type_casted_binds = lambda {
|
|
64
|
-
# extract_raw_bind_values
|
|
65
|
-
binds.map do |bind|
|
|
66
|
-
if bind.respond_to?(:value_for_database)
|
|
67
|
-
bind.value_for_database
|
|
68
|
-
else
|
|
69
|
-
bind
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
}
|
|
73
|
-
end
|
|
74
|
-
super
|
|
75
|
-
end
|
|
76
59
|
end
|
|
77
60
|
end
|
|
78
61
|
|
|
@@ -10,8 +10,6 @@ module ArJdbc
|
|
|
10
10
|
NO_BINDS = [].freeze
|
|
11
11
|
|
|
12
12
|
def exec_insert(sql, name = nil, binds = NO_BINDS, pk = nil, sequence_name = nil, returning: nil)
|
|
13
|
-
sql = transform_query(sql)
|
|
14
|
-
|
|
15
13
|
if preventing_writes?
|
|
16
14
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
17
15
|
end
|
|
@@ -20,11 +18,15 @@ module ArJdbc
|
|
|
20
18
|
|
|
21
19
|
binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
|
|
22
20
|
|
|
21
|
+
type_casted_binds = type_casted_binds(binds)
|
|
22
|
+
|
|
23
23
|
with_raw_connection do |conn|
|
|
24
24
|
if without_prepared_statement?(binds)
|
|
25
25
|
log(sql, name) { conn.execute_insert_pk(sql, pk) }
|
|
26
26
|
else
|
|
27
|
-
log(sql, name, binds) do
|
|
27
|
+
log(sql, name, binds, type_casted_binds) do
|
|
28
|
+
# TODO: the ideas is to pass type casted binds but dozens of tests fail
|
|
29
|
+
# conn.execute_insert_pk(sql, type_casted_binds, pk)
|
|
28
30
|
conn.execute_insert_pk(sql, binds, pk)
|
|
29
31
|
end
|
|
30
32
|
end
|
|
@@ -34,8 +36,6 @@ module ArJdbc
|
|
|
34
36
|
# It appears that at this point (AR 5.0) "prepare" should only ever be true
|
|
35
37
|
# if prepared statements are enabled
|
|
36
38
|
def internal_exec_query(sql, name = nil, binds = NO_BINDS, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
|
|
37
|
-
sql = transform_query(sql)
|
|
38
|
-
|
|
39
39
|
if preventing_writes? && write_query?(sql)
|
|
40
40
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
41
41
|
end
|
|
@@ -55,15 +55,13 @@ module ArJdbc
|
|
|
55
55
|
log(sql, name, type_casted_binds, async: async) do
|
|
56
56
|
# this is different from normal AR that always caches
|
|
57
57
|
cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
|
|
58
|
-
conn.execute_prepared_query(sql,
|
|
58
|
+
conn.execute_prepared_query(sql, binds, cached_statement)
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def exec_update(sql, name = 'SQL', binds = NO_BINDS)
|
|
65
|
-
sql = transform_query(sql)
|
|
66
|
-
|
|
67
65
|
if preventing_writes?
|
|
68
66
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
|
69
67
|
end
|
|
@@ -72,11 +70,17 @@ module ArJdbc
|
|
|
72
70
|
|
|
73
71
|
binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
|
|
74
72
|
|
|
73
|
+
type_casted_binds = type_casted_binds(binds)
|
|
74
|
+
|
|
75
75
|
with_raw_connection do |conn|
|
|
76
76
|
if without_prepared_statement?(binds)
|
|
77
77
|
log(sql, name) { conn.execute_update(sql) }
|
|
78
78
|
else
|
|
79
|
-
log(sql, name, binds
|
|
79
|
+
log(sql, name, binds, type_casted_binds) do
|
|
80
|
+
# TODO: the ideas is to pass type casted binds but some tests fail
|
|
81
|
+
# conn.execute_prepared_update(sql, type_casted_binds)
|
|
82
|
+
conn.execute_prepared_update(sql, binds)
|
|
83
|
+
end
|
|
80
84
|
end
|
|
81
85
|
end
|
|
82
86
|
end
|
|
@@ -90,22 +94,21 @@ module ArJdbc
|
|
|
90
94
|
|
|
91
95
|
private
|
|
92
96
|
|
|
97
|
+
def without_prepared_statement?(binds)
|
|
98
|
+
!prepared_statements || binds.empty?
|
|
99
|
+
end
|
|
100
|
+
|
|
93
101
|
def convert_legacy_binds_to_attributes(binds)
|
|
94
102
|
binds.map do |column, value|
|
|
95
103
|
ActiveRecord::Relation::QueryAttribute.new(nil, type_cast(value, column), ActiveModel::Type::Value.new)
|
|
96
104
|
end
|
|
97
105
|
end
|
|
98
106
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
verified!
|
|
104
|
-
result
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
+
def preprocess_query(sql)
|
|
108
|
+
check_if_write_query(sql) if respond_to?(:check_if_write_query, true)
|
|
109
|
+
mark_transaction_written_if_write(sql) if respond_to?(:mark_transaction_written_if_write, true)
|
|
110
|
+
sql
|
|
107
111
|
end
|
|
108
|
-
|
|
109
112
|
end
|
|
110
113
|
end
|
|
111
114
|
end
|
|
Binary file
|
data/lib/arjdbc/mssql/adapter.rb
CHANGED
|
@@ -479,31 +479,31 @@ module ActiveRecord
|
|
|
479
479
|
def translate_exception(exception, message:, sql:, binds:)
|
|
480
480
|
case message
|
|
481
481
|
when /no connection available/i
|
|
482
|
-
ConnectionNotEstablished.new(
|
|
482
|
+
ConnectionNotEstablished.new(message, connection_pool: @pool)
|
|
483
483
|
when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
|
|
484
|
-
RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
484
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
485
485
|
when /Violation of PRIMARY KEY constraint .* Cannot insert duplicate key in object .* The duplicate key value is/i
|
|
486
|
-
RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
486
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
487
487
|
when /Lock request time out period exceeded/i
|
|
488
488
|
LockTimeout.new(message, sql: sql, binds: binds)
|
|
489
489
|
when /The .* statement conflicted with the FOREIGN KEY constraint/
|
|
490
|
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
490
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
491
491
|
when /Could not drop object .* because it is referenced by a FOREIGN KEY constraint/
|
|
492
|
-
StatementInvalid.new(message, sql: sql, binds: binds)
|
|
492
|
+
StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
493
493
|
when /The .* statement conflicted with the REFERENCE constraint/
|
|
494
|
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
494
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
495
495
|
when /(String or binary data would be truncated)/i
|
|
496
|
-
ValueTooLong.new(message, sql: sql, binds: binds)
|
|
496
|
+
ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
497
497
|
when /Cannot insert the value NULL into column .* does not allow nulls/
|
|
498
|
-
NotNullViolation.new(message, sql: sql, binds: binds)
|
|
498
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
499
499
|
when /Arithmetic overflow error converting expression/
|
|
500
500
|
RangeError.new(message, sql: sql, binds: binds)
|
|
501
501
|
when /Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot isolation/
|
|
502
|
-
StatementInvalid.new(message, sql: sql, binds: binds)
|
|
502
|
+
StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
503
503
|
when /Incorrect syntax near the keyword .*/
|
|
504
|
-
StatementInvalid.new(message, sql: sql, binds: binds)
|
|
504
|
+
StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
505
505
|
when /Could not find stored procedure .*/
|
|
506
|
-
StatementInvalid.new(message, sql: sql, binds: binds)
|
|
506
|
+
StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
507
507
|
else
|
|
508
508
|
super
|
|
509
509
|
end
|
|
@@ -93,11 +93,7 @@ module ActiveRecord
|
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def internal_exec_query(sql, name = 'SQL', binds = [], prepare: false, async: false, allow_retry: false)
|
|
96
|
-
sql =
|
|
97
|
-
|
|
98
|
-
check_if_write_query(sql)
|
|
99
|
-
|
|
100
|
-
mark_transaction_written_if_write(sql)
|
|
96
|
+
sql = preprocess_query(sql)
|
|
101
97
|
|
|
102
98
|
# binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
|
|
103
99
|
|
|
@@ -105,7 +101,7 @@ module ActiveRecord
|
|
|
105
101
|
type_casted_binds = type_casted_binds(binds)
|
|
106
102
|
# puts "[2]internal----->sql: #{type_casted_binds.size}, binds: #{type_casted_binds}"
|
|
107
103
|
|
|
108
|
-
if
|
|
104
|
+
if binds.nil? || binds.empty?
|
|
109
105
|
log(sql, name) do
|
|
110
106
|
with_raw_connection do |conn|
|
|
111
107
|
result = conditional_indentity_insert(sql) do
|
|
@@ -132,14 +128,12 @@ module ActiveRecord
|
|
|
132
128
|
end
|
|
133
129
|
|
|
134
130
|
def exec_update(sql, name = nil, binds = [])
|
|
135
|
-
sql =
|
|
131
|
+
sql = preprocess_query(sql)
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
mark_transaction_written_if_write(sql)
|
|
133
|
+
type_casted_binds = type_casted_binds(binds)
|
|
140
134
|
|
|
141
135
|
# puts "exec_update----->sql: #{sql}, binds: #{binds}"
|
|
142
|
-
if
|
|
136
|
+
if binds.nil? || binds.empty?
|
|
143
137
|
log(sql, name) do
|
|
144
138
|
with_raw_connection do |conn|
|
|
145
139
|
result = conn.execute_update(sql)
|
|
@@ -148,9 +142,9 @@ module ActiveRecord
|
|
|
148
142
|
end
|
|
149
143
|
end
|
|
150
144
|
else
|
|
151
|
-
log(sql, name, binds) do
|
|
145
|
+
log(sql, name, binds, type_casted_binds) do
|
|
152
146
|
with_raw_connection do |conn|
|
|
153
|
-
result = conn.execute_prepared_update(sql,
|
|
147
|
+
result = conn.execute_prepared_update(sql, type_casted_binds)
|
|
154
148
|
verified!
|
|
155
149
|
result
|
|
156
150
|
end
|
|
@@ -160,14 +154,12 @@ module ActiveRecord
|
|
|
160
154
|
alias :exec_delete :exec_update
|
|
161
155
|
|
|
162
156
|
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil)
|
|
163
|
-
sql =
|
|
157
|
+
sql = preprocess_query(sql)
|
|
164
158
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
mark_transaction_written_if_write(sql)
|
|
159
|
+
type_casted_binds = type_casted_binds(binds)
|
|
168
160
|
|
|
169
161
|
# puts "exec_insert----->sql: #{sql}, binds: #{binds}"
|
|
170
|
-
if
|
|
162
|
+
if binds.nil? || binds.empty?
|
|
171
163
|
log(sql, name) do
|
|
172
164
|
with_raw_connection do |conn|
|
|
173
165
|
result = conditional_indentity_insert(sql) do
|
|
@@ -178,12 +170,14 @@ module ActiveRecord
|
|
|
178
170
|
end
|
|
179
171
|
end
|
|
180
172
|
else
|
|
181
|
-
log(sql, name, binds) do
|
|
173
|
+
log(sql, name, binds, type_casted_binds) do
|
|
182
174
|
with_raw_connection do |conn|
|
|
183
175
|
result = conditional_indentity_insert(sql) do
|
|
184
176
|
# DEPRECATION WARNING: to_time will always preserve the timezone offset of the receiver in Rails 8.0.
|
|
185
177
|
# To opt in to the new behavior, set `ActiveSupport.to_time_preserves_timezone = true`.
|
|
186
178
|
# (called from block in execute_insert_pk
|
|
179
|
+
# TODO: the ideas is to pass type casted binds but causes strange test failures
|
|
180
|
+
# conn.execute_insert_pk(sql, type_casted_binds, pk)
|
|
187
181
|
conn.execute_insert_pk(sql, binds, pk)
|
|
188
182
|
end
|
|
189
183
|
verified!
|
|
@@ -195,15 +189,23 @@ module ActiveRecord
|
|
|
195
189
|
|
|
196
190
|
private
|
|
197
191
|
|
|
198
|
-
def
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
def cast_result(raw_result)
|
|
193
|
+
return ActiveRecord::Result.empty if raw_result.nil?
|
|
194
|
+
|
|
195
|
+
# nothing to cast here. the java part returns an AR Result
|
|
196
|
+
raw_result
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
|
|
201
|
+
result = conditional_indentity_insert(sql) { raw_connection.execute(sql) }
|
|
202
|
+
|
|
203
|
+
count = 0
|
|
204
|
+
count = result.count if result.respond_to?(:count)
|
|
205
|
+
|
|
206
|
+
verified!
|
|
207
|
+
notification_payload[:row_count] = count
|
|
208
|
+
result
|
|
207
209
|
end
|
|
208
210
|
|
|
209
211
|
def sql_for_insert(sql, pk, binds, returning) # :nodoc:
|
|
@@ -50,7 +50,12 @@ module ActiveRecord
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
binds.each do |bind|
|
|
53
|
-
value =
|
|
53
|
+
value = if bind.is_a?(::ActiveModel::Attribute)
|
|
54
|
+
quote(bind.value_for_database)
|
|
55
|
+
else
|
|
56
|
+
quote(bind)
|
|
57
|
+
end
|
|
58
|
+
|
|
54
59
|
sql = sql.sub('?', value)
|
|
55
60
|
end
|
|
56
61
|
|
data/lib/arjdbc/mssql.rb
CHANGED
|
@@ -219,6 +219,10 @@ module ArJdbc
|
|
|
219
219
|
true
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
+
def supports_native_partitioning?
|
|
223
|
+
database_version >= 100_000
|
|
224
|
+
end
|
|
225
|
+
|
|
222
226
|
def supports_insert_returning?
|
|
223
227
|
true
|
|
224
228
|
end
|
|
@@ -953,7 +957,6 @@ module ActiveRecord::ConnectionAdapters
|
|
|
953
957
|
FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
|
|
954
958
|
|
|
955
959
|
def execute_and_clear(sql, name, binds, prepare: false, async: false)
|
|
956
|
-
sql = transform_query(sql)
|
|
957
960
|
check_if_write_query(sql)
|
|
958
961
|
|
|
959
962
|
if !prepare || without_prepared_statement?(binds)
|
|
@@ -15,6 +15,51 @@ module ArJdbc
|
|
|
15
15
|
|
|
16
16
|
"EXPLAIN (#{options.join(", ").upcase})"
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true)
|
|
22
|
+
sql = preprocess_query(sql)
|
|
23
|
+
|
|
24
|
+
# puts "[1]internal----->sql: #{sql}, binds: #{binds}"
|
|
25
|
+
type_casted_binds = type_casted_binds(binds)
|
|
26
|
+
# puts "[2]internal----->sql: #{type_casted_binds.size}, binds: #{type_casted_binds}"
|
|
27
|
+
|
|
28
|
+
with_raw_connection do |conn|
|
|
29
|
+
if without_prepared_statement?(binds)
|
|
30
|
+
log(sql, name, async: async) { conn.execute_query(sql) }
|
|
31
|
+
else
|
|
32
|
+
log(sql, name, type_casted_binds, async: async) do
|
|
33
|
+
# this is different from normal AR that always caches
|
|
34
|
+
cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
|
|
35
|
+
conn.execute_prepared_query(sql, type_casted_binds, cached_statement)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
|
|
42
|
+
result = raw_connection.execute(sql)
|
|
43
|
+
|
|
44
|
+
count = 0
|
|
45
|
+
count = result.count if result.respond_to?(:count)
|
|
46
|
+
|
|
47
|
+
verified!
|
|
48
|
+
notification_payload[:row_count] = count
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cast_result(raw_result)
|
|
53
|
+
return ActiveRecord::Result.empty if raw_result.nil?
|
|
54
|
+
|
|
55
|
+
fields = raw_result.fields
|
|
56
|
+
|
|
57
|
+
if fields.empty?
|
|
58
|
+
ActiveRecord::Result.empty
|
|
59
|
+
else
|
|
60
|
+
ActiveRecord::Result.new(fields, raw_result.values)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
18
63
|
end
|
|
19
64
|
end
|
|
20
65
|
end
|
|
@@ -6,6 +6,23 @@ module ArJdbc
|
|
|
6
6
|
ForeignKeyDefinition = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
|
|
7
7
|
Utils = ActiveRecord::ConnectionAdapters::PostgreSQL::Utils
|
|
8
8
|
|
|
9
|
+
def decode_string_array(value)
|
|
10
|
+
return value if value.is_a?(Array)
|
|
11
|
+
_arjdbc_array_parser.parse_pg_array(value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def _arjdbc_array_parser
|
|
17
|
+
@_arjdbc_array_parser ||= begin
|
|
18
|
+
obj = Object.new
|
|
19
|
+
obj.extend(ActiveRecord::ConnectionAdapters::PostgreSQL::ArrayParser)
|
|
20
|
+
obj
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
public
|
|
25
|
+
|
|
9
26
|
def foreign_keys(table_name)
|
|
10
27
|
scope = quoted_scope(table_name)
|
|
11
28
|
fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
|
|
@@ -22,6 +22,8 @@ require "arjdbc/sqlite3/pragmas"
|
|
|
22
22
|
|
|
23
23
|
require "arjdbc/abstract/relation_query_attribute_monkey_patch"
|
|
24
24
|
|
|
25
|
+
require "arjdbc/sqlite3/database_statements"
|
|
26
|
+
|
|
25
27
|
module SQLite3
|
|
26
28
|
module Constants
|
|
27
29
|
module Open
|
|
@@ -223,17 +225,29 @@ module ArJdbc
|
|
|
223
225
|
|
|
224
226
|
# REFERENTIAL INTEGRITY ====================================
|
|
225
227
|
|
|
228
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
229
|
+
if statements.is_a?(Array)
|
|
230
|
+
# SQLite JDBC doesn't support multiple statements in one execute
|
|
231
|
+
# Execute each statement separately
|
|
232
|
+
statements.each do |sql|
|
|
233
|
+
raw_execute(sql, name, **kwargs.merge(batch: true))
|
|
234
|
+
end
|
|
235
|
+
else
|
|
236
|
+
raw_execute(statements, name, batch: true, **kwargs)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
226
240
|
def disable_referential_integrity # :nodoc:
|
|
227
241
|
old_foreign_keys = query_value("PRAGMA foreign_keys")
|
|
228
242
|
old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
|
|
229
243
|
|
|
230
244
|
begin
|
|
231
|
-
execute("PRAGMA defer_foreign_keys = ON")
|
|
232
|
-
execute("PRAGMA foreign_keys = OFF")
|
|
245
|
+
execute("PRAGMA defer_foreign_keys = ON", "SCHEMA")
|
|
246
|
+
execute("PRAGMA foreign_keys = OFF", "SCHEMA")
|
|
233
247
|
yield
|
|
234
248
|
ensure
|
|
235
|
-
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
|
|
236
|
-
execute("PRAGMA foreign_keys = #{old_foreign_keys}")
|
|
249
|
+
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}", "SCHEMA")
|
|
250
|
+
execute("PRAGMA foreign_keys = #{old_foreign_keys}", "SCHEMA")
|
|
237
251
|
end
|
|
238
252
|
end
|
|
239
253
|
|
|
@@ -388,6 +402,24 @@ module ArJdbc
|
|
|
388
402
|
end
|
|
389
403
|
end
|
|
390
404
|
|
|
405
|
+
# Returns a list of defined virtual tables (for Rails 8 compatibility)
|
|
406
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)(?:\s*\((.*)\))?/im
|
|
407
|
+
def virtual_tables
|
|
408
|
+
query = <<~SQL
|
|
409
|
+
SELECT name, sql FROM sqlite_master
|
|
410
|
+
WHERE type = 'table' AND sql LIKE 'CREATE VIRTUAL TABLE%';
|
|
411
|
+
SQL
|
|
412
|
+
|
|
413
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |(name, sql), memo|
|
|
414
|
+
match = sql.match(VIRTUAL_TABLE_REGEX)
|
|
415
|
+
next unless match
|
|
416
|
+
|
|
417
|
+
module_name = match[1]
|
|
418
|
+
arguments = match[2] || ""
|
|
419
|
+
memo[name] = [module_name, arguments.strip]
|
|
420
|
+
end.to_a
|
|
421
|
+
end
|
|
422
|
+
|
|
391
423
|
def build_insert_sql(insert) # :nodoc:
|
|
392
424
|
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
|
393
425
|
|
|
@@ -791,7 +823,17 @@ module ArJdbc
|
|
|
791
823
|
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
|
792
824
|
if ::SQLite3::Pragmas.respond_to?(pragma)
|
|
793
825
|
stmt = ::SQLite3::Pragmas.public_send(pragma, value)
|
|
794
|
-
|
|
826
|
+
# Skip pragma execution if we're inside a transaction and it's not allowed
|
|
827
|
+
begin
|
|
828
|
+
raw_connection.execute(stmt)
|
|
829
|
+
rescue => e
|
|
830
|
+
if e.message.include?("Safety level may not be changed inside a transaction")
|
|
831
|
+
# Log warning and skip this pragma
|
|
832
|
+
warn "Cannot set pragma '#{pragma}' inside a transaction, skipping"
|
|
833
|
+
else
|
|
834
|
+
raise
|
|
835
|
+
end
|
|
836
|
+
end
|
|
795
837
|
else
|
|
796
838
|
warn "Unknown SQLite pragma: #{pragma}"
|
|
797
839
|
end
|
|
@@ -845,8 +887,9 @@ module ActiveRecord::ConnectionAdapters
|
|
|
845
887
|
include ArJdbc::Abstract::ConnectionManagement
|
|
846
888
|
include ArJdbc::Abstract::DatabaseStatements
|
|
847
889
|
include ArJdbc::Abstract::StatementCache
|
|
848
|
-
include
|
|
890
|
+
# Don't include TransactionSupport - use Rails' SQLite3::DatabaseStatements instead
|
|
849
891
|
|
|
892
|
+
include ::ArJdbc::SQLite3::DatabaseStatements
|
|
850
893
|
|
|
851
894
|
##
|
|
852
895
|
# :singleton-method:
|
|
@@ -954,7 +997,8 @@ module ActiveRecord::ConnectionAdapters
|
|
|
954
997
|
|
|
955
998
|
# because the JDBC driver doesn't like multiple SQL statements in one JDBC statement
|
|
956
999
|
def combine_multi_statements(total_sql)
|
|
957
|
-
|
|
1000
|
+
# For Rails 8 compatibility - join multiple statements with semicolon
|
|
1001
|
+
total_sql.is_a?(Array) ? total_sql.join(";\n") : total_sql
|
|
958
1002
|
end
|
|
959
1003
|
|
|
960
1004
|
def type_map
|