activerecord-jdbc-adapter 60.0-java → 61.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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();
|