activerecord-jdbc-alt-adapter 52.2.3-java → 60.1.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 +3 -0
- data/.travis.yml +80 -52
- data/Gemfile +10 -3
- data/README.md +55 -37
- data/Rakefile +31 -5
- data/Rakefile.jdbc +8 -1
- data/activerecord-jdbc-adapter.gemspec +6 -9
- data/activerecord-jdbc-alt-adapter.gemspec +6 -9
- data/lib/arel/visitors/sqlserver.rb +33 -23
- data/lib/arjdbc/abstract/connection_management.rb +7 -0
- data/lib/arjdbc/abstract/core.rb +17 -24
- data/lib/arjdbc/abstract/database_statements.rb +31 -20
- data/lib/arjdbc/abstract/statement_cache.rb +2 -5
- data/lib/arjdbc/abstract/transaction_support.rb +5 -3
- data/lib/arjdbc/db2/column.rb +0 -39
- data/lib/arjdbc/derby/adapter.rb +1 -20
- data/lib/arjdbc/firebird/adapter.rb +0 -21
- data/lib/arjdbc/h2/adapter.rb +0 -15
- data/lib/arjdbc/hsqldb/adapter.rb +0 -14
- data/lib/arjdbc/informix/adapter.rb +0 -23
- data/lib/arjdbc/jdbc.rb +0 -4
- data/lib/arjdbc/jdbc/adapter.rb +3 -1
- data/lib/arjdbc/jdbc/adapter_require.rb +3 -1
- data/lib/arjdbc/jdbc/base_ext.rb +3 -1
- data/lib/arjdbc/jdbc/callbacks.rb +2 -0
- data/lib/arjdbc/jdbc/column.rb +3 -5
- data/lib/arjdbc/jdbc/connection.rb +2 -0
- data/lib/arjdbc/jdbc/connection_methods.rb +2 -0
- data/lib/arjdbc/jdbc/error.rb +2 -0
- data/lib/arjdbc/jdbc/extension.rb +2 -0
- data/lib/arjdbc/jdbc/java.rb +3 -1
- data/lib/arjdbc/jdbc/railtie.rb +3 -1
- data/lib/arjdbc/jdbc/rake_tasks.rb +3 -1
- data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -1
- data/lib/arjdbc/jdbc/type_cast.rb +2 -0
- data/lib/arjdbc/jdbc/type_converter.rb +2 -0
- data/lib/arjdbc/mssql.rb +3 -1
- data/lib/arjdbc/mssql/adapter.rb +112 -46
- data/lib/arjdbc/mssql/column.rb +5 -1
- data/lib/arjdbc/mssql/connection_methods.rb +13 -2
- data/lib/arjdbc/mssql/database_limits.rb +2 -0
- data/lib/arjdbc/mssql/database_statements.rb +44 -6
- data/lib/arjdbc/mssql/errors.rb +2 -0
- data/lib/arjdbc/mssql/explain_support.rb +3 -1
- data/lib/arjdbc/mssql/extensions/attribute_methods.rb +5 -1
- data/lib/arjdbc/mssql/extensions/calculations.rb +2 -0
- data/lib/arjdbc/mssql/quoting.rb +38 -0
- data/lib/arjdbc/mssql/schema_creation.rb +24 -2
- data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
- data/lib/arjdbc/mssql/schema_dumper.rb +2 -0
- data/lib/arjdbc/mssql/schema_statements.rb +63 -21
- data/lib/arjdbc/mssql/transaction.rb +2 -0
- data/lib/arjdbc/mssql/types.rb +2 -0
- data/lib/arjdbc/mssql/types/binary_types.rb +2 -0
- data/lib/arjdbc/mssql/types/date_and_time_types.rb +2 -0
- data/lib/arjdbc/mssql/types/deprecated_types.rb +2 -0
- data/lib/arjdbc/mssql/types/numeric_types.rb +2 -0
- data/lib/arjdbc/mssql/types/string_types.rb +2 -0
- data/lib/arjdbc/mssql/utils.rb +2 -0
- data/lib/arjdbc/mysql/adapter.rb +47 -18
- data/lib/arjdbc/mysql/connection_methods.rb +13 -7
- data/lib/arjdbc/postgresql/adapter.rb +240 -214
- data/lib/arjdbc/postgresql/base/array_decoder.rb +2 -0
- data/lib/arjdbc/postgresql/base/array_encoder.rb +4 -2
- data/lib/arjdbc/postgresql/base/array_parser.rb +4 -2
- data/lib/arjdbc/postgresql/base/pgconn.rb +2 -0
- data/lib/arjdbc/postgresql/column.rb +11 -6
- data/lib/arjdbc/postgresql/connection_methods.rb +3 -1
- data/lib/arjdbc/postgresql/name.rb +2 -0
- data/lib/arjdbc/postgresql/oid_types.rb +3 -1
- data/lib/arjdbc/sqlite3/adapter.rb +188 -180
- data/lib/arjdbc/sqlite3/connection_methods.rb +16 -4
- data/lib/arjdbc/tasks/databases.rake +13 -10
- data/lib/arjdbc/tasks/mssql_database_tasks.rb +49 -5
- data/lib/arjdbc/util/quoted_cache.rb +3 -1
- data/lib/arjdbc/util/serialized_attributes.rb +3 -1
- data/lib/arjdbc/util/table_copier.rb +3 -1
- data/lib/arjdbc/version.rb +1 -1
- data/pom.xml +4 -4
- data/rakelib/01-tomcat.rake +2 -2
- data/rakelib/02-test.rake +0 -2
- data/rakelib/rails.rake +1 -1
- data/src/java/arjdbc/ArJdbcModule.java +5 -5
- data/src/java/arjdbc/jdbc/DriverWrapper.java +1 -9
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +468 -637
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +319 -38
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +13 -23
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +44 -31
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +94 -99
- data/src/java/arjdbc/util/DateTimeUtils.java +34 -12
- metadata +7 -17
@@ -29,23 +29,35 @@ import arjdbc.jdbc.Callable;
|
|
29
29
|
import arjdbc.jdbc.RubyJdbcConnection;
|
30
30
|
import arjdbc.util.DateTimeUtils;
|
31
31
|
|
32
|
+
import java.lang.reflect.InvocationTargetException;
|
33
|
+
import java.lang.reflect.Method;
|
32
34
|
import java.sql.Connection;
|
33
35
|
import java.sql.DatabaseMetaData;
|
36
|
+
import java.sql.Date;
|
34
37
|
import java.sql.PreparedStatement;
|
35
38
|
import java.sql.ResultSet;
|
36
39
|
import java.sql.Savepoint;
|
40
|
+
import java.sql.Statement;
|
37
41
|
import java.sql.SQLException;
|
38
|
-
import java.sql.Types;
|
39
42
|
import java.sql.Timestamp;
|
43
|
+
import java.sql.Types;
|
40
44
|
import java.util.Locale;
|
45
|
+
import java.util.ArrayList;
|
46
|
+
import java.util.HashMap;
|
47
|
+
import java.util.List;
|
48
|
+
import java.util.Map;
|
41
49
|
|
50
|
+
import org.joda.time.DateTime;
|
51
|
+
import org.joda.time.DateTimeZone;
|
42
52
|
import org.jruby.Ruby;
|
43
53
|
import org.jruby.RubyArray;
|
44
54
|
import org.jruby.RubyBoolean;
|
45
55
|
import org.jruby.RubyClass;
|
46
56
|
import org.jruby.RubyString;
|
47
57
|
import org.jruby.RubySymbol;
|
58
|
+
import org.jruby.RubyTime;
|
48
59
|
import org.jruby.anno.JRubyMethod;
|
60
|
+
import org.jruby.runtime.Block;
|
49
61
|
import org.jruby.runtime.ObjectAllocator;
|
50
62
|
import org.jruby.runtime.ThreadContext;
|
51
63
|
import org.jruby.runtime.builtin.IRubyObject;
|
@@ -58,6 +70,54 @@ import org.jruby.util.ByteList;
|
|
58
70
|
public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
59
71
|
private static final long serialVersionUID = -745716565005219263L;
|
60
72
|
|
73
|
+
private static final int DATETIMEOFFSET_TYPE;
|
74
|
+
private static final Method DateTimeOffsetGetMinutesOffsetMethod;
|
75
|
+
private static final Method DateTimeOffsetGetTimestampMethod;
|
76
|
+
private static final Method DateTimeOffsetValueOfMethod;
|
77
|
+
private static final Method PreparedStatementSetDateTimeOffsetMethod;
|
78
|
+
|
79
|
+
private static final Map<String, Integer> MSSQL_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
|
80
|
+
static {
|
81
|
+
|
82
|
+
Class<?> DateTimeOffset;
|
83
|
+
Class<?> MssqlPreparedStatement;
|
84
|
+
Class<?> MssqlTypes;
|
85
|
+
try {
|
86
|
+
DateTimeOffset = Class.forName("microsoft.sql.DateTimeOffset");
|
87
|
+
MssqlPreparedStatement = Class.forName("com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement");
|
88
|
+
MssqlTypes = Class.forName("microsoft.sql.Types");
|
89
|
+
} catch (ClassNotFoundException e) {
|
90
|
+
System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
|
91
|
+
throw new RuntimeException("You must require the Microsoft JDBC driver to use this gem");
|
92
|
+
}
|
93
|
+
|
94
|
+
try {
|
95
|
+
DATETIMEOFFSET_TYPE = MssqlTypes.getField("DATETIMEOFFSET").getInt(null);
|
96
|
+
DateTimeOffsetGetMinutesOffsetMethod = DateTimeOffset.getDeclaredMethod("getMinutesOffset");
|
97
|
+
DateTimeOffsetGetTimestampMethod = DateTimeOffset.getDeclaredMethod("getTimestamp");
|
98
|
+
|
99
|
+
Class<?>[] valueOfArgTypes = { Timestamp.class, int.class };
|
100
|
+
DateTimeOffsetValueOfMethod = DateTimeOffset.getDeclaredMethod("valueOf", valueOfArgTypes);
|
101
|
+
|
102
|
+
Class<?>[] setOffsetArgTypes = { int.class, DateTimeOffset };
|
103
|
+
PreparedStatementSetDateTimeOffsetMethod = MssqlPreparedStatement.getDeclaredMethod("setDateTimeOffset", setOffsetArgTypes);
|
104
|
+
} catch (Exception e) {
|
105
|
+
System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
|
106
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
107
|
+
}
|
108
|
+
|
109
|
+
MSSQL_JDBC_TYPE_FOR.put("binary_basic", Types.BINARY);
|
110
|
+
MSSQL_JDBC_TYPE_FOR.put("image", Types.BINARY);
|
111
|
+
MSSQL_JDBC_TYPE_FOR.put("datetimeoffset", DATETIMEOFFSET_TYPE);
|
112
|
+
MSSQL_JDBC_TYPE_FOR.put("money", Types.DECIMAL);
|
113
|
+
MSSQL_JDBC_TYPE_FOR.put("smalldatetime", Types.TIMESTAMP);
|
114
|
+
MSSQL_JDBC_TYPE_FOR.put("smallmoney", Types.DECIMAL);
|
115
|
+
MSSQL_JDBC_TYPE_FOR.put("ss_timestamp", Types.BINARY);
|
116
|
+
MSSQL_JDBC_TYPE_FOR.put("text_basic", Types.LONGVARCHAR);
|
117
|
+
MSSQL_JDBC_TYPE_FOR.put("uuid", Types.CHAR);
|
118
|
+
MSSQL_JDBC_TYPE_FOR.put("varchar_max", Types.VARCHAR);
|
119
|
+
}
|
120
|
+
|
61
121
|
public MSSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
|
62
122
|
super(runtime, metaClass);
|
63
123
|
}
|
@@ -89,6 +149,176 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
89
149
|
return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
|
90
150
|
}
|
91
151
|
|
152
|
+
// Support multiple result sets for mssql
|
153
|
+
@Override
|
154
|
+
@JRubyMethod(name = "execute", required = 1)
|
155
|
+
public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
|
156
|
+
final String query = sqlString(sql);
|
157
|
+
return withConnection(context, new Callable<IRubyObject>() {
|
158
|
+
public IRubyObject call(final Connection connection) throws SQLException {
|
159
|
+
Statement statement = null;
|
160
|
+
try {
|
161
|
+
statement = createStatement(context, connection);
|
162
|
+
|
163
|
+
// For DBs that do support multiple statements, lets return the last result set
|
164
|
+
// to be consistent with AR
|
165
|
+
boolean hasResultSet = doExecute(statement, query);
|
166
|
+
int updateCount = statement.getUpdateCount();
|
167
|
+
|
168
|
+
final List<IRubyObject> results = new ArrayList<IRubyObject>();
|
169
|
+
ResultSet resultSet;
|
170
|
+
|
171
|
+
while (hasResultSet || updateCount != -1) {
|
172
|
+
|
173
|
+
if (hasResultSet) {
|
174
|
+
resultSet = statement.getResultSet();
|
175
|
+
|
176
|
+
// Unfortunately the result set gets closed when getMoreResults()
|
177
|
+
// is called, so we have to process the result sets as we get them
|
178
|
+
// this shouldn't be an issue in most cases since we're only getting 1 result set anyways
|
179
|
+
results.add(mapExecuteResult(context, connection, resultSet));
|
180
|
+
} else {
|
181
|
+
results.add(context.runtime.newFixnum(updateCount));
|
182
|
+
}
|
183
|
+
|
184
|
+
// Check to see if there is another result set
|
185
|
+
hasResultSet = statement.getMoreResults();
|
186
|
+
updateCount = statement.getUpdateCount();
|
187
|
+
}
|
188
|
+
|
189
|
+
if (results.size() == 0) {
|
190
|
+
return context.nil; // If no results, return nil
|
191
|
+
} else if (results.size() == 1) {
|
192
|
+
return results.get(0);
|
193
|
+
} else {
|
194
|
+
return context.runtime.newArray(results);
|
195
|
+
}
|
196
|
+
|
197
|
+
} catch (final SQLException e) {
|
198
|
+
debugErrorSQL(context, query);
|
199
|
+
throw e;
|
200
|
+
} finally {
|
201
|
+
close(statement);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
});
|
205
|
+
}
|
206
|
+
|
207
|
+
/**
|
208
|
+
* Executes an INSERT SQL statement
|
209
|
+
* @param context
|
210
|
+
* @param sql
|
211
|
+
* @param pk Rails PK
|
212
|
+
* @return ActiveRecord::Result
|
213
|
+
* @throws SQLException
|
214
|
+
*/
|
215
|
+
@Override
|
216
|
+
@JRubyMethod(name = "execute_insert_pk", required = 2)
|
217
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
|
218
|
+
|
219
|
+
// MSSQL does not like composite primary keys here so chop it if there is more than one column
|
220
|
+
IRubyObject modifiedPk = pk;
|
221
|
+
|
222
|
+
if (pk instanceof RubyArray) {
|
223
|
+
RubyArray ary = (RubyArray) pk;
|
224
|
+
if (ary.size() > 0) {
|
225
|
+
modifiedPk = ary.eltInternal(0);
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
return super.execute_insert_pk(context, sql, modifiedPk);
|
230
|
+
}
|
231
|
+
|
232
|
+
/**
|
233
|
+
* Executes an INSERT SQL statement using a prepared statement
|
234
|
+
* @param context
|
235
|
+
* @param sql
|
236
|
+
* @param binds RubyArray of values to be bound to the query
|
237
|
+
* @param pk Rails PK
|
238
|
+
* @return ActiveRecord::Result
|
239
|
+
* @throws SQLException
|
240
|
+
*/
|
241
|
+
@Override
|
242
|
+
@JRubyMethod(name = "execute_insert_pk", required = 3)
|
243
|
+
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
|
244
|
+
final IRubyObject pk) {
|
245
|
+
// MSSQL does not like composite primary keys here so chop it if there is more than one column
|
246
|
+
IRubyObject modifiedPk = pk;
|
247
|
+
|
248
|
+
if (pk instanceof RubyArray) {
|
249
|
+
RubyArray ary = (RubyArray) pk;
|
250
|
+
if (ary.size() > 0) {
|
251
|
+
modifiedPk = ary.eltInternal(0);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
return super.execute_insert_pk(context, sql, binds, modifiedPk);
|
256
|
+
}
|
257
|
+
|
258
|
+
@Override
|
259
|
+
protected Integer jdbcTypeFor(final String type) {
|
260
|
+
|
261
|
+
Integer typeValue = MSSQL_JDBC_TYPE_FOR.get(type);
|
262
|
+
|
263
|
+
if ( typeValue != null ) {
|
264
|
+
return typeValue;
|
265
|
+
}
|
266
|
+
|
267
|
+
return super.jdbcTypeFor(type);
|
268
|
+
}
|
269
|
+
|
270
|
+
// Datetimeoffset values also make it into here
|
271
|
+
@Override
|
272
|
+
protected void setStringParameter(final ThreadContext context, final Connection connection,
|
273
|
+
final PreparedStatement statement, final int index, final IRubyObject value,
|
274
|
+
final IRubyObject attribute, final int type) throws SQLException {
|
275
|
+
|
276
|
+
// datetimeoffset values also make it in here
|
277
|
+
if (type == DATETIMEOFFSET_TYPE) {
|
278
|
+
|
279
|
+
Object dto = convertToDateTimeOffset(context, value);
|
280
|
+
|
281
|
+
try {
|
282
|
+
|
283
|
+
Object[] setStatementArgs = { index, dto };
|
284
|
+
PreparedStatementSetDateTimeOffsetMethod.invoke(statement, setStatementArgs);
|
285
|
+
|
286
|
+
} catch (IllegalAccessException e) {
|
287
|
+
debugMessage(context.runtime, e.getMessage());
|
288
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
289
|
+
} catch (InvocationTargetException e) {
|
290
|
+
debugMessage(context.runtime, e.getMessage());
|
291
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
292
|
+
}
|
293
|
+
|
294
|
+
return;
|
295
|
+
}
|
296
|
+
super.setStringParameter(context, connection, statement, index, value, attribute, type);
|
297
|
+
}
|
298
|
+
|
299
|
+
private Object convertToDateTimeOffset(final ThreadContext context, final IRubyObject value) {
|
300
|
+
|
301
|
+
RubyTime time = (RubyTime) value;
|
302
|
+
DateTime dt = time.getDateTime();
|
303
|
+
Timestamp timestamp = new Timestamp(dt.getMillis());
|
304
|
+
timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
|
305
|
+
int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
|
306
|
+
|
307
|
+
try {
|
308
|
+
|
309
|
+
Object[] dtoArgs = { timestamp, offsetMinutes };
|
310
|
+
return DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
|
311
|
+
|
312
|
+
} catch (IllegalAccessException e) {
|
313
|
+
debugMessage(context.runtime, e.getMessage());
|
314
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
315
|
+
} catch (InvocationTargetException e) {
|
316
|
+
debugMessage(context.runtime, e.getMessage());
|
317
|
+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
|
92
322
|
@Override
|
93
323
|
protected RubyArray mapTables(final ThreadContext context, final Connection connection,
|
94
324
|
final String catalog, final String schemaPattern, final String tablePattern,
|
@@ -305,29 +535,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
305
535
|
statement.setObject(index, timeStr, Types.NVARCHAR);
|
306
536
|
}
|
307
537
|
|
308
|
-
// Overrides the method in parent, we only remove the savepoint
|
309
|
-
// from the getSavepoints Map
|
310
|
-
@JRubyMethod(name = "release_savepoint", required = 1)
|
311
|
-
public IRubyObject release_savepoint(final ThreadContext context, final IRubyObject name) {
|
312
|
-
if (name == context.nil) throw context.runtime.newArgumentError("nil savepoint name given");
|
313
|
-
|
314
|
-
final Connection connection = getConnection(true);
|
315
|
-
|
316
|
-
Object savepoint = getSavepoints(context).remove(name);
|
317
|
-
|
318
|
-
if (savepoint == null) throw newSavepointNotSetError(context, name, "release");
|
319
|
-
|
320
|
-
// NOTE: RubyHash.remove does not convert to Java as get does :
|
321
|
-
if (!(savepoint instanceof Savepoint)) {
|
322
|
-
savepoint = ((IRubyObject) savepoint).toJava(Savepoint.class);
|
323
|
-
}
|
324
|
-
|
325
|
-
// The 'releaseSavepoint' method is not currently supported
|
326
|
-
// by the Microsoft SQL Server JDBC Driver
|
327
|
-
// connection.releaseSavepoint((Savepoint) savepoint);
|
328
|
-
return context.nil;
|
329
|
-
}
|
330
|
-
|
331
538
|
//----------------------------------------------------------------
|
332
539
|
// read_uncommitted: "READ UNCOMMITTED",
|
333
540
|
// read_committed: "READ COMMITTED",
|
@@ -443,6 +650,42 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
443
650
|
});
|
444
651
|
}
|
445
652
|
|
653
|
+
// Get MSSQL minor version eg. 0
|
654
|
+
@JRubyMethod
|
655
|
+
public IRubyObject database_minor_version(final ThreadContext context) throws SQLException {
|
656
|
+
return withConnection(context, new Callable<IRubyObject>() {
|
657
|
+
public IRubyObject call(final Connection connection) throws SQLException {
|
658
|
+
final DatabaseMetaData metaData = connection.getMetaData();
|
659
|
+
|
660
|
+
return context.runtime.newFixnum( metaData.getDatabaseMinorVersion() );
|
661
|
+
}
|
662
|
+
});
|
663
|
+
}
|
664
|
+
|
665
|
+
// Get MSSQL product name eg. Microsoft SQL Server
|
666
|
+
@JRubyMethod
|
667
|
+
public IRubyObject database_product_name(final ThreadContext context) throws SQLException {
|
668
|
+
return withConnection(context, new Callable<IRubyObject>() {
|
669
|
+
public IRubyObject call(final Connection connection) throws SQLException {
|
670
|
+
final DatabaseMetaData metaData = connection.getMetaData();
|
671
|
+
|
672
|
+
return context.runtime.newString( metaData.getDatabaseProductName() );
|
673
|
+
}
|
674
|
+
});
|
675
|
+
}
|
676
|
+
|
677
|
+
// Get MSSQL product version eg. '14.00.3238'
|
678
|
+
@JRubyMethod
|
679
|
+
public IRubyObject database_product_version(final ThreadContext context) throws SQLException {
|
680
|
+
return withConnection(context, new Callable<IRubyObject>() {
|
681
|
+
public IRubyObject call(final Connection connection) throws SQLException {
|
682
|
+
final DatabaseMetaData metaData = connection.getMetaData();
|
683
|
+
|
684
|
+
return context.runtime.newString( metaData.getDatabaseProductVersion() );
|
685
|
+
}
|
686
|
+
});
|
687
|
+
}
|
688
|
+
|
446
689
|
/**
|
447
690
|
* Microsoft SQL 2000+ support schemas
|
448
691
|
*/
|
@@ -453,16 +696,64 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
453
696
|
|
454
697
|
/**
|
455
698
|
* Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
|
699
|
+
* Also handle datetimeoffset values here
|
456
700
|
*/
|
457
701
|
@Override
|
458
702
|
protected IRubyObject jdbcToRuby(
|
459
703
|
final ThreadContext context, final Ruby runtime,
|
460
704
|
final int column, int type, final ResultSet resultSet)
|
461
705
|
throws SQLException {
|
462
|
-
|
706
|
+
|
707
|
+
if (type == DATETIMEOFFSET_TYPE) {
|
708
|
+
|
709
|
+
Object dto = resultSet.getObject(column); // Returns a microsoft.sql.DateTimeOffset
|
710
|
+
|
711
|
+
if (dto == null) return context.nil;
|
712
|
+
|
713
|
+
try {
|
714
|
+
|
715
|
+
int minutes = (int) DateTimeOffsetGetMinutesOffsetMethod.invoke(dto);
|
716
|
+
DateTimeZone zone = DateTimeZone.forOffsetHoursMinutes(minutes / 60, minutes % 60);
|
717
|
+
Timestamp ts = (Timestamp) DateTimeOffsetGetTimestampMethod.invoke(dto);
|
718
|
+
|
719
|
+
int nanos = ts.getNanos(); // max 999-999-999
|
720
|
+
nanos = nanos % 1000000;
|
721
|
+
|
722
|
+
// We have to do this differently than the newTime helper because the Timestamp loses its zone information when passed around
|
723
|
+
DateTime dateTime = new DateTime(ts.getTime(), zone);
|
724
|
+
return RubyTime.newTime(context.runtime, dateTime, nanos);
|
725
|
+
|
726
|
+
} catch (IllegalAccessException e) {
|
727
|
+
debugMessage(runtime, e.getMessage());
|
728
|
+
return context.nil;
|
729
|
+
} catch (InvocationTargetException e) {
|
730
|
+
debugMessage(runtime, e.getMessage());
|
731
|
+
return context.nil;
|
732
|
+
}
|
733
|
+
}
|
734
|
+
|
735
|
+
if (type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR) type = Types.CLOB;
|
463
736
|
return super.jdbcToRuby(context, runtime, column, type, resultSet);
|
464
737
|
}
|
465
738
|
|
739
|
+
/**
|
740
|
+
* Converts a JDBC date object to a Ruby date by referencing Date#civil
|
741
|
+
* @param context current thread context
|
742
|
+
* @param resultSet the jdbc result set to pull the value from
|
743
|
+
* @param index the index of the column to convert
|
744
|
+
* @return RubyNil if NULL or RubyDate if there is a value
|
745
|
+
* @throws SQLException if it fails to retrieve the value from the result set
|
746
|
+
*/
|
747
|
+
@Override
|
748
|
+
protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
|
749
|
+
|
750
|
+
final Date value = resultSet.getDate(index);
|
751
|
+
|
752
|
+
if (value == null) return context.nil;
|
753
|
+
|
754
|
+
return DateTimeUtils.newDate(context, value);
|
755
|
+
}
|
756
|
+
|
466
757
|
@Override
|
467
758
|
protected ColumnData[] extractColumns(final ThreadContext context,
|
468
759
|
final Connection connection, final ResultSet resultSet,
|
@@ -492,19 +783,9 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
492
783
|
return columns;
|
493
784
|
}
|
494
785
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
// "jTDS Type 4 JDBC Driver for MS SQL Server and Sybase"
|
499
|
-
// SQLJDBC: "Microsoft JDBC Driver 4.0 for SQL Server"
|
500
|
-
return withConnection(context, new Callable<RubyBoolean>() {
|
501
|
-
// NOTE: only used in one place for now (on release_savepoint) ...
|
502
|
-
// might get optimized to only happen once since driver won't change
|
503
|
-
public RubyBoolean call(final Connection connection) throws SQLException {
|
504
|
-
final String driver = connection.getMetaData().getDriverName();
|
505
|
-
return context.getRuntime().newBoolean( driver.indexOf("jTDS") >= 0 );
|
506
|
-
}
|
507
|
-
});
|
786
|
+
@Override
|
787
|
+
protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
|
788
|
+
// MSSQL doesn't support releasing savepoints
|
508
789
|
}
|
509
790
|
|
510
791
|
}
|
@@ -40,8 +40,6 @@ import java.sql.Statement;
|
|
40
40
|
import java.sql.Timestamp;
|
41
41
|
import java.sql.Types;
|
42
42
|
|
43
|
-
import org.joda.time.DateTime;
|
44
|
-
import org.joda.time.DateTimeZone;
|
45
43
|
import org.jruby.*;
|
46
44
|
import org.jruby.anno.JRubyMethod;
|
47
45
|
import org.jruby.exceptions.RaiseException;
|
@@ -89,11 +87,9 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
89
87
|
|
90
88
|
@JRubyMethod(name = { "full_version" })
|
91
89
|
public IRubyObject db_version(final ThreadContext context) {
|
92
|
-
return withConnection(context,
|
93
|
-
|
94
|
-
|
95
|
-
return context.runtime.newString(metaData.getDatabaseProductVersion());
|
96
|
-
}
|
90
|
+
return withConnection(context, (Callable<IRubyObject>) connection -> {
|
91
|
+
final DatabaseMetaData metaData = connection.getMetaData();
|
92
|
+
return context.runtime.newString(metaData.getDatabaseProductVersion());
|
97
93
|
});
|
98
94
|
}
|
99
95
|
|
@@ -108,7 +104,7 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
108
104
|
if ( major < 5 ) {
|
109
105
|
final RubyClass errorClass = getConnectionNotEstablished(context.runtime);
|
110
106
|
throw context.runtime.newRaiseException(errorClass,
|
111
|
-
|
107
|
+
"MySQL adapter requires driver >= 5.0 got: " + major + "." + minor);
|
112
108
|
}
|
113
109
|
if ( major == 5 && minor < 1 ) { // need 5.1 for JDBC 4.0
|
114
110
|
// lightweight validation query: "/* ping */ SELECT 1"
|
@@ -203,7 +199,7 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
203
199
|
return resultSet.wasNull() ? context.nil : RubyString.newEmptyString(runtime);
|
204
200
|
}
|
205
201
|
|
206
|
-
if ( rawDateTime != null && rawDateTime
|
202
|
+
if ( rawDateTime != null && rawDateTime) {
|
207
203
|
return RubyString.newString(runtime, DateTimeUtils.dummyTimeToString(value));
|
208
204
|
}
|
209
205
|
|
@@ -237,7 +233,7 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
237
233
|
if (lowerCase == null) {
|
238
234
|
lowerCase = lowerCaseIdentifiers = connection.getMetaData().storesLowerCaseIdentifiers();
|
239
235
|
}
|
240
|
-
return lowerCase
|
236
|
+
return lowerCase ? value.toLowerCase() : value;
|
241
237
|
}
|
242
238
|
|
243
239
|
@Override
|
@@ -247,7 +243,9 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
247
243
|
connection = super.newConnection();
|
248
244
|
}
|
249
245
|
catch (SQLException ex) {
|
250
|
-
|
246
|
+
int errorCode = ex.getErrorCode();
|
247
|
+
// access denied, no database
|
248
|
+
if (errorCode == 1044 || errorCode == 1049) throw newNoDatabaseError(ex);
|
251
249
|
throw ex;
|
252
250
|
}
|
253
251
|
if ( doStopCleanupThread() ) shutdownCleanupThread();
|
@@ -261,7 +259,7 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
261
259
|
}
|
262
260
|
|
263
261
|
private static boolean doStopCleanupThread() throws SQLException {
|
264
|
-
return stopCleanupThread != null && stopCleanupThread
|
262
|
+
return stopCleanupThread != null && stopCleanupThread;
|
265
263
|
}
|
266
264
|
|
267
265
|
private static boolean cleanupThreadShutdown;
|
@@ -276,19 +274,11 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
276
274
|
catch (ClassNotFoundException e) {
|
277
275
|
debugMessage(null, "missing MySQL JDBC cleanup thread ", e);
|
278
276
|
}
|
279
|
-
catch (NoSuchMethodException e) {
|
280
|
-
debugMessage(null, e);
|
281
|
-
}
|
282
|
-
catch (IllegalAccessException e) {
|
277
|
+
catch (NoSuchMethodException | SecurityException | IllegalAccessException e) {
|
283
278
|
debugMessage(null, e);
|
284
|
-
}
|
285
|
-
catch (InvocationTargetException e) {
|
279
|
+
} catch (InvocationTargetException e) {
|
286
280
|
debugMessage(null, e.getTargetException());
|
287
|
-
}
|
288
|
-
catch (SecurityException e) {
|
289
|
-
debugMessage(null, e);
|
290
|
-
}
|
291
|
-
finally { cleanupThreadShutdown = true; }
|
281
|
+
} finally { cleanupThreadShutdown = true; }
|
292
282
|
}
|
293
283
|
|
294
284
|
}
|