activerecord-jdbc-adapter 60.4-java → 61.2-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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) {
@@ -197,9 +197,9 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
197
197
  RubyClass arrayClass = oidArray(context);
198
198
  RubyBasicObject attributeType = (RubyBasicObject) attributeType(context, attribute);
199
199
  // The type or its delegate is an OID::Array
200
- if (arrayClass.isInstance(attributeType) ||
200
+ if (attributeType != null && (arrayClass.isInstance(attributeType) ||
201
201
  (attributeType.hasInstanceVariable("@delegate_dc_obj") &&
202
- arrayClass.isInstance(attributeType.getInstanceVariable("@delegate_dc_obj")))) {
202
+ arrayClass.isInstance(attributeType.getInstanceVariable("@delegate_dc_obj"))))) {
203
203
  return "array";
204
204
  }
205
205
 
@@ -436,7 +436,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
436
436
  break;
437
437
 
438
438
  case "interval":
439
- statement.setObject(index, new PGInterval(value.toString()));
439
+ statement.setObject(index, stringToPGInterval(value.toString()));
440
440
  break;
441
441
 
442
442
  case "json":
@@ -493,6 +493,74 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
493
493
  }
494
494
  }
495
495
 
496
+ private int lookAhead(String value, int position, String find) {
497
+ char [] tokens = find.toCharArray();
498
+ int found = -1;
499
+
500
+ for ( int i = 0; i < tokens.length; i++ ) {
501
+ found = value.indexOf(tokens[i], position);
502
+ if ( found > 0 ) {
503
+ return found;
504
+ }
505
+ }
506
+ return found;
507
+ }
508
+
509
+ private Object stringToPGInterval(String value) throws SQLException {
510
+ if (!value.startsWith("P")) return new PGInterval(value);
511
+
512
+ PGInterval interval = new PGInterval();
513
+
514
+ /* this is copied from pgjdbc with fixes for Rails */
515
+ int number = 0;
516
+ String dateValue;
517
+ String timeValue = null;
518
+
519
+ int hasTime = value.indexOf('T');
520
+ if ( hasTime > 0 ) {
521
+ /* skip over the P */
522
+ dateValue = value.substring(1,hasTime);
523
+ timeValue = value.substring(hasTime + 1);
524
+ } else {
525
+ /* skip over the P */
526
+ dateValue = value.substring(1);
527
+ }
528
+
529
+ for ( int i = 0; i < dateValue.length(); i++ ) {
530
+ int lookAhead = lookAhead(dateValue, i, "YMD");
531
+ if (lookAhead > 0) {
532
+ char type = dateValue.charAt(lookAhead);
533
+ number = Integer.parseInt(dateValue.substring(i, lookAhead));
534
+ if (type == 'Y') {
535
+ interval.setYears(number);
536
+ } else if (type == 'M') {
537
+ interval.setMonths(number);
538
+ } else if (type == 'D') {
539
+ interval.setDays(number);
540
+ }
541
+ i = lookAhead;
542
+ }
543
+ }
544
+ if ( timeValue != null ) {
545
+ for (int i = 0; i < timeValue.length(); i++) {
546
+ int lookAhead = lookAhead(timeValue, i, "HMS");
547
+ if (lookAhead > 0) {
548
+ char type = timeValue.charAt(lookAhead);
549
+ String part = timeValue.substring(i, lookAhead);
550
+ if (timeValue.charAt(lookAhead) == 'H') {
551
+ interval.setHours(Integer.parseInt(part));
552
+ } else if (timeValue.charAt(lookAhead) == 'M') {
553
+ interval.setMinutes(Integer.parseInt(part));
554
+ } else if (timeValue.charAt(lookAhead) == 'S') {
555
+ interval.setSeconds(Double.parseDouble(part));
556
+ }
557
+ i = lookAhead;
558
+ }
559
+ }
560
+ }
561
+ return interval;
562
+ }
563
+
496
564
  protected IRubyObject jdbcToRuby(ThreadContext context, Ruby runtime, int column, int type, ResultSet resultSet) throws SQLException {
497
565
  return typeMap != null ?
498
566
  convertWithTypeMap(context, runtime, column, type, resultSet) :
@@ -874,34 +942,25 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
874
942
  private static String formatInterval(final Object object) {
875
943
  final PGInterval interval = (PGInterval) object;
876
944
  final StringBuilder str = new StringBuilder(32);
945
+ str.append("P");
877
946
 
878
947
  final int years = interval.getYears();
879
- if (years != 0) str.append(years).append(" years ");
948
+ if (years != 0) str.append(years).append("Y");
880
949
 
881
950
  final int months = interval.getMonths();
882
- if (months != 0) str.append(months).append(" months ");
951
+ if (months != 0) str.append(months).append("M");
883
952
 
884
953
  final int days = interval.getDays();
885
- if (days != 0) str.append(days).append(" days ");
954
+ if (days != 0) str.append(days).append("D");
886
955
 
887
956
  final int hours = interval.getHours();
888
957
  final int mins = interval.getMinutes();
889
- final int secs = (int) interval.getSeconds();
890
- if (hours != 0 || mins != 0 || secs != 0) { // xx:yy:zz if not all 00
891
- if (hours < 10) str.append('0');
892
-
893
- str.append(hours).append(':');
894
-
895
- if (mins < 10) str.append('0');
896
-
897
- str.append(mins).append(':');
898
-
899
- if (secs < 10) str.append('0');
900
-
901
- str.append(secs);
902
-
903
- } else if (str.length() > 1) {
904
- str.deleteCharAt(str.length() - 1); // " " at the end
958
+ final double secs = interval.getSeconds();
959
+ if (hours != 0 || mins != 0 || secs != 0) {
960
+ str.append("T");
961
+ if (hours != 0) str.append(hours).append("H");
962
+ if (mins != 0) str.append(mins).append("M");
963
+ if (secs != 0) str.append(secs).append("S");
905
964
  }
906
965
 
907
966
  return str.toString();
@@ -370,10 +370,9 @@ public class SQLite3RubyJdbcConnection extends RubyJdbcConnection {
370
370
  }
371
371
 
372
372
  @Override
373
- public IRubyObject begin(ThreadContext context, IRubyObject level) {
374
- throw context.runtime.newRaiseException(getTransactionIsolationError(context.runtime),
375
- "SQLite3 does not support isolation levels"
376
- );
373
+ protected boolean resetSavepoints(final ThreadContext context, final Connection connection) throws SQLException {
374
+ connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
375
+ return super.resetSavepoints(context, connection);
377
376
  }
378
377
 
379
378
  @Override
metadata CHANGED
@@ -1,21 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-jdbc-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: '60.4'
4
+ version: '61.2'
5
5
  platform: java
6
6
  authors:
7
7
  - Nick Sieger, Ola Bini, Karol Bucek and JRuby contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-29 00:00:00.000000000 Z
11
+ date: 2022-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 6.0.0
18
+ version: 6.1.0
19
19
  name: activerecord
20
20
  prerelease: false
21
21
  type: :runtime
@@ -23,7 +23,7 @@ dependencies:
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 6.0.0
26
+ version: 6.1.0
27
27
  description: 'AR-JDBC is a database adapter for Rails'' ActiveRecord component designed
28
28
  to be used with JRuby built upon Java''s JDBC API for database access. Provides
29
29
  (ActiveRecord) built-in adapters: MySQL, PostgreSQL, SQLite3, and SQLServer.'
@@ -35,6 +35,7 @@ executables: []
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
+ - ".github/workflows/ruby.yml"
38
39
  - ".gitignore"
39
40
  - ".travis.yml"
40
41
  - ".yardopts"
@@ -230,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
231
  - !ruby/object:Gem::Version
231
232
  version: '0'
232
233
  requirements: []
233
- rubygems_version: 3.2.14
234
+ rubygems_version: 3.2.29
234
235
  signing_key:
235
236
  specification_version: 4
236
237
  summary: JDBC adapter for ActiveRecord, for use within JRuby on Rails.