activerecord-jdbc-adapter 60.0-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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +14 -11
- data/Gemfile +1 -1
- data/README.md +8 -7
- data/activerecord-jdbc-adapter.gemspec +1 -1
- data/lib/arel/visitors/postgresql_jdbc.rb +1 -1
- data/lib/arjdbc/abstract/core.rb +1 -0
- data/lib/arjdbc/abstract/database_statements.rb +6 -2
- data/lib/arjdbc/abstract/transaction_support.rb +20 -7
- data/lib/arjdbc/mysql/adapter.rb +14 -5
- data/lib/arjdbc/mysql/connection_methods.rb +6 -1
- data/lib/arjdbc/postgresql/adapter.rb +94 -63
- data/lib/arjdbc/postgresql/column.rb +1 -1
- data/lib/arjdbc/postgresql/connection_methods.rb +1 -0
- data/lib/arjdbc/postgresql/oid_types.rb +5 -4
- data/lib/arjdbc/sqlite3/adapter.rb +106 -56
- data/lib/arjdbc/sqlite3/connection_methods.rb +12 -1
- data/lib/arjdbc/tasks/databases.rake +15 -10
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/01-tomcat.rake +2 -2
- data/rakelib/rails.rake +1 -1
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +124 -41
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +51 -0
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +101 -36
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +3 -4
- data/src/java/arjdbc/util/DateTimeUtils.java +12 -4
- metadata +6 -7
data/lib/arjdbc/version.rb
CHANGED
data/rakelib/01-tomcat.rake
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
namespace :'tomcat-jndi' do # contains a FS JNDI impl (for tests)
|
2
2
|
|
3
|
-
TOMCAT_MAVEN_REPO = '
|
3
|
+
TOMCAT_MAVEN_REPO = 'https://repo1.maven.org/maven2/org/apache/tomcat'
|
4
4
|
TOMCAT_VERSION = '7.0.54'
|
5
5
|
|
6
6
|
DOWNLOAD_DIR = File.expand_path('../test/jars', File.dirname(__FILE__))
|
@@ -48,4 +48,4 @@ namespace :'tomcat-jndi' do # contains a FS JNDI impl (for tests)
|
|
48
48
|
rm jar_path if File.exist?(jar_path)
|
49
49
|
end
|
50
50
|
|
51
|
-
end
|
51
|
+
end
|
data/rakelib/rails.rake
CHANGED
@@ -51,7 +51,7 @@ namespace :rails do
|
|
51
51
|
ruby_opts_string += " -C \"#{ar_path}\""
|
52
52
|
ruby_opts_string += " -rbundler/setup"
|
53
53
|
ruby_opts_string += " -rminitest -rminitest/excludes" unless ENV['NO_EXCLUDES'].eql?('true')
|
54
|
-
file_list = ENV["TEST"] ? FileList[ ENV["TEST"] ] : test_files_finder.call
|
54
|
+
file_list = ENV["TEST"] ? FileList[ ENV["TEST"].split(',') ] : test_files_finder.call
|
55
55
|
file_list_string = file_list.map { |fn| "\"#{fn}\"" }.join(' ')
|
56
56
|
# test_loader_code = "-e \"ARGV.each{|f| require f}\"" # :direct
|
57
57
|
option_list = ( ENV["TESTOPTS"] || ENV["TESTOPT"] || ENV["TEST_OPTS"] || '' )
|
@@ -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
|
}
|
@@ -676,7 +675,10 @@ public class RubyJdbcConnection extends RubyObject {
|
|
676
675
|
|
677
676
|
private void connectImpl(final boolean forceConnection) throws SQLException {
|
678
677
|
setConnection( forceConnection ? newConnection() : null );
|
679
|
-
if (
|
678
|
+
if (forceConnection) {
|
679
|
+
if (getConnectionImpl() == null) throw new SQLException("Didn't get a connection. Wrong URL?");
|
680
|
+
configureConnection();
|
681
|
+
}
|
680
682
|
}
|
681
683
|
|
682
684
|
@JRubyMethod(name = "read_only?")
|
@@ -832,24 +834,45 @@ public class RubyJdbcConnection extends RubyObject {
|
|
832
834
|
return mapQueryResult(context, connection, resultSet);
|
833
835
|
}
|
834
836
|
|
837
|
+
private static String[] createStatementPk(IRubyObject pk) {
|
838
|
+
String[] statementPk;
|
839
|
+
if (pk instanceof RubyArray) {
|
840
|
+
RubyArray ary = (RubyArray) pk;
|
841
|
+
int size = ary.size();
|
842
|
+
statementPk = new String[size];
|
843
|
+
for (int i = 0; i < size; i++) {
|
844
|
+
statementPk[i] = sqlString(ary.eltInternal(i));
|
845
|
+
}
|
846
|
+
} else {
|
847
|
+
statementPk = new String[] { sqlString(pk) };
|
848
|
+
}
|
849
|
+
return statementPk;
|
850
|
+
}
|
851
|
+
|
835
852
|
/**
|
836
853
|
* Executes an INSERT SQL statement
|
837
854
|
* @param context
|
838
855
|
* @param sql
|
856
|
+
* @param pk Rails PK
|
839
857
|
* @return ActiveRecord::Result
|
840
858
|
* @throws SQLException
|
841
859
|
*/
|
842
|
-
@JRubyMethod(name = "
|
843
|
-
public IRubyObject
|
860
|
+
@JRubyMethod(name = "execute_insert_pk", required = 2)
|
861
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
|
844
862
|
return withConnection(context, connection -> {
|
845
863
|
Statement statement = null;
|
846
864
|
final String query = sqlString(sql);
|
847
865
|
try {
|
848
866
|
|
849
867
|
statement = createStatement(context, connection);
|
850
|
-
statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
|
851
|
-
return mapGeneratedKeys(context, connection, statement);
|
852
868
|
|
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));
|
873
|
+
}
|
874
|
+
|
875
|
+
return mapGeneratedKeys(context, connection, statement);
|
853
876
|
} catch (final SQLException e) {
|
854
877
|
debugErrorSQL(context, query);
|
855
878
|
throw e;
|
@@ -859,26 +882,37 @@ public class RubyJdbcConnection extends RubyObject {
|
|
859
882
|
});
|
860
883
|
}
|
861
884
|
|
885
|
+
@Deprecated
|
886
|
+
@JRubyMethod(name = "execute_insert", required = 1)
|
887
|
+
public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql) {
|
888
|
+
return execute_insert_pk(context, sql, context.nil);
|
889
|
+
}
|
890
|
+
|
862
891
|
/**
|
863
892
|
* Executes an INSERT SQL statement using a prepared statement
|
864
893
|
* @param context
|
865
894
|
* @param sql
|
866
895
|
* @param binds RubyArray of values to be bound to the query
|
896
|
+
* @param pk Rails PK
|
867
897
|
* @return ActiveRecord::Result
|
868
898
|
* @throws SQLException
|
869
899
|
*/
|
870
|
-
@JRubyMethod(name = "
|
871
|
-
public IRubyObject
|
900
|
+
@JRubyMethod(name = "execute_insert_pk", required = 3)
|
901
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
|
902
|
+
final IRubyObject pk) {
|
872
903
|
return withConnection(context, connection -> {
|
873
904
|
PreparedStatement statement = null;
|
874
905
|
final String query = sqlString(sql);
|
875
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));
|
911
|
+
}
|
876
912
|
|
877
|
-
statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
878
913
|
setStatementParameters(context, connection, statement, (RubyArray) binds);
|
879
914
|
statement.executeUpdate();
|
880
915
|
return mapGeneratedKeys(context, connection, statement);
|
881
|
-
|
882
916
|
} catch (final SQLException e) {
|
883
917
|
debugErrorSQL(context, query);
|
884
918
|
throw e;
|
@@ -888,6 +922,12 @@ public class RubyJdbcConnection extends RubyObject {
|
|
888
922
|
});
|
889
923
|
}
|
890
924
|
|
925
|
+
@Deprecated
|
926
|
+
@JRubyMethod(name = "execute_insert", required = 2)
|
927
|
+
public IRubyObject execute_insert(final ThreadContext context, final IRubyObject binds, final IRubyObject sql) {
|
928
|
+
return execute_insert_pk(context, sql, binds, context.nil);
|
929
|
+
}
|
930
|
+
|
891
931
|
/**
|
892
932
|
* Executes an UPDATE (DELETE) SQL statement
|
893
933
|
* @param context
|
@@ -967,12 +1007,12 @@ public class RubyJdbcConnection extends RubyObject {
|
|
967
1007
|
binds = null;
|
968
1008
|
} else { // (sql, binds)
|
969
1009
|
maxRows = 0;
|
970
|
-
binds = (RubyArray) TypeConverter.checkArrayType(args[1]);
|
1010
|
+
binds = (RubyArray) TypeConverter.checkArrayType(context, args[1]);
|
971
1011
|
}
|
972
1012
|
break;
|
973
1013
|
case 3: // (sql, max_rows, binds)
|
974
1014
|
maxRows = RubyNumeric.fix2int(args[1]);
|
975
|
-
binds = (RubyArray) TypeConverter.checkArrayType(args[2]);
|
1015
|
+
binds = (RubyArray) TypeConverter.checkArrayType(context, args[2]);
|
976
1016
|
break;
|
977
1017
|
default: // (sql) 1-arg
|
978
1018
|
maxRows = 0;
|
@@ -1061,6 +1101,28 @@ public class RubyJdbcConnection extends RubyObject {
|
|
1061
1101
|
});
|
1062
1102
|
}
|
1063
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
|
+
|
1064
1126
|
/**
|
1065
1127
|
* Prepares a query, returns a wrapped PreparedStatement. This takes care of exception wrapping
|
1066
1128
|
* @param context which context this method is executing on.
|
@@ -2120,7 +2182,7 @@ public class RubyJdbcConnection extends RubyObject {
|
|
2120
2182
|
return RubyString.newString(runtime, DateTimeUtils.dateToString(value));
|
2121
2183
|
}
|
2122
2184
|
|
2123
|
-
return DateTimeUtils.newDateAsTime(context, value,
|
2185
|
+
return DateTimeUtils.newDateAsTime(context, value, DateTimeZone.UTC).callMethod(context, "to_date");
|
2124
2186
|
}
|
2125
2187
|
|
2126
2188
|
protected IRubyObject timeToRuby(final ThreadContext context,
|
@@ -2357,9 +2419,16 @@ public class RubyJdbcConnection extends RubyObject {
|
|
2357
2419
|
final Connection connection, final PreparedStatement statement,
|
2358
2420
|
final int index, IRubyObject attribute) throws SQLException {
|
2359
2421
|
|
2360
|
-
|
2361
|
-
final int type
|
2362
|
-
|
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
|
+
}
|
2363
2432
|
|
2364
2433
|
// All the set methods were calling this first so save a method call in the nil case
|
2365
2434
|
if ( value == context.nil ) {
|
@@ -2475,6 +2544,34 @@ public class RubyJdbcConnection extends RubyObject {
|
|
2475
2544
|
return Types.OTHER; // -1 as well as 0 are used in Types
|
2476
2545
|
}
|
2477
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
|
+
|
2478
2575
|
protected Integer jdbcTypeFor(final String type) {
|
2479
2576
|
return JDBC_TYPE_FOR.get(type);
|
2480
2577
|
}
|
@@ -2486,7 +2583,9 @@ public class RubyJdbcConnection extends RubyObject {
|
|
2486
2583
|
}
|
2487
2584
|
|
2488
2585
|
protected static IRubyObject attributeSQLType(final ThreadContext context, final IRubyObject attribute) {
|
2489
|
-
|
2586
|
+
final IRubyObject type = attributeType(context, attribute);
|
2587
|
+
if (type != null) return type.callMethod(context, "type");
|
2588
|
+
return context.nil;
|
2490
2589
|
}
|
2491
2590
|
|
2492
2591
|
private final CachingCallSite value_site = new FunctionalCachingCallSite("value"); // AR::Attribute#value
|
@@ -2499,23 +2598,7 @@ public class RubyJdbcConnection extends RubyObject {
|
|
2499
2598
|
|
2500
2599
|
final IRubyObject value = value_site.call(context, attribute, attribute);
|
2501
2600
|
|
2502
|
-
|
2503
|
-
return "integer";
|
2504
|
-
}
|
2505
|
-
|
2506
|
-
if (value instanceof RubyNumeric) {
|
2507
|
-
return "float";
|
2508
|
-
}
|
2509
|
-
|
2510
|
-
if (value instanceof RubyTime) {
|
2511
|
-
return "timestamp";
|
2512
|
-
}
|
2513
|
-
|
2514
|
-
if (value instanceof RubyBoolean) {
|
2515
|
-
return "boolean";
|
2516
|
-
}
|
2517
|
-
|
2518
|
-
return "string";
|
2601
|
+
return internedTypeForPrimitive(context, value);
|
2519
2602
|
}
|
2520
2603
|
|
2521
2604
|
protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final IRubyObject value) {
|
@@ -200,6 +200,57 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
200
200
|
});
|
201
201
|
}
|
202
202
|
|
203
|
+
/**
|
204
|
+
* Executes an INSERT SQL statement
|
205
|
+
* @param context
|
206
|
+
* @param sql
|
207
|
+
* @param pk Rails PK
|
208
|
+
* @return ActiveRecord::Result
|
209
|
+
* @throws SQLException
|
210
|
+
*/
|
211
|
+
@Override
|
212
|
+
@JRubyMethod(name = "execute_insert_pk", required = 2)
|
213
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
|
214
|
+
|
215
|
+
// MSSQL does not like composite primary keys here so chop it if there is more than one column
|
216
|
+
IRubyObject modifiedPk = pk;
|
217
|
+
|
218
|
+
if (pk instanceof RubyArray) {
|
219
|
+
RubyArray ary = (RubyArray) pk;
|
220
|
+
if (ary.size() > 0) {
|
221
|
+
modifiedPk = ary.eltInternal(0);
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
225
|
+
return super.execute_insert_pk(context, sql, modifiedPk);
|
226
|
+
}
|
227
|
+
|
228
|
+
/**
|
229
|
+
* Executes an INSERT SQL statement using a prepared statement
|
230
|
+
* @param context
|
231
|
+
* @param sql
|
232
|
+
* @param binds RubyArray of values to be bound to the query
|
233
|
+
* @param pk Rails PK
|
234
|
+
* @return ActiveRecord::Result
|
235
|
+
* @throws SQLException
|
236
|
+
*/
|
237
|
+
@Override
|
238
|
+
@JRubyMethod(name = "execute_insert_pk", required = 3)
|
239
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
|
240
|
+
final IRubyObject pk) {
|
241
|
+
// MSSQL does not like composite primary keys here so chop it if there is more than one column
|
242
|
+
IRubyObject modifiedPk = pk;
|
243
|
+
|
244
|
+
if (pk instanceof RubyArray) {
|
245
|
+
RubyArray ary = (RubyArray) pk;
|
246
|
+
if (ary.size() > 0) {
|
247
|
+
modifiedPk = ary.eltInternal(0);
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
return super.execute_insert_pk(context, sql, binds, modifiedPk);
|
252
|
+
}
|
253
|
+
|
203
254
|
@Override
|
204
255
|
protected Integer jdbcTypeFor(final String type) {
|
205
256
|
|
@@ -36,26 +36,18 @@ import java.io.ByteArrayInputStream;
|
|
36
36
|
import java.lang.StringBuilder;
|
37
37
|
import java.lang.reflect.InvocationTargetException;
|
38
38
|
import java.math.BigDecimal;
|
39
|
-
import java.sql
|
40
|
-
import java.sql.
|
41
|
-
import java.
|
42
|
-
import java.sql.ResultSet;
|
43
|
-
import java.sql.ResultSetMetaData;
|
44
|
-
import java.sql.SQLException;
|
45
|
-
import java.sql.Timestamp;
|
46
|
-
import java.sql.Types;
|
47
|
-
import java.util.ArrayList;
|
48
|
-
import java.util.Collections;
|
49
|
-
import java.util.HashMap;
|
50
|
-
import java.util.Map;
|
51
|
-
import java.util.UUID;
|
39
|
+
import java.sql.*;
|
40
|
+
import java.sql.Date;
|
41
|
+
import java.util.*;
|
52
42
|
import java.util.regex.Pattern;
|
53
43
|
import java.util.regex.Matcher;
|
54
44
|
|
45
|
+
import org.joda.time.DateTime;
|
55
46
|
import org.jruby.*;
|
56
47
|
import org.jruby.anno.JRubyMethod;
|
57
48
|
import org.jruby.exceptions.RaiseException;
|
58
49
|
import org.jruby.ext.bigdecimal.RubyBigDecimal;
|
50
|
+
import org.jruby.ext.date.RubyDate;
|
59
51
|
import org.jruby.javasupport.JavaUtil;
|
60
52
|
import org.jruby.runtime.ObjectAllocator;
|
61
53
|
import org.jruby.runtime.ThreadContext;
|
@@ -115,6 +107,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
115
107
|
|
116
108
|
// Used to wipe trailing 0's from points (3.0, 5.6) -> (3, 5.6)
|
117
109
|
private static final Pattern pointCleanerPattern = Pattern.compile("\\.0\\b");
|
110
|
+
private static final TimeZone TZ_DEFAULT = TimeZone.getDefault();
|
118
111
|
|
119
112
|
private RubyClass resultClass;
|
120
113
|
private RubyHash typeMap = null;
|
@@ -204,9 +197,9 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
204
197
|
RubyClass arrayClass = oidArray(context);
|
205
198
|
RubyBasicObject attributeType = (RubyBasicObject) attributeType(context, attribute);
|
206
199
|
// The type or its delegate is an OID::Array
|
207
|
-
if (arrayClass.isInstance(attributeType) ||
|
200
|
+
if (attributeType != null && (arrayClass.isInstance(attributeType) ||
|
208
201
|
(attributeType.hasInstanceVariable("@delegate_dc_obj") &&
|
209
|
-
arrayClass.isInstance(attributeType.getInstanceVariable("@delegate_dc_obj")))) {
|
202
|
+
arrayClass.isInstance(attributeType.getInstanceVariable("@delegate_dc_obj"))))) {
|
210
203
|
return "array";
|
211
204
|
}
|
212
205
|
|
@@ -386,7 +379,20 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
386
379
|
}
|
387
380
|
}
|
388
381
|
|
389
|
-
|
382
|
+
if ( ! "Date".equals(value.getMetaClass().getName()) && value.respondsTo("to_date") ) {
|
383
|
+
value = value.callMethod(context, "to_date");
|
384
|
+
}
|
385
|
+
|
386
|
+
if (value instanceof RubyDate) {
|
387
|
+
RubyDate rubyDate = (RubyDate) value;
|
388
|
+
DateTime dt = rubyDate.getDateTime();
|
389
|
+
// pgjdbc needs adjustment for default JVM timezone
|
390
|
+
statement.setDate(index, new Date(dt.getMillis() - TZ_DEFAULT.getOffset(dt.getMillis())));
|
391
|
+
return;
|
392
|
+
}
|
393
|
+
|
394
|
+
// NOTE: assuming Date#to_s does right ...
|
395
|
+
statement.setDate(index, Date.valueOf(value.toString()));
|
390
396
|
}
|
391
397
|
|
392
398
|
@Override
|
@@ -430,7 +436,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
430
436
|
break;
|
431
437
|
|
432
438
|
case "interval":
|
433
|
-
statement.setObject(index,
|
439
|
+
statement.setObject(index, stringToPGInterval(value.toString()));
|
434
440
|
break;
|
435
441
|
|
436
442
|
case "json":
|
@@ -487,6 +493,74 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
487
493
|
}
|
488
494
|
}
|
489
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
|
+
|
490
564
|
protected IRubyObject jdbcToRuby(ThreadContext context, Ruby runtime, int column, int type, ResultSet resultSet) throws SQLException {
|
491
565
|
return typeMap != null ?
|
492
566
|
convertWithTypeMap(context, runtime, column, type, resultSet) :
|
@@ -868,34 +942,25 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
868
942
|
private static String formatInterval(final Object object) {
|
869
943
|
final PGInterval interval = (PGInterval) object;
|
870
944
|
final StringBuilder str = new StringBuilder(32);
|
945
|
+
str.append("P");
|
871
946
|
|
872
947
|
final int years = interval.getYears();
|
873
|
-
if (years != 0) str.append(years).append("
|
948
|
+
if (years != 0) str.append(years).append("Y");
|
874
949
|
|
875
950
|
final int months = interval.getMonths();
|
876
|
-
if (months != 0) str.append(months).append("
|
951
|
+
if (months != 0) str.append(months).append("M");
|
877
952
|
|
878
953
|
final int days = interval.getDays();
|
879
|
-
if (days != 0) str.append(days).append("
|
954
|
+
if (days != 0) str.append(days).append("D");
|
880
955
|
|
881
956
|
final int hours = interval.getHours();
|
882
957
|
final int mins = interval.getMinutes();
|
883
|
-
final
|
884
|
-
if (hours != 0 || mins != 0 || secs != 0) {
|
885
|
-
|
886
|
-
|
887
|
-
str.append(
|
888
|
-
|
889
|
-
if (mins < 10) str.append('0');
|
890
|
-
|
891
|
-
str.append(mins).append(':');
|
892
|
-
|
893
|
-
if (secs < 10) str.append('0');
|
894
|
-
|
895
|
-
str.append(secs);
|
896
|
-
|
897
|
-
} else if (str.length() > 1) {
|
898
|
-
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");
|
899
964
|
}
|
900
965
|
|
901
966
|
return str.toString();
|