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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +23 -16
  3. data/.github/workflows/ruby.yml +123 -18
  4. data/.mise.toml +3 -0
  5. data/Gemfile +43 -48
  6. data/README.md +3 -2
  7. data/RUNNING_TESTS.md +9 -1
  8. data/Rakefile +63 -15
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  11. data/lib/arjdbc/abstract/core.rb +0 -17
  12. data/lib/arjdbc/abstract/database_statements.rb +21 -18
  13. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  14. data/lib/arjdbc/mssql/adapter.rb +11 -11
  15. data/lib/arjdbc/mssql/database_statements.rb +30 -28
  16. data/lib/arjdbc/mssql/explain_support.rb +6 -1
  17. data/lib/arjdbc/mssql.rb +1 -1
  18. data/lib/arjdbc/postgresql/adapter.rb +4 -1
  19. data/lib/arjdbc/postgresql/database_statements.rb +45 -0
  20. data/lib/arjdbc/postgresql/schema_statements.rb +17 -0
  21. data/lib/arjdbc/sqlite3/adapter.rb +51 -7
  22. data/lib/arjdbc/sqlite3/database_statements.rb +26 -0
  23. data/lib/arjdbc/version.rb +1 -1
  24. data/pom.xml +3 -3
  25. data/rakelib/02-test.rake +1 -1
  26. data/rakelib/db.rake +5 -3
  27. data/rakelib/rails.rake +0 -1
  28. data/src/java/arjdbc/ArJdbcModule.java +13 -10
  29. data/src/java/arjdbc/db2/DB2Module.java +4 -4
  30. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +8 -9
  31. data/src/java/arjdbc/h2/H2Module.java +2 -1
  32. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +8 -7
  33. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +5 -6
  34. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +4 -3
  35. data/src/java/arjdbc/jdbc/DriverWrapper.java +13 -8
  36. data/src/java/arjdbc/jdbc/JdbcResult.java +10 -7
  37. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +168 -154
  38. data/src/java/arjdbc/mssql/MSSQLModule.java +3 -3
  39. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +13 -9
  40. data/src/java/arjdbc/mysql/MySQLModule.java +3 -3
  41. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +25 -23
  42. data/src/java/arjdbc/oracle/OracleModule.java +2 -3
  43. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +22 -18
  44. data/src/java/arjdbc/postgresql/PostgreSQLModule.java +2 -3
  45. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +24 -12
  46. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +16 -12
  47. data/src/java/arjdbc/sqlite3/SQLite3Module.java +2 -3
  48. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +78 -29
  49. data/src/java/arjdbc/util/DateTimeUtils.java +7 -5
  50. data/src/java/arjdbc/util/ObjectSupport.java +2 -2
  51. data/src/java/arjdbc/util/StringHelper.java +1 -1
  52. metadata +7 -6
  53. 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
- ADAPTERS = %w[mysql postgresql sqlite3].map { |a| "activerecord-jdbc#{a}-adapter" }
111
- DRIVERS = %w[mysql postgres sqlite3].map { |a| "jdbc-#{a}" }
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 = '1.8'; debug = true
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.exists?( matched_jar )
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
- [ 'java.class.path', 'sun.boot.class.path' ].map { |key| ENV_JAVA[key] }.each do |v|
266
- classpath += v.split(File::PATH_SEPARATOR).find_all { |jar| jar =~ /jruby/i } if v
267
- end
268
- # Using Java 9+. Let's try and infer jruby.jar location from rbconfig
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", "~> 7.2.2"
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", "~> 7.2.2"
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'
@@ -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, type_casted_binds, cached_statement)
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) { conn.execute_prepared_update(sql, 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 raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
100
- log(sql, name, async: async) do
101
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
102
- result = conn.execute(sql)
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
@@ -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(exception)
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 = transform_query(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 without_prepared_statement?(binds)
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 = transform_query(sql)
131
+ sql = preprocess_query(sql)
136
132
 
137
- check_if_write_query(sql)
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 without_prepared_statement?(binds)
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, binds)
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 = transform_query(sql)
157
+ sql = preprocess_query(sql)
164
158
 
165
- check_if_write_query(sql)
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 without_prepared_statement?(binds)
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 raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
199
- # puts "raw_execute----->sql: #{sql}"
200
- log(sql, name, async: async) do
201
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
202
- result = conditional_indentity_insert(sql) { conn.execute(sql) }
203
- verified!
204
- result
205
- end
206
- end
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 = quote(bind.value_for_database)
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
@@ -6,4 +6,4 @@ module ArJdbc
6
6
  MsSQL = MSSQL # compatibility with 1.2
7
7
  end
8
8
 
9
- ArJdbc.warn_unsupported_adapter "mssql", [7, 2] # warns on AR >= 4.2
9
+ ArJdbc.warn_unsupported_adapter "mssql", [8, 0] # warns on AR >= 4.2
@@ -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
- raw_execute(stmt, "SCHEMA")
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 ArJdbc::Abstract::TransactionSupport
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
- total_sql
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