activerecord-jdbc-adapter 60.3-java → 61.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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) {