activerecord-jdbc-adapter 51.3-java → 51.8-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 +22 -18
- data/README.md +11 -9
- data/Rakefile +30 -4
- data/activerecord-jdbc-adapter.gemspec +1 -1
- data/lib/arjdbc/abstract/core.rb +2 -2
- data/lib/arjdbc/abstract/database_statements.rb +8 -2
- data/lib/arjdbc/jdbc.rb +0 -4
- data/lib/arjdbc/jdbc/column.rb +5 -11
- data/lib/arjdbc/mysql/connection_methods.rb +13 -7
- data/lib/arjdbc/postgresql/column.rb +6 -3
- data/lib/arjdbc/postgresql/connection_methods.rb +3 -1
- data/lib/arjdbc/sqlite3/connection_methods.rb +1 -0
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/01-tomcat.rake +2 -2
- data/rakelib/02-test.rake +0 -3
- data/rakelib/rails.rake +1 -1
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +62 -8
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +310 -14
- data/src/java/arjdbc/util/DateTimeUtils.java +22 -5
- metadata +4 -16
- data/lib/active_record/connection_adapters/mssql_adapter.rb +0 -1
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -1
- data/lib/arjdbc/mssql.rb +0 -7
- data/lib/arjdbc/mssql/adapter.rb +0 -804
- data/lib/arjdbc/mssql/column.rb +0 -200
- data/lib/arjdbc/mssql/connection_methods.rb +0 -79
- data/lib/arjdbc/mssql/explain_support.rb +0 -99
- data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
- data/lib/arjdbc/mssql/lock_methods.rb +0 -77
- data/lib/arjdbc/mssql/types.rb +0 -343
- data/lib/arjdbc/mssql/utils.rb +0 -82
@@ -487,7 +487,7 @@ public class RubyJdbcConnection extends RubyObject {
|
|
487
487
|
savepoint = ((IRubyObject) savepoint).toJava(Savepoint.class);
|
488
488
|
}
|
489
489
|
|
490
|
-
|
490
|
+
releaseSavepoint(connection, (Savepoint) savepoint);
|
491
491
|
return context.nil;
|
492
492
|
}
|
493
493
|
catch (SQLException e) {
|
@@ -495,6 +495,11 @@ public class RubyJdbcConnection extends RubyObject {
|
|
495
495
|
}
|
496
496
|
}
|
497
497
|
|
498
|
+
// MSSQL doesn't support releasing savepoints so we make it possible to override the actual release action
|
499
|
+
protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
|
500
|
+
connection.releaseSavepoint(savepoint);
|
501
|
+
}
|
502
|
+
|
498
503
|
protected static RuntimeException newSavepointNotSetError(final ThreadContext context, final IRubyObject name, final String op) {
|
499
504
|
RubyClass StatementInvalid = ActiveRecord(context).getClass("StatementInvalid");
|
500
505
|
return context.runtime.newRaiseException(StatementInvalid, "could not " + op + " savepoint: '" + name + "' (not set)");
|
@@ -722,7 +727,10 @@ public class RubyJdbcConnection extends RubyObject {
|
|
722
727
|
|
723
728
|
private void connectImpl(final boolean forceConnection) throws SQLException {
|
724
729
|
setConnection( forceConnection ? newConnection() : null );
|
725
|
-
if (
|
730
|
+
if (forceConnection) {
|
731
|
+
if (getConnectionImpl() == null) throw new SQLException("Didn't get a connection. Wrong URL?");
|
732
|
+
configureConnection();
|
733
|
+
}
|
726
734
|
}
|
727
735
|
|
728
736
|
@JRubyMethod(name = "read_only?")
|
@@ -880,15 +888,31 @@ public class RubyJdbcConnection extends RubyObject {
|
|
880
888
|
return mapQueryResult(context, connection, resultSet);
|
881
889
|
}
|
882
890
|
|
891
|
+
private static String[] createStatementPk(IRubyObject pk) {
|
892
|
+
String[] statementPk;
|
893
|
+
if (pk instanceof RubyArray) {
|
894
|
+
RubyArray ary = (RubyArray) pk;
|
895
|
+
int size = ary.size();
|
896
|
+
statementPk = new String[size];
|
897
|
+
for (int i = 0; i < size; i++) {
|
898
|
+
statementPk[i] = sqlString(ary.eltInternal(i));
|
899
|
+
}
|
900
|
+
} else {
|
901
|
+
statementPk = new String[] { sqlString(pk) };
|
902
|
+
}
|
903
|
+
return statementPk;
|
904
|
+
}
|
905
|
+
|
883
906
|
/**
|
884
907
|
* Executes an INSERT SQL statement
|
885
908
|
* @param context
|
886
909
|
* @param sql
|
910
|
+
* @param pk Rails PK
|
887
911
|
* @return ActiveRecord::Result
|
888
912
|
* @throws SQLException
|
889
913
|
*/
|
890
|
-
@JRubyMethod(name = "
|
891
|
-
public IRubyObject
|
914
|
+
@JRubyMethod(name = "execute_insert_pk", required = 2)
|
915
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
|
892
916
|
return withConnection(context, new Callable<IRubyObject>() {
|
893
917
|
public IRubyObject call(final Connection connection) throws SQLException {
|
894
918
|
Statement statement = null;
|
@@ -896,7 +920,13 @@ public class RubyJdbcConnection extends RubyObject {
|
|
896
920
|
try {
|
897
921
|
|
898
922
|
statement = createStatement(context, connection);
|
899
|
-
|
923
|
+
|
924
|
+
if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
|
925
|
+
statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
|
926
|
+
} else {
|
927
|
+
statement.executeUpdate(query, createStatementPk(pk));
|
928
|
+
}
|
929
|
+
|
900
930
|
return mapGeneratedKeys(context, connection, statement);
|
901
931
|
|
902
932
|
} catch (final SQLException e) {
|
@@ -909,23 +939,35 @@ public class RubyJdbcConnection extends RubyObject {
|
|
909
939
|
});
|
910
940
|
}
|
911
941
|
|
942
|
+
@Deprecated
|
943
|
+
@JRubyMethod(name = "execute_insert", required = 1)
|
944
|
+
public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql) {
|
945
|
+
return execute_insert_pk(context, sql, context.nil);
|
946
|
+
}
|
947
|
+
|
912
948
|
/**
|
913
949
|
* Executes an INSERT SQL statement using a prepared statement
|
914
950
|
* @param context
|
915
951
|
* @param sql
|
916
952
|
* @param binds RubyArray of values to be bound to the query
|
953
|
+
* @param pk Rails PK
|
917
954
|
* @return ActiveRecord::Result
|
918
955
|
* @throws SQLException
|
919
956
|
*/
|
920
|
-
@JRubyMethod(name = "
|
921
|
-
public IRubyObject
|
957
|
+
@JRubyMethod(name = "execute_insert_pk", required = 3)
|
958
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
|
959
|
+
final IRubyObject pk) {
|
922
960
|
return withConnection(context, new Callable<IRubyObject>() {
|
923
961
|
public IRubyObject call(final Connection connection) throws SQLException {
|
924
962
|
PreparedStatement statement = null;
|
925
963
|
final String query = sqlString(sql);
|
926
964
|
try {
|
965
|
+
if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
|
966
|
+
statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
967
|
+
} else {
|
968
|
+
statement = connection.prepareStatement(query, createStatementPk(pk));
|
969
|
+
}
|
927
970
|
|
928
|
-
statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
|
929
971
|
setStatementParameters(context, connection, statement, (RubyArray) binds);
|
930
972
|
statement.executeUpdate();
|
931
973
|
return mapGeneratedKeys(context, connection, statement);
|
@@ -940,6 +982,12 @@ public class RubyJdbcConnection extends RubyObject {
|
|
940
982
|
});
|
941
983
|
}
|
942
984
|
|
985
|
+
@Deprecated
|
986
|
+
@JRubyMethod(name = "execute_insert", required = 2)
|
987
|
+
public IRubyObject execute_insert(final ThreadContext context, final IRubyObject binds, final IRubyObject sql) {
|
988
|
+
return execute_insert_pk(context, sql, binds, context.nil);
|
989
|
+
}
|
990
|
+
|
943
991
|
/**
|
944
992
|
* Executes an UPDATE (DELETE) SQL statement
|
945
993
|
* @param context
|
@@ -3917,6 +3965,12 @@ public class RubyJdbcConnection extends RubyObject {
|
|
3917
3965
|
}
|
3918
3966
|
}
|
3919
3967
|
|
3968
|
+
public static void debugMessage(final ThreadContext context, final IRubyObject obj) {
|
3969
|
+
if ( isDebug(context.runtime) ) {
|
3970
|
+
debugMessage(context.runtime, obj.callMethod(context, "inspect"));
|
3971
|
+
}
|
3972
|
+
}
|
3973
|
+
|
3920
3974
|
public static void debugMessage(final Ruby runtime, final String msg, final Object e) {
|
3921
3975
|
if ( isDebug(runtime) ) {
|
3922
3976
|
final PrintStream out = runtime != null ? runtime.getOut() : System.out;
|
@@ -28,17 +28,32 @@ package arjdbc.mssql;
|
|
28
28
|
import arjdbc.jdbc.Callable;
|
29
29
|
import arjdbc.jdbc.RubyJdbcConnection;
|
30
30
|
|
31
|
+
import java.lang.reflect.InvocationTargetException;
|
32
|
+
import java.lang.reflect.Method;
|
31
33
|
import java.sql.Connection;
|
32
34
|
import java.sql.DatabaseMetaData;
|
35
|
+
import java.sql.Date;
|
36
|
+
import java.sql.PreparedStatement;
|
33
37
|
import java.sql.ResultSet;
|
38
|
+
import java.sql.Savepoint;
|
39
|
+
import java.sql.Statement;
|
34
40
|
import java.sql.SQLException;
|
41
|
+
import java.sql.Timestamp;
|
35
42
|
import java.sql.Types;
|
43
|
+
import java.util.ArrayList;
|
44
|
+
import java.util.HashMap;
|
45
|
+
import java.util.List;
|
46
|
+
import java.util.Map;
|
36
47
|
|
48
|
+
import arjdbc.util.DateTimeUtils;
|
49
|
+
import org.joda.time.DateTime;
|
50
|
+
import org.joda.time.DateTimeZone;
|
37
51
|
import org.jruby.Ruby;
|
38
52
|
import org.jruby.RubyArray;
|
39
53
|
import org.jruby.RubyBoolean;
|
40
54
|
import org.jruby.RubyClass;
|
41
55
|
import org.jruby.RubyString;
|
56
|
+
import org.jruby.RubyTime;
|
42
57
|
import org.jruby.anno.JRubyMethod;
|
43
58
|
import org.jruby.runtime.ObjectAllocator;
|
44
59
|
import org.jruby.runtime.ThreadContext;
|
@@ -52,6 +67,53 @@ import org.jruby.util.ByteList;
|
|
52
67
|
public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
53
68
|
private static final long serialVersionUID = -745716565005219263L;
|
54
69
|
|
70
|
+
private static final int DATETIMEOFFSET_TYPE;
|
71
|
+
private static final Method DateTimeOffsetGetMinutesOffsetMethod;
|
72
|
+
private static final Method DateTimeOffsetGetTimestampMethod;
|
73
|
+
private static final Method DateTimeOffsetValueOfMethod;
|
74
|
+
private static final Method PreparedStatementSetDateTimeOffsetMethod;
|
75
|
+
|
76
|
+
private static final Map<String, Integer> MSSQL_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
|
77
|
+
static {
|
78
|
+
|
79
|
+
Class<?> DateTimeOffset;
|
80
|
+
Class<?> MssqlPreparedStatement;
|
81
|
+
Class<?> MssqlTypes;
|
82
|
+
try {
|
83
|
+
DateTimeOffset = Class.forName("microsoft.sql.DateTimeOffset");
|
84
|
+
MssqlPreparedStatement = Class.forName("com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement");
|
85
|
+
MssqlTypes = Class.forName("microsoft.sql.Types");
|
86
|
+
} catch (ClassNotFoundException e) {
|
87
|
+
System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
|
88
|
+
throw new RuntimeException("You must require the Microsoft JDBC driver to use this gem");
|
89
|
+
}
|
90
|
+
|
91
|
+
try {
|
92
|
+
DATETIMEOFFSET_TYPE = MssqlTypes.getField("DATETIMEOFFSET").getInt(null);
|
93
|
+
DateTimeOffsetGetMinutesOffsetMethod = DateTimeOffset.getDeclaredMethod("getMinutesOffset");
|
94
|
+
DateTimeOffsetGetTimestampMethod = DateTimeOffset.getDeclaredMethod("getTimestamp");
|
95
|
+
|
96
|
+
Class<?>[] valueOfArgTypes = { Timestamp.class, int.class };
|
97
|
+
DateTimeOffsetValueOfMethod = DateTimeOffset.getDeclaredMethod("valueOf", valueOfArgTypes);
|
98
|
+
|
99
|
+
Class<?>[] setOffsetArgTypes = { int.class, DateTimeOffset };
|
100
|
+
PreparedStatementSetDateTimeOffsetMethod = MssqlPreparedStatement.getDeclaredMethod("setDateTimeOffset", setOffsetArgTypes);
|
101
|
+
} catch (Exception e) {
|
102
|
+
System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
|
103
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
104
|
+
}
|
105
|
+
|
106
|
+
MSSQL_JDBC_TYPE_FOR.put("binary_basic", Types.BINARY);
|
107
|
+
MSSQL_JDBC_TYPE_FOR.put("datetimeoffset", DATETIMEOFFSET_TYPE);
|
108
|
+
MSSQL_JDBC_TYPE_FOR.put("money", Types.DECIMAL);
|
109
|
+
MSSQL_JDBC_TYPE_FOR.put("smalldatetime", Types.TIMESTAMP);
|
110
|
+
MSSQL_JDBC_TYPE_FOR.put("smallmoney", Types.DECIMAL);
|
111
|
+
MSSQL_JDBC_TYPE_FOR.put("ss_timestamp", Types.BINARY);
|
112
|
+
MSSQL_JDBC_TYPE_FOR.put("text_basic", Types.LONGVARCHAR);
|
113
|
+
MSSQL_JDBC_TYPE_FOR.put("uuid", Types.CHAR);
|
114
|
+
MSSQL_JDBC_TYPE_FOR.put("varchar_max", Types.VARCHAR);
|
115
|
+
}
|
116
|
+
|
55
117
|
public MSSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
|
56
118
|
super(runtime, metaClass);
|
57
119
|
}
|
@@ -83,6 +145,187 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
83
145
|
return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
|
84
146
|
}
|
85
147
|
|
148
|
+
// Support multiple result sets for mssql
|
149
|
+
@Override
|
150
|
+
@JRubyMethod(name = "execute", required = 1)
|
151
|
+
public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
|
152
|
+
final String query = sqlString(sql);
|
153
|
+
return withConnection(context, new Callable<IRubyObject>() {
|
154
|
+
public IRubyObject call(final Connection connection) throws SQLException {
|
155
|
+
Statement statement = null;
|
156
|
+
try {
|
157
|
+
statement = createStatement(context, connection);
|
158
|
+
|
159
|
+
// For DBs that do support multiple statements, lets return the last result set
|
160
|
+
// to be consistent with AR
|
161
|
+
boolean hasResultSet = doExecute(statement, query);
|
162
|
+
int updateCount = statement.getUpdateCount();
|
163
|
+
|
164
|
+
final List<IRubyObject> results = new ArrayList<IRubyObject>();
|
165
|
+
ResultSet resultSet;
|
166
|
+
|
167
|
+
while (hasResultSet || updateCount != -1) {
|
168
|
+
|
169
|
+
if (hasResultSet) {
|
170
|
+
resultSet = statement.getResultSet();
|
171
|
+
|
172
|
+
// Unfortunately the result set gets closed when getMoreResults()
|
173
|
+
// is called, so we have to process the result sets as we get them
|
174
|
+
// this shouldn't be an issue in most cases since we're only getting 1 result set anyways
|
175
|
+
results.add(mapExecuteResult(context, connection, resultSet));
|
176
|
+
} else {
|
177
|
+
results.add(context.runtime.newFixnum(updateCount));
|
178
|
+
}
|
179
|
+
|
180
|
+
// Check to see if there is another result set
|
181
|
+
hasResultSet = statement.getMoreResults();
|
182
|
+
updateCount = statement.getUpdateCount();
|
183
|
+
}
|
184
|
+
|
185
|
+
if (results.size() == 0) {
|
186
|
+
return context.nil; // If no results, return nil
|
187
|
+
} else if (results.size() == 1) {
|
188
|
+
return results.get(0);
|
189
|
+
} else {
|
190
|
+
return context.runtime.newArray(results);
|
191
|
+
}
|
192
|
+
|
193
|
+
} catch (final SQLException e) {
|
194
|
+
debugErrorSQL(context, query);
|
195
|
+
throw e;
|
196
|
+
} finally {
|
197
|
+
close(statement);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
});
|
201
|
+
}
|
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
|
+
|
254
|
+
@Override
|
255
|
+
protected Integer jdbcTypeFor(final String type) {
|
256
|
+
|
257
|
+
Integer typeValue = MSSQL_JDBC_TYPE_FOR.get(type);
|
258
|
+
|
259
|
+
if ( typeValue != null ) {
|
260
|
+
return typeValue;
|
261
|
+
}
|
262
|
+
|
263
|
+
return super.jdbcTypeFor(type);
|
264
|
+
}
|
265
|
+
|
266
|
+
// Datetimeoffset values also make it into here
|
267
|
+
@Override
|
268
|
+
protected void setStringParameter(final ThreadContext context, final Connection connection,
|
269
|
+
final PreparedStatement statement, final int index, final IRubyObject value,
|
270
|
+
final IRubyObject attribute, final int type) throws SQLException {
|
271
|
+
|
272
|
+
// datetimeoffset values also make it in here
|
273
|
+
if (type == DATETIMEOFFSET_TYPE) {
|
274
|
+
|
275
|
+
Object dto = convertToDateTimeOffset(context, value);
|
276
|
+
|
277
|
+
try {
|
278
|
+
|
279
|
+
Object[] setStatementArgs = { index, dto };
|
280
|
+
PreparedStatementSetDateTimeOffsetMethod.invoke(statement, setStatementArgs);
|
281
|
+
|
282
|
+
} catch (IllegalAccessException e) {
|
283
|
+
debugMessage(context.runtime, e.getMessage());
|
284
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
285
|
+
} catch (InvocationTargetException e) {
|
286
|
+
debugMessage(context.runtime, e.getMessage());
|
287
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
288
|
+
}
|
289
|
+
|
290
|
+
return;
|
291
|
+
}
|
292
|
+
super.setStringParameter(context, connection, statement, index, value, attribute, type);
|
293
|
+
}
|
294
|
+
|
295
|
+
// We need higher precision than the default for Time objects (which is milliseconds) so we use a DateTimeOffset object
|
296
|
+
@Override
|
297
|
+
protected void setTimeParameter(final ThreadContext context,
|
298
|
+
final Connection connection, final PreparedStatement statement,
|
299
|
+
final int index, IRubyObject value,
|
300
|
+
final IRubyObject attribute, final int type) throws SQLException {
|
301
|
+
|
302
|
+
statement.setObject(index, convertToDateTimeOffset(context, value), Types.TIME);
|
303
|
+
|
304
|
+
}
|
305
|
+
|
306
|
+
private Object convertToDateTimeOffset(final ThreadContext context, final IRubyObject value) {
|
307
|
+
|
308
|
+
RubyTime time = (RubyTime) value;
|
309
|
+
DateTime dt = time.getDateTime();
|
310
|
+
Timestamp timestamp = new Timestamp(dt.getMillis());
|
311
|
+
timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
|
312
|
+
int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
|
313
|
+
|
314
|
+
try {
|
315
|
+
|
316
|
+
Object[] dtoArgs = { timestamp, offsetMinutes };
|
317
|
+
return DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
|
318
|
+
|
319
|
+
} catch (IllegalAccessException e) {
|
320
|
+
debugMessage(context.runtime, e.getMessage());
|
321
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
322
|
+
} catch (InvocationTargetException e) {
|
323
|
+
debugMessage(context.runtime, e.getMessage());
|
324
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
325
|
+
}
|
326
|
+
}
|
327
|
+
|
328
|
+
|
86
329
|
@Override
|
87
330
|
protected RubyArray mapTables(final ThreadContext context, final Connection connection,
|
88
331
|
final String catalog, final String schemaPattern, final String tablePattern,
|
@@ -124,16 +367,79 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
124
367
|
|
125
368
|
/**
|
126
369
|
* Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
|
370
|
+
* Also handle datetimeoffset values here
|
127
371
|
*/
|
128
372
|
@Override
|
129
373
|
protected IRubyObject jdbcToRuby(
|
130
374
|
final ThreadContext context, final Ruby runtime,
|
131
375
|
final int column, int type, final ResultSet resultSet)
|
132
376
|
throws SQLException {
|
133
|
-
|
377
|
+
|
378
|
+
if (type == DATETIMEOFFSET_TYPE) {
|
379
|
+
|
380
|
+
Object dto = resultSet.getObject(column); // Returns a microsoft.sql.DateTimeOffset
|
381
|
+
|
382
|
+
if (dto == null) return context.nil;
|
383
|
+
|
384
|
+
try {
|
385
|
+
|
386
|
+
int minutes = (int) DateTimeOffsetGetMinutesOffsetMethod.invoke(dto);
|
387
|
+
DateTimeZone zone = DateTimeZone.forOffsetHoursMinutes(minutes / 60, minutes % 60);
|
388
|
+
Timestamp ts = (Timestamp) DateTimeOffsetGetTimestampMethod.invoke(dto);
|
389
|
+
|
390
|
+
int nanos = ts.getNanos(); // max 999-999-999
|
391
|
+
nanos = nanos % 1000000;
|
392
|
+
|
393
|
+
// We have to do this differently than the newTime helper because the Timestamp loses its zone information when passed around
|
394
|
+
DateTime dateTime = new DateTime(ts.getTime(), zone);
|
395
|
+
return RubyTime.newTime(context.runtime, dateTime, nanos);
|
396
|
+
|
397
|
+
} catch (IllegalAccessException e) {
|
398
|
+
debugMessage(runtime, e.getMessage());
|
399
|
+
return context.nil;
|
400
|
+
} catch (InvocationTargetException e) {
|
401
|
+
debugMessage(runtime, e.getMessage());
|
402
|
+
return context.nil;
|
403
|
+
}
|
404
|
+
}
|
405
|
+
|
406
|
+
if (type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR) type = Types.CLOB;
|
134
407
|
return super.jdbcToRuby(context, runtime, column, type, resultSet);
|
135
408
|
}
|
136
409
|
|
410
|
+
/**
|
411
|
+
* Converts a JDBC date object to a Ruby date by referencing Date#civil
|
412
|
+
* @param context current thread context
|
413
|
+
* @param resultSet the jdbc result set to pull the value from
|
414
|
+
* @param index the index of the column to convert
|
415
|
+
* @return RubyNil if NULL or RubyDate if there is a value
|
416
|
+
* @throws SQLException if it fails to retrieve the value from the result set
|
417
|
+
*/
|
418
|
+
@Override
|
419
|
+
protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
|
420
|
+
|
421
|
+
final Date value = resultSet.getDate(index);
|
422
|
+
|
423
|
+
if (value == null) return context.nil;
|
424
|
+
|
425
|
+
return DateTimeUtils.newDate(context, value);
|
426
|
+
}
|
427
|
+
|
428
|
+
/**
|
429
|
+
* Converts a JDBC time to a Ruby time. We use timestamp because java.sql.Time doesn't support sub-millisecond values
|
430
|
+
* @param context current thread context
|
431
|
+
* @param resultSet the jdbc result set to pull the value from
|
432
|
+
* @param index the index of the column to convert
|
433
|
+
* @return RubyNil if NULL or RubyTime if there is a value
|
434
|
+
* @throws SQLException if it fails to retrieve the value from the result set
|
435
|
+
*/
|
436
|
+
@Override
|
437
|
+
protected IRubyObject timeToRuby(final ThreadContext context,final Ruby runtime,
|
438
|
+
final ResultSet resultSet, final int column) throws SQLException {
|
439
|
+
|
440
|
+
return timestampToRuby(context, runtime, resultSet, column);
|
441
|
+
}
|
442
|
+
|
137
443
|
@Override
|
138
444
|
protected ColumnData[] extractColumns(final ThreadContext context,
|
139
445
|
final Connection connection, final ResultSet resultSet,
|
@@ -163,19 +469,9 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
163
469
|
return columns;
|
164
470
|
}
|
165
471
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
// "jTDS Type 4 JDBC Driver for MS SQL Server and Sybase"
|
170
|
-
// SQLJDBC: "Microsoft JDBC Driver 4.0 for SQL Server"
|
171
|
-
return withConnection(context, new Callable<RubyBoolean>() {
|
172
|
-
// NOTE: only used in one place for now (on release_savepoint) ...
|
173
|
-
// might get optimized to only happen once since driver won't change
|
174
|
-
public RubyBoolean call(final Connection connection) throws SQLException {
|
175
|
-
final String driver = connection.getMetaData().getDriverName();
|
176
|
-
return context.getRuntime().newBoolean( driver.indexOf("jTDS") >= 0 );
|
177
|
-
}
|
178
|
-
});
|
472
|
+
@Override
|
473
|
+
protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
|
474
|
+
// MSSQL doesn't support releasing savepoints
|
179
475
|
}
|
180
476
|
|
181
477
|
}
|