activerecord-jdbc-alt-adapter 61.0.0-java → 70.0.0.rc1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +273 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +8 -6
  5. data/README.md +2 -1
  6. data/Rakefile +1 -1
  7. data/activerecord-jdbc-adapter.gemspec +2 -2
  8. data/activerecord-jdbc-alt-adapter.gemspec +2 -2
  9. data/lib/arel/visitors/compat.rb +5 -33
  10. data/lib/arel/visitors/h2.rb +1 -13
  11. data/lib/arel/visitors/hsqldb.rb +1 -21
  12. data/lib/arel/visitors/sql_server.rb +2 -103
  13. data/lib/arjdbc/abstract/core.rb +8 -9
  14. data/lib/arjdbc/abstract/database_statements.rb +4 -4
  15. data/lib/arjdbc/discover.rb +0 -67
  16. data/lib/arjdbc/hsqldb/adapter.rb +2 -2
  17. data/lib/arjdbc/jdbc/adapter.rb +3 -3
  18. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  19. data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
  20. data/lib/arjdbc/jdbc/column.rb +1 -26
  21. data/lib/arjdbc/jdbc/type_cast.rb +2 -2
  22. data/lib/arjdbc/jdbc.rb +0 -7
  23. data/lib/arjdbc/mssql/adapter.rb +134 -105
  24. data/lib/arjdbc/mssql/quoting.rb +26 -27
  25. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  26. data/lib/arjdbc/mssql/schema_definitions.rb +32 -17
  27. data/lib/arjdbc/mssql/schema_dumper.rb +13 -1
  28. data/lib/arjdbc/mssql/schema_statements.rb +61 -36
  29. data/lib/arjdbc/mssql/transaction.rb +2 -2
  30. data/lib/arjdbc/mssql/types/date_and_time_types.rb +6 -6
  31. data/lib/arjdbc/mssql/types/numeric_types.rb +2 -2
  32. data/lib/arjdbc/mssql.rb +1 -1
  33. data/lib/arjdbc/mysql/adapter.rb +2 -1
  34. data/lib/arjdbc/oracle/adapter.rb +4 -23
  35. data/lib/arjdbc/postgresql/adapter.rb +64 -1
  36. data/lib/arjdbc/postgresql/oid_types.rb +68 -47
  37. data/lib/arjdbc/sqlite3/adapter.rb +132 -88
  38. data/lib/arjdbc/tasks/database_tasks.rb +0 -12
  39. data/lib/arjdbc/util/serialized_attributes.rb +0 -22
  40. data/lib/arjdbc/util/table_copier.rb +2 -1
  41. data/lib/arjdbc/version.rb +1 -1
  42. data/rakelib/02-test.rake +3 -18
  43. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +17 -2
  44. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +14 -1
  45. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +33 -0
  46. metadata +8 -40
  47. data/lib/active_record/connection_adapters/as400_adapter.rb +0 -2
  48. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -1
  49. data/lib/active_record/connection_adapters/derby_adapter.rb +0 -1
  50. data/lib/active_record/connection_adapters/informix_adapter.rb +0 -1
  51. data/lib/arel/visitors/db2.rb +0 -137
  52. data/lib/arel/visitors/derby.rb +0 -112
  53. data/lib/arel/visitors/firebird.rb +0 -79
  54. data/lib/arjdbc/db2/adapter.rb +0 -808
  55. data/lib/arjdbc/db2/as400.rb +0 -142
  56. data/lib/arjdbc/db2/column.rb +0 -131
  57. data/lib/arjdbc/db2/connection_methods.rb +0 -48
  58. data/lib/arjdbc/db2.rb +0 -4
  59. data/lib/arjdbc/derby/active_record_patch.rb +0 -13
  60. data/lib/arjdbc/derby/adapter.rb +0 -521
  61. data/lib/arjdbc/derby/connection_methods.rb +0 -20
  62. data/lib/arjdbc/derby/schema_creation.rb +0 -15
  63. data/lib/arjdbc/derby.rb +0 -3
  64. data/lib/arjdbc/firebird/adapter.rb +0 -413
  65. data/lib/arjdbc/firebird/connection_methods.rb +0 -23
  66. data/lib/arjdbc/firebird.rb +0 -4
  67. data/lib/arjdbc/informix/adapter.rb +0 -139
  68. data/lib/arjdbc/informix/connection_methods.rb +0 -9
  69. data/lib/arjdbc/sybase/adapter.rb +0 -47
  70. data/lib/arjdbc/sybase.rb +0 -2
  71. data/lib/arjdbc/tasks/db2_database_tasks.rb +0 -104
  72. data/lib/arjdbc/tasks/derby_database_tasks.rb +0 -95
  73. data/src/java/arjdbc/derby/DerbyModule.java +0 -178
  74. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +0 -152
  75. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +0 -174
  76. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +0 -75
@@ -10,6 +10,7 @@ require "active_record/connection_adapters/abstract_adapter"
10
10
  require "active_record/connection_adapters/statement_pool"
11
11
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
12
12
  require "active_record/connection_adapters/sqlite3/quoting"
13
+ require "active_record/connection_adapters/sqlite3/database_statements"
13
14
  require "active_record/connection_adapters/sqlite3/schema_creation"
14
15
  require "active_record/connection_adapters/sqlite3/schema_definitions"
15
16
  require "active_record/connection_adapters/sqlite3/schema_dumper"
@@ -64,6 +65,7 @@ module ArJdbc
64
65
  # DIFFERENCE: FQN
65
66
  include ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
66
67
  include ::ActiveRecord::ConnectionAdapters::SQLite3::SchemaStatements
68
+ include ::ActiveRecord::ConnectionAdapters::SQLite3::DatabaseStatements
67
69
 
68
70
  NATIVE_DATABASE_TYPES = {
69
71
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -79,15 +81,30 @@ module ArJdbc
79
81
  boolean: { name: "boolean" },
80
82
  json: { name: "json" },
81
83
  }
82
-
83
- # DIFFERENCE: class_attribute in original adapter is moved down to our section which is a class
84
- # since we cannot define it here in the module (original source this is a class).
84
+
85
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
86
+ private
87
+ def dealloc(stmt)
88
+ stmt.close unless stmt.closed?
89
+ end
90
+ end
85
91
 
86
92
  def initialize(connection, logger, connection_options, config)
93
+ @memory_database = config[:database] == ":memory:"
87
94
  super(connection, logger, config)
88
95
  configure_connection
89
96
  end
90
97
 
98
+ def self.database_exists?(config)
99
+ config = config.symbolize_keys
100
+ if config[:database] == ":memory:"
101
+ true
102
+ else
103
+ database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
104
+ File.exist?(database_file)
105
+ end
106
+ end
107
+
91
108
  def supports_ddl_transactions?
92
109
  true
93
110
  end
@@ -101,7 +118,7 @@ module ArJdbc
101
118
  end
102
119
 
103
120
  def supports_partial_index?
104
- database_version >= "3.9.0"
121
+ true
105
122
  end
106
123
 
107
124
  def supports_expression_index?
@@ -144,6 +161,25 @@ module ArJdbc
144
161
  alias supports_insert_conflict_target? supports_insert_on_conflict?
145
162
 
146
163
  # DIFFERENCE: active?, reconnect!, disconnect! handles by arjdbc core
164
+ def supports_concurrent_connections?
165
+ !@memory_database
166
+ end
167
+
168
+ def active?
169
+ !@raw_connection.closed?
170
+ end
171
+
172
+ def reconnect!
173
+ super
174
+ connect if @connection.closed?
175
+ end
176
+
177
+ # Disconnects from the database if already connected. Otherwise, this
178
+ # method does nothing.
179
+ def disconnect!
180
+ super
181
+ @connection.close rescue nil
182
+ end
147
183
 
148
184
  def supports_index_sort_order?
149
185
  true
@@ -182,48 +218,8 @@ module ArJdbc
182
218
  end
183
219
  end
184
220
 
185
- #--
186
- # DATABASE STATEMENTS ======================================
187
- #++
188
-
189
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
190
- :pragma
191
- ) # :nodoc:
192
- private_constant :READ_QUERY
193
-
194
- def write_query?(sql) # :nodoc:
195
- !READ_QUERY.match?(sql)
196
- end
197
-
198
- def explain(arel, binds = [])
199
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
200
- # DIFFERENCE: FQN
201
- ::ActiveRecord::ConnectionAdapters::SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
202
- end
203
-
204
- # DIFFERENCE: implemented in ArJdbc::Abstract::DatabaseStatements
205
- #def exec_query(sql, name = nil, binds = [], prepare: false)
206
-
207
- # DIFFERENCE: implemented in ArJdbc::Abstract::DatabaseStatements
208
- #def exec_delete(sql, name = "SQL", binds = [])
209
-
210
- def last_inserted_id(result)
211
- @connection.last_insert_row_id
212
- end
213
-
214
- # DIFFERENCE: implemented in ArJdbc::Abstract::DatabaseStatements
215
- #def execute(sql, name = nil) #:nodoc:
216
-
217
- def begin_db_transaction #:nodoc:
218
- log("begin transaction", 'TRANSACTION') { @connection.transaction }
219
- end
220
-
221
- def commit_db_transaction #:nodoc:
222
- log("commit transaction", 'TRANSACTION') { @connection.commit }
223
- end
224
-
225
- def exec_rollback_db_transaction #:nodoc:
226
- log("rollback transaction", 'TRANSACTION') { @connection.rollback }
221
+ def all_foreign_keys_valid? # :nodoc:
222
+ execute("PRAGMA foreign_key_check").blank?
227
223
  end
228
224
 
229
225
  # SCHEMA STATEMENTS ========================================
@@ -233,14 +229,15 @@ module ArJdbc
233
229
  pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
234
230
  end
235
231
 
236
- def remove_index(table_name, column_name = nil, **options) # :nodoc:
237
- return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
232
+ def remove_index(table_name, column_name = nil, **options) # :nodoc:
233
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
238
234
 
239
235
  index_name = index_name_for_remove(table_name, column_name, options)
240
236
 
241
237
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
242
238
  end
243
239
 
240
+
244
241
  # Renames a table.
245
242
  #
246
243
  # Example:
@@ -265,9 +262,17 @@ module ArJdbc
265
262
  def remove_column(table_name, column_name, type = nil, **options) #:nodoc:
266
263
  alter_table(table_name) do |definition|
267
264
  definition.remove_column column_name
268
- definition.foreign_keys.delete_if do |_, fk_options|
269
- fk_options[:column] == column_name.to_s
265
+ definition.foreign_keys.delete_if { |fk| fk.column == column_name.to_s }
266
+ end
267
+ end
268
+
269
+ def remove_columns(table_name, *column_names, type: nil, **options) # :nodoc:
270
+ alter_table(table_name) do |definition|
271
+ column_names.each do |column_name|
272
+ definition.remove_column column_name
270
273
  end
274
+ column_names = column_names.map(&:to_s)
275
+ definition.foreign_keys.delete_if { |fk| column_names.include?(fk.column) }
271
276
  end
272
277
  end
273
278
 
@@ -291,8 +296,8 @@ module ArJdbc
291
296
  def change_column(table_name, column_name, type, **options) #:nodoc:
292
297
  alter_table(table_name) do |definition|
293
298
  definition[column_name].instance_eval do
294
- self.type = type
295
- self.options.merge!(options)
299
+ self.type = aliased_types(type.to_s, type)
300
+ self.options.merge!(options)
296
301
  end
297
302
  end
298
303
  end
@@ -322,18 +327,6 @@ module ArJdbc
322
327
  end
323
328
  end
324
329
 
325
- def insert_fixtures_set(fixture_set, tables_to_delete = [])
326
- disable_referential_integrity do
327
- transaction(requires_new: true) do
328
- tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
329
-
330
- fixture_set.each do |table_name, rows|
331
- rows.each { |row| insert_fixture(row, table_name) }
332
- end
333
- end
334
- end
335
- end
336
-
337
330
  def build_insert_sql(insert) # :nodoc:
338
331
  sql = +"INSERT #{insert.into} #{insert.values_list}"
339
332
 
@@ -341,23 +334,23 @@ module ArJdbc
341
334
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
342
335
  elsif insert.update_duplicates?
343
336
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
344
- sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
345
- sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
337
+ if insert.raw_update_sql?
338
+ sql << insert.raw_update_sql
339
+ else
340
+ sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
341
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
342
+ end
346
343
  end
347
344
 
348
345
  sql
349
346
  end
350
347
 
351
- def shared_cache?
352
- config[:properties] && config[:properties][:shared_cache] == true
348
+ def shared_cache? # :nodoc:
349
+ @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
353
350
  end
354
351
 
355
352
  def get_database_version # :nodoc:
356
- SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
357
- end
358
-
359
- def build_truncate_statement(table_name)
360
- "DELETE FROM #{quote_table_name(table_name)}"
353
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
361
354
  end
362
355
 
363
356
  def check_version
@@ -380,6 +373,34 @@ module ArJdbc
380
373
  end
381
374
  alias column_definitions table_structure
382
375
 
376
+ def extract_value_from_default(default)
377
+ case default
378
+ when /^null$/i
379
+ nil
380
+ # Quoted types
381
+ when /^'(.*)'$/m
382
+ $1.gsub("''", "'")
383
+ # Quoted types
384
+ when /^"(.*)"$/m
385
+ $1.gsub('""', '"')
386
+ # Numeric types
387
+ when /\A-?\d+(\.\d*)?\z/
388
+ $&
389
+ else
390
+ # Anything else is blank or some function
391
+ # and we can't know the value of that, so return nil.
392
+ nil
393
+ end
394
+ end
395
+
396
+ def extract_default_function(default_value, default)
397
+ default if has_default_function?(default_value, default)
398
+ end
399
+
400
+ def has_default_function?(default_value, default)
401
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
402
+ end
403
+
383
404
  # See: https://www.sqlite.org/lang_altertable.html
384
405
  # SQLite has an additional restriction on the ALTER TABLE statement
385
406
  def invalid_alter_table_type?(type, options)
@@ -440,8 +461,13 @@ module ArJdbc
440
461
  options[:rename][column.name.to_sym] ||
441
462
  column.name) : column.name
442
463
 
464
+ if column.has_default?
465
+ type = lookup_cast_type_from_column(column)
466
+ default = type.deserialize(column.default)
467
+ end
468
+
443
469
  @definition.column(column_name, column.type,
444
- limit: column.limit, default: column.default,
470
+ limit: column.limit, default: default,
445
471
  precision: column.precision, scale: column.scale,
446
472
  null: column.null, collation: column.collation,
447
473
  primary_key: column_name == from_primary_key
@@ -459,9 +485,6 @@ module ArJdbc
459
485
  def copy_table_indexes(from, to, rename = {})
460
486
  indexes(from).each do |index|
461
487
  name = index.name
462
- # indexes sqlite creates for internal use start with `sqlite_` and
463
- # don't need to be copied
464
- next if name.start_with?("sqlite_")
465
488
  if to == "a#{from}"
466
489
  name = "t#{name}"
467
490
  elsif from == "a#{to}"
@@ -481,6 +504,7 @@ module ArJdbc
481
504
  options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
482
505
  options[:unique] = true if index.unique
483
506
  options[:where] = index.where if index.where
507
+ options[:order] = index.orders if index.orders
484
508
  add_index(to, columns, **options)
485
509
  end
486
510
  end
@@ -500,20 +524,22 @@ module ArJdbc
500
524
  end
501
525
 
502
526
  def translate_exception(exception, message:, sql:, binds:)
503
- case exception.message
504
527
  # SQLite 3.8.2 returns a newly formatted error message:
505
528
  # UNIQUE constraint failed: *table_name*.*column_name*
506
529
  # Older versions of SQLite return:
507
530
  # column *column_name* is not unique
508
- when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
531
+ if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
509
532
  # DIFFERENCE: FQN
510
533
  ::ActiveRecord::RecordNotUnique.new(message, sql: sql, binds: binds)
511
- when /.* may not be NULL/, /NOT NULL constraint failed: .*/
534
+ elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
512
535
  # DIFFERENCE: FQN
513
536
  ::ActiveRecord::NotNullViolation.new(message, sql: sql, binds: binds)
514
- when /FOREIGN KEY constraint failed/i
537
+ elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
515
538
  # DIFFERENCE: FQN
516
539
  ::ActiveRecord::InvalidForeignKey.new(message, sql: sql, binds: binds)
540
+ elsif exception.message.match?(/called on a closed database/i)
541
+ # DIFFERENCE: FQN
542
+ ::ActiveRecord::ConnectionNotEstablished.new(exception)
517
543
  else
518
544
  super
519
545
  end
@@ -533,12 +559,12 @@ module ArJdbc
533
559
  # Result will have following sample string
534
560
  # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
535
561
  # "password_digest" varchar COLLATE "NOCASE");
536
- result = exec_query(sql, "SCHEMA").first
562
+ result = query_value(sql, "SCHEMA")
537
563
 
538
564
  if result
539
565
  # Splitting with left parentheses and discarding the first part will return all
540
566
  # columns separated with comma(,).
541
- columns_string = result["sql"].split("(", 2).last
567
+ columns_string = result.split("(", 2).last
542
568
 
543
569
  columns_string.split(",").each do |column_string|
544
570
  # This regex will match the column name and collation type and will save
@@ -564,7 +590,21 @@ module ArJdbc
564
590
  Arel::Visitors::SQLite.new(self)
565
591
  end
566
592
 
593
+ def build_statement_pool
594
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
595
+ end
596
+
597
+ def connect
598
+ @connection = ::SQLite3::Database.new(
599
+ @config[:database].to_s,
600
+ @config.merge(results_as_hash: true)
601
+ )
602
+ end
603
+
567
604
  def configure_connection
605
+ # FIXME: missing from adapter
606
+ # @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
607
+
568
608
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
569
609
  end
570
610
 
@@ -720,11 +760,15 @@ module ActiveRecord::ConnectionAdapters
720
760
 
721
761
  # because the JDBC driver doesn't like multiple SQL statements in one JDBC statement
722
762
  def combine_multi_statements(total_sql)
723
- if total_sql.length == 1
724
- total_sql.first
725
- else
726
- total_sql
727
- end
763
+ total_sql
764
+ end
765
+
766
+ # combine
767
+ def write_query?(sql) # :nodoc:
768
+ return sql.any? { |stmt| super(stmt) } if sql.kind_of? Array
769
+ !READ_QUERY.match?(sql)
770
+ rescue ArgumentError # Invalid encoding
771
+ !READ_QUERY.match?(sql.b)
728
772
  end
729
773
 
730
774
  def initialize_type_map(m = type_map)
@@ -11,18 +11,6 @@ module ArJdbc
11
11
  require 'arjdbc/tasks/jdbc_database_tasks'
12
12
  require 'arjdbc/tasks/sqlite_database_tasks_patch'
13
13
  require 'arjdbc/tasks/mssql_database_tasks'
14
- #require 'arjdbc/tasks/db2_database_tasks'
15
- #require 'arjdbc/tasks/derby_database_tasks'
16
- #require 'arjdbc/tasks/h2_database_tasks'
17
- #require 'arjdbc/tasks/hsqldb_database_tasks'
18
-
19
- # re-invent built-in (but deprecated on 4.0) tasks :
20
- # tasks for custom (JDBC) adapters :
21
- #register_tasks(/db2/, DB2DatabaseTasks)
22
- #register_tasks(/derby/, DerbyDatabaseTasks)
23
- #register_tasks(/h2/, H2DatabaseTasks)
24
- #register_tasks(/hsqldb/, HSQLDBDatabaseTasks)
25
- # (default) generic JDBC task :
26
14
  register_tasks(/^jdbc$/, JdbcDatabaseTasks)
27
15
  register_tasks(/sqlserver/, MSSQLDatabaseTasks)
28
16
 
@@ -31,33 +31,11 @@ module ArJdbc
31
31
  SerializedAttributes.dump_column_value(self, column)
32
32
  end
33
33
 
34
- if defined? ActiveRecord::Type::Serialized # ArJdbc::AR42
35
-
36
34
  def self.dump_column_value(record, column)
37
35
  value = record[ column.name.to_s ]
38
36
  column.cast_type.type_cast_for_database(value)
39
37
  end
40
38
 
41
- else
42
-
43
- def self.dump_column_value(record, column)
44
- value = record[ name = column.name.to_s ]
45
- if record.class.respond_to?(:serialized_attributes)
46
- if coder = record.class.serialized_attributes[name]
47
- value = coder.respond_to?(:dump) ? coder.dump(value) : value.to_yaml
48
- end
49
- else
50
- if record.respond_to?(:unserializable_attribute?)
51
- value = value.to_yaml if record.unserializable_attribute?(name, column)
52
- else
53
- value = value.to_yaml if value.is_a?(Hash)
54
- end
55
- end
56
- value
57
- end
58
-
59
- end
60
-
61
39
  def self.setup(lob_type = nil, after_save_alias = nil)
62
40
  ActiveRecord::Base.send :include, self # include SerializedAttributes
63
41
  ActiveRecord::Base.lob_type = lob_type unless lob_type.nil?
@@ -4,7 +4,8 @@ module ArJdbc
4
4
  module Util
5
5
  module TableCopier
6
6
 
7
- # taken from SQLite adapter, code loosely based on http://git.io/P7tFQA
7
+ # taken from SQLite adapter, code loosely based on
8
+ # https://github.com/rails/rails/blob/d3e5118/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
8
9
 
9
10
  # Performs changes for table by first copying (and preserving contents)
10
11
  # into another (temporary) table, than alters and copies all data back.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArJdbc
4
- VERSION = '61.0.0'
4
+ VERSION = '70.0.0.rc1'
5
5
  end
data/rakelib/02-test.rake CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  test_tasks = [ 'test_mysql', 'test_sqlite3', 'test_postgresql_with_hint' ]
3
3
  if defined?(JRUBY_VERSION)
4
- test_tasks.push :test_derby, :test_hsqldb, :test_h2
4
+ test_tasks.push :test_hsqldb, :test_h2
5
5
  test_tasks.push :test_jndi, :test_jdbc
6
6
  end
7
7
 
@@ -55,7 +55,6 @@ def test_task_for(adapter, options = {})
55
55
  test_task
56
56
  end
57
57
 
58
- test_task_for :Derby, :desc => 'Run tests against (embedded) DerbyDB'
59
58
  test_task_for :H2, :desc => 'Run tests against H2 database engine'
60
59
  test_task_for :HSQLDB, :desc => 'Run tests against HyperSQL (Java) database'
61
60
  test_task_for :MSSQL, :driver => :jtds, :database_name => 'MS-SQL (SQLServer)'
@@ -65,29 +64,22 @@ test_task_for :PostgreSQL, :driver => ENV['JDBC_POSTGRES_VERSION'] || 'postgres'
65
64
  task :test_postgres => :test_postgresql # alias
66
65
  test_task_for :SQLite3, :driver => ENV['JDBC_SQLITE_VERSION']
67
66
  task :test_sqlite => :test_sqlite3 # alias
68
- test_task_for :Firebird
69
67
 
70
68
  test_task_for :MariaDB, :files => FileList["test/db/mysql/*_test.rb"] do |test_task| #, :prereqs => 'db:mysql'
71
69
  test_task.ruby_opts << '-rdb/mariadb_config'
72
70
  end
73
71
 
74
72
  # ensure driver for these DBs is on your class-path
75
- [ :Oracle, :DB2, :Informix, :CacheDB ].each do |adapter|
73
+ [ :Oracle ].each do |adapter|
76
74
  test_task_for adapter, :desc => "Run tests against #{adapter} (ensure driver is on class-path)"
77
75
  end
78
76
 
79
- test_task_for :AS400, :desc => "Run tests against AS400 (DB2) (ensure driver is on class-path)",
80
- :files => FileList["test/db2*_test.rb"] + FileList["test/db/db2/*_test.rb"]
81
-
82
- #task :test_jdbc => [ :test_jdbc_mysql, :test_jdbc_derby ] if defined?(JRUBY_VERSION)
83
-
84
77
  test_task_for 'JDBC', :desc => 'Run tests against plain JDBC adapter (uses MySQL and Derby)',
85
78
  :prereqs => [ 'db:mysql' ], :files => FileList['test/*jdbc_*test.rb'] do |test_task|
86
- test_task.libs << 'jdbc-mysql/lib' << 'jdbc-derby/lib'
79
+ test_task.libs << 'jdbc-mysql/lib'
87
80
  end
88
81
 
89
82
  # TODO since Derby AR 5.x support is not implemented we only run JNDI with MySQL :
90
- #task :test_jndi => [ :test_jndi_mysql, :test_jndi_derby ] if defined?(JRUBY_VERSION)
91
83
  task :test_jndi => [ :test_jndi_mysql ] if defined?(JRUBY_VERSION)
92
84
 
93
85
  jndi_classpath = [ 'test/jars/tomcat-juli.jar', 'test/jars/tomcat-catalina.jar' ]
@@ -101,13 +93,6 @@ get_jndi_classpath_opt = lambda do
101
93
  "-J-cp \"#{cp.join(File::PATH_SEPARATOR)}\""
102
94
  end
103
95
 
104
- test_task_for 'JNDI_Derby', :desc => 'Run tests against a Derby JNDI connection',
105
- :prereqs => jndi_classpath, :files => FileList['test/*jndi_derby*test.rb'] do |test_task|
106
- test_task.libs << 'jdbc-derby/lib'
107
- test_task.ruby_opts << get_jndi_classpath_opt.call
108
- #test_task.verbose = true
109
- end
110
-
111
96
  test_task_for 'JNDI_MySQL', :desc => 'Run tests against a MySQL JNDI connection',
112
97
  :prereqs => [ 'db:mysql' ] + jndi_classpath, :files => FileList['test/*jndi_mysql*test.rb'] do |test_task|
113
98
  test_task.libs << 'jdbc-mysql/lib'
@@ -720,6 +720,21 @@ public class RubyJdbcConnection extends RubyObject {
720
720
  }
721
721
  }
722
722
 
723
+ @JRubyMethod(name = "closed?")
724
+ public IRubyObject closed_p(ThreadContext context) {
725
+ try {
726
+ final Connection connection = getConnectionInternal(false);
727
+
728
+ if (connection == null) return context.fals;
729
+
730
+ // NOTE: isClosed method generally cannot be called to determine
731
+ // whether a connection to a database is valid or invalid ...
732
+ return context.runtime.newBoolean(connection.isClosed());
733
+ } catch (SQLException e) {
734
+ return handleException(context, e);
735
+ }
736
+ }
737
+
723
738
  @JRubyMethod(name = "close")
724
739
  public IRubyObject close(final ThreadContext context) {
725
740
  final Connection connection = getConnection(false);
@@ -2642,8 +2657,8 @@ public class RubyJdbcConnection extends RubyObject {
2642
2657
  }
2643
2658
 
2644
2659
  private String default_timezone(final ThreadContext context) {
2645
- final RubyClass base = getBase(context.runtime);
2646
- return default_timezone.call(context, base, base).asJavaString(); // :utc (or :local)
2660
+ final RubyModule activeRecord = ActiveRecord(context);
2661
+ return default_timezone.call(context, activeRecord, activeRecord).asJavaString(); // :utc (or :local)
2647
2662
  }
2648
2663
 
2649
2664
  // ActiveRecord::Base.default_timezone
@@ -387,7 +387,20 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
387
387
  RubyDate rubyDate = (RubyDate) value;
388
388
  DateTime dt = rubyDate.getDateTime();
389
389
  // pgjdbc needs adjustment for default JVM timezone
390
- statement.setDate(index, new Date(dt.getMillis() - TZ_DEFAULT.getOffset(dt.getMillis())));
390
+ //
391
+ // If the date is a day when Daylight savings starts (2am changed to 3am),
392
+ // And we are in a positive GMT timezone (Australia/Melbourne)
393
+ // Then removing milliseconds equal to the TimeZone (+11 GMT),
394
+ // will result in the date being the previous day 23:00, because of the missing hour.
395
+ // So we check if the date after the shift is outside of daylight time and remove an hours worth of milliseconds.
396
+ final long dateMillis = dt.getMillis();
397
+ long offset = TZ_DEFAULT.getOffset(dt.getMillis());
398
+ if (TZ_DEFAULT.inDaylightTime(new Date(dt.getMillis())) && !TZ_DEFAULT.inDaylightTime(new Date(dateMillis - offset))) {
399
+ offset -= 3600000; // 1 hour
400
+ }
401
+ Date utcShiftedDate = new Date(dateMillis - offset);
402
+
403
+ statement.setDate(index, utcShiftedDate);
391
404
  return;
392
405
  }
393
406
 
@@ -402,6 +402,11 @@ public class SQLite3RubyJdbcConnection extends RubyJdbcConnection {
402
402
  finally { close(statement); }
403
403
  }
404
404
 
405
+ @JRubyMethod
406
+ public IRubyObject filename(ThreadContext context) {
407
+ return getConfigValue(context, "database");
408
+ }
409
+
405
410
  @Override
406
411
  @JRubyMethod(name = "rollback_savepoint", required = 1)
407
412
  public IRubyObject rollback_savepoint(final ThreadContext context, final IRubyObject name) {
@@ -460,6 +465,34 @@ public class SQLite3RubyJdbcConnection extends RubyJdbcConnection {
460
465
  return context.runtime.newBoolean(connection.isReadOnly());
461
466
  }
462
467
 
468
+ // note: sqlite3 cext uses this same method but we do not combine all our statements
469
+ // into a single ; delimited string but leave it as an array of statements. This is
470
+ // because the JDBC way of handling batches is to use addBatch().
471
+ @JRubyMethod(name = "execute_batch2")
472
+ public IRubyObject execute_batch2(ThreadContext context, IRubyObject statementsArg) {
473
+ // Assume we will only call this with an array.
474
+ final RubyArray statements = (RubyArray) statementsArg;
475
+ return withConnection(context, connection -> {
476
+ Statement statement = null;
477
+ try {
478
+ statement = createStatement(context, connection);
479
+
480
+ int length = statements.getLength();
481
+ for (int i = 0; i < length; i++) {
482
+ statement.addBatch(sqlString(statements.eltOk(i)));
483
+ }
484
+ statement.executeBatch();
485
+ return context.nil;
486
+ } catch (final SQLException e) {
487
+ // Generate list semicolon list of statements which should match AR error formatting more.
488
+ debugErrorSQL(context, sqlString(statements.join(context, context.runtime.newString(";\n"))));
489
+ throw e;
490
+ } finally {
491
+ close(statement);
492
+ }
493
+ });
494
+ }
495
+
463
496
  @Override
464
497
  protected void setDecimalParameter(final ThreadContext context,
465
498
  final Connection connection, final PreparedStatement statement,