activerecord-jdbc-adapter 60.3-java → 61.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.
@@ -48,7 +48,7 @@ module ArJdbc
48
48
  end
49
49
 
50
50
  def has_default_function?(default_value, default)
51
- !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
51
+ !default_value && default && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
52
52
  end
53
53
  end
54
54
 
@@ -145,7 +145,7 @@ module ArJdbc
145
145
  m.register_type 'uuid', OID::Uuid.new
146
146
  m.register_type 'xml', OID::Xml.new
147
147
  m.register_type 'tsvector', OID::SpecializedString.new(:tsvector)
148
- m.register_type 'macaddr', OID::SpecializedString.new(:macaddr)
148
+ m.register_type 'macaddr', OID::Macaddr.new
149
149
  m.register_type 'citext', OID::SpecializedString.new(:citext)
150
150
  m.register_type 'ltree', OID::SpecializedString.new(:ltree)
151
151
  m.register_type 'line', OID::SpecializedString.new(:line)
@@ -155,9 +155,9 @@ module ArJdbc
155
155
  m.register_type 'polygon', OID::SpecializedString.new(:polygon)
156
156
  m.register_type 'circle', OID::SpecializedString.new(:circle)
157
157
 
158
- m.register_type 'interval' do |_, _, sql_type|
158
+ m.register_type 'interval' do |*args, sql_type|
159
159
  precision = extract_precision(sql_type)
160
- OID::SpecializedString.new(:interval, precision: precision)
160
+ OID::Interval.new(precision: precision)
161
161
  end
162
162
 
163
163
  register_class_with_precision m, 'time', Type::Time
@@ -244,6 +244,7 @@ module ArJdbc
244
244
  ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
245
245
  ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
246
246
  ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
247
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
247
248
  ActiveRecord::Type.register(:json, Type::Json, adapter: :postgresql)
248
249
  ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
249
250
  ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
@@ -16,6 +16,33 @@ require "active_record/connection_adapters/sqlite3/schema_dumper"
16
16
  require "active_record/connection_adapters/sqlite3/schema_statements"
17
17
  require "active_support/core_ext/class/attribute"
18
18
 
19
+ module SQLite3
20
+ module Constants
21
+ module Open
22
+ READONLY = 0x00000001
23
+ READWRITE = 0x00000002
24
+ CREATE = 0x00000004
25
+ DELETEONCLOSE = 0x00000008
26
+ EXCLUSIVE = 0x00000010
27
+ AUTOPROXY = 0x00000020
28
+ URI = 0x00000040
29
+ MEMORY = 0x00000080
30
+ MAIN_DB = 0x00000100
31
+ TEMP_DB = 0x00000200
32
+ TRANSIENT_DB = 0x00000400
33
+ MAIN_JOURNAL = 0x00000800
34
+ TEMP_JOURNAL = 0x00001000
35
+ SUBJOURNAL = 0x00002000
36
+ MASTER_JOURNAL = 0x00004000
37
+ NOMUTEX = 0x00008000
38
+ FULLMUTEX = 0x00010000
39
+ SHAREDCACHE = 0x00020000
40
+ PRIVATECACHE = 0x00040000
41
+ WAL = 0x00080000
42
+ end
43
+ end
44
+ end
45
+
19
46
  module ArJdbc
20
47
  # All the code in this module is a copy of ConnectionAdapters::SQLite3Adapter from active_record 5.
21
48
  # The constants at the front of this file are to allow the rest of the file to remain with no modifications
@@ -69,6 +96,10 @@ module ArJdbc
69
96
  true
70
97
  end
71
98
 
99
+ def supports_transaction_isolation?
100
+ true
101
+ end
102
+
72
103
  def supports_partial_index?
73
104
  database_version >= "3.9.0"
74
105
  end
@@ -85,6 +116,10 @@ module ArJdbc
85
116
  true
86
117
  end
87
118
 
119
+ def supports_check_constraints?
120
+ true
121
+ end
122
+
88
123
  def supports_views?
89
124
  true
90
125
  end
@@ -114,13 +149,6 @@ module ArJdbc
114
149
  true
115
150
  end
116
151
 
117
- # Returns 62. SQLite supports index names up to 64
118
- # characters. The rest is used by Rails internally to perform
119
- # temporary rename operations
120
- def allowed_index_name_length
121
- index_name_length - 2
122
- end
123
-
124
152
  def native_database_types #:nodoc:
125
153
  NATIVE_DATABASE_TYPES
126
154
  end
@@ -159,7 +187,7 @@ module ArJdbc
159
187
  #++
160
188
 
161
189
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
162
- :begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with
190
+ :pragma
163
191
  ) # :nodoc:
164
192
  private_constant :READ_QUERY
165
193
 
@@ -187,15 +215,15 @@ module ArJdbc
187
215
  #def execute(sql, name = nil) #:nodoc:
188
216
 
189
217
  def begin_db_transaction #:nodoc:
190
- log("begin transaction", nil) { @connection.transaction }
218
+ log("begin transaction", 'TRANSACTION') { @connection.transaction }
191
219
  end
192
220
 
193
221
  def commit_db_transaction #:nodoc:
194
- log("commit transaction", nil) { @connection.commit }
222
+ log("commit transaction", 'TRANSACTION') { @connection.commit }
195
223
  end
196
224
 
197
225
  def exec_rollback_db_transaction #:nodoc:
198
- log("rollback transaction", nil) { @connection.rollback }
226
+ log("rollback transaction", 'TRANSACTION') { @connection.rollback }
199
227
  end
200
228
 
201
229
  # SCHEMA STATEMENTS ========================================
@@ -205,8 +233,11 @@ module ArJdbc
205
233
  pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
206
234
  end
207
235
 
208
- def remove_index(table_name, options = {}) #:nodoc:
209
- index_name = index_name_for_remove(table_name, options)
236
+ def remove_index(table_name, column_name = nil, **options) # :nodoc:
237
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
238
+
239
+ index_name = index_name_for_remove(table_name, column_name, options)
240
+
210
241
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
211
242
  end
212
243
 
@@ -215,14 +246,16 @@ module ArJdbc
215
246
  # Example:
216
247
  # rename_table('octopuses', 'octopi')
217
248
  def rename_table(table_name, new_name)
249
+ schema_cache.clear_data_source_cache!(table_name.to_s)
250
+ schema_cache.clear_data_source_cache!(new_name.to_s)
218
251
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
219
252
  rename_table_indexes(table_name, new_name)
220
253
  end
221
254
 
222
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
255
+ def add_column(table_name, column_name, type, **options) #:nodoc:
223
256
  if invalid_alter_table_type?(type, options)
224
257
  alter_table(table_name) do |definition|
225
- definition.column(column_name, type, options)
258
+ definition.column(column_name, type, **options)
226
259
  end
227
260
  else
228
261
  super
@@ -255,16 +288,11 @@ module ArJdbc
255
288
  end
256
289
  end
257
290
 
258
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
291
+ def change_column(table_name, column_name, type, **options) #:nodoc:
259
292
  alter_table(table_name) do |definition|
260
293
  definition[column_name].instance_eval do
261
294
  self.type = type
262
- self.limit = options[:limit] if options.include?(:limit)
263
- self.default = options[:default] if options.include?(:default)
264
- self.null = options[:null] if options.include?(:null)
265
- self.precision = options[:precision] if options.include?(:precision)
266
- self.scale = options[:scale] if options.include?(:scale)
267
- self.collation = options[:collation] if options.include?(:collation)
295
+ self.options.merge!(options)
268
296
  end
269
297
  end
270
298
  end
@@ -313,12 +341,17 @@ module ArJdbc
313
341
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
314
342
  elsif insert.update_duplicates?
315
343
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
344
+ sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
316
345
  sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
317
346
  end
318
347
 
319
348
  sql
320
349
  end
321
350
 
351
+ def shared_cache?
352
+ config[:properties] && config[:properties][:shared_cache] == true
353
+ end
354
+
322
355
  def get_database_version # :nodoc:
323
356
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
324
357
  end
@@ -327,15 +360,6 @@ module ArJdbc
327
360
  "DELETE FROM #{quote_table_name(table_name)}"
328
361
  end
329
362
 
330
- def build_truncate_statements(*table_names)
331
- table_names.flatten.map { |table_name| build_truncate_statement table_name }
332
- end
333
-
334
- def truncate(table_name, name = nil)
335
- ActiveRecord::Base.clear_query_caches_for_current_thread if @query_cache_enabled
336
- execute(build_truncate_statement(table_name), name)
337
- end
338
-
339
363
  def check_version
340
364
  if database_version < "3.8.0"
341
365
  raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
@@ -349,11 +373,6 @@ module ArJdbc
349
373
  999
350
374
  end
351
375
 
352
- def initialize_type_map(m = type_map)
353
- super
354
- register_class_with_limit m, %r(int)i, SQLite3Integer
355
- end
356
-
357
376
  def table_structure(table_name)
358
377
  structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
359
378
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
@@ -368,7 +387,12 @@ module ArJdbc
368
387
  options[:null] == false && options[:default].nil?
369
388
  end
370
389
 
371
- def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
390
+ def alter_table(
391
+ table_name,
392
+ foreign_keys = foreign_keys(table_name),
393
+ check_constraints = check_constraints(table_name),
394
+ **options
395
+ )
372
396
  altered_table_name = "a#{table_name}"
373
397
 
374
398
  caller = lambda do |definition|
@@ -378,7 +402,11 @@ module ArJdbc
378
402
  fk.options[:column] = column
379
403
  end
380
404
  to_table = strip_table_name_prefix_and_suffix(fk.to_table)
381
- definition.foreign_key(to_table, fk.options)
405
+ definition.foreign_key(to_table, **fk.options)
406
+ end
407
+
408
+ check_constraints.each do |chk|
409
+ definition.check_constraint(chk.expression, **chk.options)
382
410
  end
383
411
 
384
412
  yield definition if block_given?
@@ -400,11 +428,12 @@ module ArJdbc
400
428
  def copy_table(from, to, options = {})
401
429
  from_primary_key = primary_key(from)
402
430
  options[:id] = false
403
- create_table(to, options) do |definition|
431
+ create_table(to, **options) do |definition|
404
432
  @definition = definition
405
433
  if from_primary_key.is_a?(Array)
406
434
  @definition.primary_keys from_primary_key
407
435
  end
436
+
408
437
  columns(from).each do |column|
409
438
  column_name = options[:rename] ?
410
439
  (options[:rename][column.name] ||
@@ -432,7 +461,7 @@ module ArJdbc
432
461
  name = index.name
433
462
  # indexes sqlite creates for internal use start with `sqlite_` and
434
463
  # don't need to be copied
435
- next if name.starts_with?("sqlite_")
464
+ next if name.start_with?("sqlite_")
436
465
  if to == "a#{from}"
437
466
  name = "t#{name}"
438
467
  elsif from == "a#{to}"
@@ -449,10 +478,10 @@ module ArJdbc
449
478
 
450
479
  unless columns.empty?
451
480
  # index name can't be the same
452
- opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
453
- opts[:unique] = true if index.unique
454
- opts[:where] = index.where if index.where
455
- add_index(to, columns, opts)
481
+ options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
482
+ options[:unique] = true if index.unique
483
+ options[:where] = index.where if index.where
484
+ add_index(to, columns, **options)
456
485
  end
457
486
  end
458
487
  end
@@ -517,7 +546,7 @@ module ArJdbc
517
546
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
518
547
  end
519
548
 
520
- basic_structure.map! do |column|
549
+ basic_structure.map do |column|
521
550
  column_name = column["name"]
522
551
 
523
552
  if collation_hash.has_key? column_name
@@ -539,18 +568,6 @@ module ArJdbc
539
568
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
540
569
  end
541
570
 
542
- # DIFFERENCE: FQN
543
- class SQLite3Integer < ::ActiveRecord::Type::Integer # :nodoc:
544
- private
545
- def _limit
546
- # INTEGER storage class can be stored 8 bytes value.
547
- # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
548
- limit || 8
549
- end
550
- end
551
-
552
- # DIFFERENCE: FQN
553
- ::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
554
571
  end
555
572
  # DIFFERENCE: A registration here is moved down to concrete class so we are not registering part of an adapter.
556
573
  end
@@ -671,7 +688,9 @@ module ActiveRecord::ConnectionAdapters
671
688
  end
672
689
 
673
690
  def begin_isolated_db_transaction(isolation)
674
- raise ActiveRecord::TransactionIsolationError, 'adapter does not support setting transaction isolation'
691
+ raise ActiveRecord::TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
692
+ raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
693
+ super
675
694
  end
676
695
 
677
696
  # SQLite driver doesn't support all types of insert statements with executeUpdate so
@@ -707,5 +726,23 @@ module ActiveRecord::ConnectionAdapters
707
726
  total_sql
708
727
  end
709
728
  end
729
+
730
+ def initialize_type_map(m = type_map)
731
+ super
732
+ register_class_with_limit m, %r(int)i, SQLite3Integer
733
+ end
734
+
735
+ # DIFFERENCE: FQN
736
+ class SQLite3Integer < ::ActiveRecord::Type::Integer # :nodoc:
737
+ private
738
+ def _limit
739
+ # INTEGER storage class can be stored 8 bytes value.
740
+ # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
741
+ limit || 8
742
+ end
743
+ end
744
+
745
+ # DIFFERENCE: FQN
746
+ ::ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
710
747
  end
711
748
  end
@@ -35,7 +35,17 @@ ArJdbc::ConnectionMethods.module_eval do
35
35
  # * http://sqlite.org/c3ref/open.html
36
36
  # * http://sqlite.org/c3ref/c_open_autoproxy.html
37
37
  # => 0x01 = readonly, 0x40 = uri (default in JDBC)
38
- config[:properties][:open_mode] = 0x01 | 0x40
38
+ config[:properties][:open_mode] = ::SQLite3::Constants::Open::READONLY | ::SQLite3::Constants::Open::URI
39
+ end
40
+
41
+ if config[:flags]
42
+ config[:properties][:open_mode] ||= 0
43
+ config[:properties][:open_mode] |= config[:flags]
44
+
45
+ # JDBC driver has an extra flag for it
46
+ if config[:flags] & ::SQLite3::Constants::Open::SHAREDCACHE != 0
47
+ config[:properties][:shared_cache] = true
48
+ end
39
49
  end
40
50
 
41
51
  timeout = config[:timeout]
@@ -5,15 +5,17 @@ module ActiveRecord::Tasks
5
5
  DatabaseTasks.module_eval do
6
6
 
7
7
  # @override patched to adapt jdbc configuration
8
- def each_current_configuration(environment, spec_name = nil)
8
+ def each_current_configuration(environment, name = nil)
9
9
  environments = [environment]
10
10
  environments << 'test' if environment == 'development'
11
11
 
12
12
  environments.each do |env|
13
13
  ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
14
- next if spec_name && spec_name != db_config.spec_name
14
+ next if name && name != db_config.name
15
15
 
16
- yield adapt_jdbc_config(db_config.config), db_config.spec_name, env unless db_config.config['database'].blank?
16
+ if db_config.database
17
+ yield adapt_jdbc_config(db_config), db_config.name, env
18
+ end
17
19
  end
18
20
  end
19
21
  end
@@ -21,21 +23,24 @@ module ActiveRecord::Tasks
21
23
  # @override patched to adapt jdbc configuration
22
24
  def each_local_configuration
23
25
  ActiveRecord::Base.configurations.configs_for.each do |db_config|
24
- next unless db_config.config['database']
26
+ next unless db_config.database
25
27
 
26
- if local_database?(db_config.config)
27
- yield adapt_jdbc_config(db_config.config)
28
+ if local_database?(db_config)
29
+ yield adapt_jdbc_config(db_config)
28
30
  else
29
- $stderr.puts "This task only modifies local databases. #{db_config.config['database']} is on a remote host."
31
+ $stderr.puts "This task only modifies local databases. #{db_config.database} is on a remote host."
30
32
  end
31
33
  end
32
34
  end
33
35
 
34
36
  private
35
37
 
36
- def adapt_jdbc_config(config)
37
- return config unless config['adapter']
38
- config.merge 'adapter' => config['adapter'].sub(/^jdbc/, '')
38
+ def adapt_jdbc_config(db_config)
39
+ if db_config.adapter.start_with? 'jdbc'
40
+ config = db_config.configuration_hash.merge(adapter: db_config.adapter.sub(/^jdbc/, ''))
41
+ db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new(db_config.env_name, db_config.name, config)
42
+ end
43
+ db_config
39
44
  end
40
45
 
41
46
  end
@@ -1,3 +1,3 @@
1
1
  module ArJdbc
2
- VERSION = '60.3'
2
+ VERSION = '61.0'
3
3
  end
@@ -86,6 +86,7 @@ import org.jruby.anno.JRubyMethod;
86
86
  import org.jruby.exceptions.RaiseException;
87
87
  import org.jruby.ext.bigdecimal.RubyBigDecimal;
88
88
  import org.jruby.ext.date.RubyDate;
89
+ import org.jruby.ext.date.RubyDateTime;
89
90
  import org.jruby.javasupport.JavaEmbedUtils;
90
91
  import org.jruby.javasupport.JavaUtil;
91
92
  import org.jruby.runtime.Block;
@@ -124,6 +125,7 @@ public class RubyJdbcConnection extends RubyObject {
124
125
  private IRubyObject config;
125
126
  private IRubyObject adapter; // the AbstractAdapter instance we belong to
126
127
  private volatile boolean connected = true;
128
+ private RubyClass attributeClass;
127
129
 
128
130
  private boolean lazy = false; // final once set on initialize
129
131
  private boolean jndi; // final once set on initialize
@@ -132,6 +134,7 @@ public class RubyJdbcConnection extends RubyObject {
132
134
 
133
135
  protected RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
134
136
  super(runtime, metaClass);
137
+ attributeClass = runtime.getModule("ActiveModel").getClass("Attribute");
135
138
  }
136
139
 
137
140
  private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
@@ -359,7 +362,7 @@ public class RubyJdbcConnection extends RubyObject {
359
362
  if ( ! connection.getAutoCommit() ) {
360
363
  try {
361
364
  connection.commit();
362
- resetSavepoints(context); // if any
365
+ resetSavepoints(context, connection); // if any
363
366
  return context.runtime.newBoolean(true);
364
367
  }
365
368
  finally {
@@ -380,7 +383,7 @@ public class RubyJdbcConnection extends RubyObject {
380
383
  if ( ! connection.getAutoCommit() ) {
381
384
  try {
382
385
  connection.rollback();
383
- resetSavepoints(context); // if any
386
+ resetSavepoints(context, connection); // if any
384
387
  return context.tru;
385
388
  } finally {
386
389
  connection.setAutoCommit(true);
@@ -516,7 +519,7 @@ public class RubyJdbcConnection extends RubyObject {
516
519
  return null;
517
520
  }
518
521
 
519
- protected boolean resetSavepoints(final ThreadContext context) {
522
+ protected boolean resetSavepoints(final ThreadContext context, final Connection connection) throws SQLException {
520
523
  if ( hasInternalVariable("savepoints") ) {
521
524
  removeInternalVariable("savepoints");
522
525
  return true;
@@ -610,11 +613,7 @@ public class RubyJdbcConnection extends RubyObject {
610
613
  return convertJavaToRuby( connection.unwrap(Connection.class) );
611
614
  }
612
615
  }
613
- catch (AbstractMethodError e) {
614
- debugStackTrace(context, e);
615
- warn(context, "driver/pool connection does not support unwrapping: " + e);
616
- }
617
- catch (SQLException e) {
616
+ catch (AbstractMethodError | SQLException e) {
618
617
  debugStackTrace(context, e);
619
618
  warn(context, "driver/pool connection does not support unwrapping: " + e);
620
619
  }
@@ -860,27 +859,25 @@ public class RubyJdbcConnection extends RubyObject {
860
859
  */
861
860
  @JRubyMethod(name = "execute_insert_pk", required = 2)
862
861
  public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
863
- return withConnection(context, new Callable<IRubyObject>() {
864
- public IRubyObject call(final Connection connection) throws SQLException {
865
- Statement statement = null;
866
- final String query = sqlString(sql);
867
- try {
868
-
869
- statement = createStatement(context, connection);
862
+ return withConnection(context, connection -> {
863
+ Statement statement = null;
864
+ final String query = sqlString(sql);
865
+ try {
870
866
 
871
- if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
872
- statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
873
- } else {
874
- statement.executeUpdate(query, createStatementPk(pk));
875
- }
867
+ statement = createStatement(context, connection);
876
868
 
877
- return mapGeneratedKeys(context, connection, statement);
878
- } catch (final SQLException e) {
879
- debugErrorSQL(context, query);
880
- throw e;
881
- } finally {
882
- close(statement);
869
+ if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
870
+ statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
871
+ } else {
872
+ statement.executeUpdate(query, createStatementPk(pk));
883
873
  }
874
+
875
+ return mapGeneratedKeys(context, connection, statement);
876
+ } catch (final SQLException e) {
877
+ debugErrorSQL(context, query);
878
+ throw e;
879
+ } finally {
880
+ close(statement);
884
881
  }
885
882
  });
886
883
  }
@@ -903,26 +900,24 @@ public class RubyJdbcConnection extends RubyObject {
903
900
  @JRubyMethod(name = "execute_insert_pk", required = 3)
904
901
  public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
905
902
  final IRubyObject pk) {
906
- return withConnection(context, new Callable<IRubyObject>() {
907
- public IRubyObject call(final Connection connection) throws SQLException {
908
- PreparedStatement statement = null;
909
- final String query = sqlString(sql);
910
- try {
911
- if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
912
- statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
913
- } else {
914
- statement = connection.prepareStatement(query, createStatementPk(pk));
915
- }
916
-
917
- setStatementParameters(context, connection, statement, (RubyArray) binds);
918
- statement.executeUpdate();
919
- return mapGeneratedKeys(context, connection, statement);
920
- } catch (final SQLException e) {
921
- debugErrorSQL(context, query);
922
- throw e;
923
- } finally {
924
- close(statement);
903
+ return withConnection(context, connection -> {
904
+ PreparedStatement statement = null;
905
+ final String query = sqlString(sql);
906
+ try {
907
+ if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
908
+ statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
909
+ } else {
910
+ statement = connection.prepareStatement(query, createStatementPk(pk));
925
911
  }
912
+
913
+ setStatementParameters(context, connection, statement, (RubyArray) binds);
914
+ statement.executeUpdate();
915
+ return mapGeneratedKeys(context, connection, statement);
916
+ } catch (final SQLException e) {
917
+ debugErrorSQL(context, query);
918
+ throw e;
919
+ } finally {
920
+ close(statement);
926
921
  }
927
922
  });
928
923
  }
@@ -1012,12 +1007,12 @@ public class RubyJdbcConnection extends RubyObject {
1012
1007
  binds = null;
1013
1008
  } else { // (sql, binds)
1014
1009
  maxRows = 0;
1015
- binds = (RubyArray) TypeConverter.checkArrayType(args[1]);
1010
+ binds = (RubyArray) TypeConverter.checkArrayType(context, args[1]);
1016
1011
  }
1017
1012
  break;
1018
1013
  case 3: // (sql, max_rows, binds)
1019
1014
  maxRows = RubyNumeric.fix2int(args[1]);
1020
- binds = (RubyArray) TypeConverter.checkArrayType(args[2]);
1015
+ binds = (RubyArray) TypeConverter.checkArrayType(context, args[2]);
1021
1016
  break;
1022
1017
  default: // (sql) 1-arg
1023
1018
  maxRows = 0;
@@ -1106,6 +1101,28 @@ public class RubyJdbcConnection extends RubyObject {
1106
1101
  });
1107
1102
  }
1108
1103
 
1104
+ @JRubyMethod(required = 1)
1105
+ public IRubyObject get_first_value(final ThreadContext context, final IRubyObject sql) {
1106
+ return withConnection(context, connection -> {
1107
+ Statement statement = null;
1108
+ final String query = sqlString(sql);
1109
+ try {
1110
+ statement = createStatement(context, connection);
1111
+ statement.execute(query);
1112
+ ResultSet rs = statement.getResultSet();
1113
+ if (rs == null || !rs.next()) return context.nil;
1114
+
1115
+ return jdbcToRuby(context, context.getRuntime(), 1, rs.getMetaData().getColumnType(1), rs);
1116
+
1117
+ } catch (final SQLException e) {
1118
+ debugErrorSQL(context, query);
1119
+ throw e;
1120
+ } finally {
1121
+ close(statement);
1122
+ }
1123
+ });
1124
+ }
1125
+
1109
1126
  /**
1110
1127
  * Prepares a query, returns a wrapped PreparedStatement. This takes care of exception wrapping
1111
1128
  * @param context which context this method is executing on.
@@ -2402,9 +2419,16 @@ public class RubyJdbcConnection extends RubyObject {
2402
2419
  final Connection connection, final PreparedStatement statement,
2403
2420
  final int index, IRubyObject attribute) throws SQLException {
2404
2421
 
2405
- //debugMessage(context, attribute);
2406
- final int type = jdbcTypeForAttribute(context, attribute);
2407
- IRubyObject value = valueForDatabase(context, attribute);
2422
+ final IRubyObject value;
2423
+ final int type;
2424
+
2425
+ if (attributeClass.isInstance(attribute)) {
2426
+ type = jdbcTypeForAttribute(context, attribute);
2427
+ value = valueForDatabase(context, attribute);
2428
+ } else {
2429
+ type = jdbcTypeForPrimitiveAttribute(context, attribute);
2430
+ value = attribute;
2431
+ }
2408
2432
 
2409
2433
  // All the set methods were calling this first so save a method call in the nil case
2410
2434
  if ( value == context.nil ) {
@@ -2520,6 +2544,34 @@ public class RubyJdbcConnection extends RubyObject {
2520
2544
  return Types.OTHER; // -1 as well as 0 are used in Types
2521
2545
  }
2522
2546
 
2547
+ protected String internedTypeForPrimitive(final ThreadContext context, final IRubyObject value) throws SQLException {
2548
+ if (value instanceof RubyString) {
2549
+ return "string";
2550
+ }
2551
+ if (value instanceof RubyInteger) {
2552
+ return "integer";
2553
+ }
2554
+ if (value instanceof RubyNumeric) {
2555
+ return "float";
2556
+ }
2557
+ if (value instanceof RubyTime || value instanceof RubyDateTime) {
2558
+ return "timestamp";
2559
+ }
2560
+ if (value instanceof RubyDate) {
2561
+ return "date";
2562
+ }
2563
+ if (value instanceof RubyBoolean) {
2564
+ return "boolean";
2565
+ }
2566
+ return "string";
2567
+ }
2568
+
2569
+ protected Integer jdbcTypeForPrimitiveAttribute(final ThreadContext context,
2570
+ final IRubyObject attribute) throws SQLException {
2571
+ final String internedType = internedTypeForPrimitive(context, attribute);
2572
+ return jdbcTypeFor(internedType);
2573
+ }
2574
+
2523
2575
  protected Integer jdbcTypeFor(final String type) {
2524
2576
  return JDBC_TYPE_FOR.get(type);
2525
2577
  }
@@ -2531,7 +2583,9 @@ public class RubyJdbcConnection extends RubyObject {
2531
2583
  }
2532
2584
 
2533
2585
  protected static IRubyObject attributeSQLType(final ThreadContext context, final IRubyObject attribute) {
2534
- return attributeType(context, attribute).callMethod(context, "type");
2586
+ final IRubyObject type = attributeType(context, attribute);
2587
+ if (type != null) return type.callMethod(context, "type");
2588
+ return context.nil;
2535
2589
  }
2536
2590
 
2537
2591
  private final CachingCallSite value_site = new FunctionalCachingCallSite("value"); // AR::Attribute#value
@@ -2544,23 +2598,7 @@ public class RubyJdbcConnection extends RubyObject {
2544
2598
 
2545
2599
  final IRubyObject value = value_site.call(context, attribute, attribute);
2546
2600
 
2547
- if (value instanceof RubyInteger) {
2548
- return "integer";
2549
- }
2550
-
2551
- if (value instanceof RubyNumeric) {
2552
- return "float";
2553
- }
2554
-
2555
- if (value instanceof RubyTime) {
2556
- return "timestamp";
2557
- }
2558
-
2559
- if (value instanceof RubyBoolean) {
2560
- return "boolean";
2561
- }
2562
-
2563
- return "string";
2601
+ return internedTypeForPrimitive(context, value);
2564
2602
  }
2565
2603
 
2566
2604
  protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final IRubyObject value) {