Skip to content

Commit e30e103

Browse files
author
Rob Widmer
committed
Fix more tests for supporting mssql
1 parent 6551977 commit e30e103

File tree

2 files changed

+96
-22
lines changed

2 files changed

+96
-22
lines changed

src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@
3636
import java.sql.PreparedStatement;
3737
import java.sql.ResultSet;
3838
import java.sql.Savepoint;
39+
import java.sql.Statement;
3940
import java.sql.SQLException;
4041
import java.sql.Timestamp;
4142
import java.sql.Types;
43+
import java.util.ArrayList;
4244
import java.util.HashMap;
45+
import java.util.List;
4346
import java.util.Map;
4447

4548
import org.joda.time.DateTime;
@@ -106,7 +109,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
106109
MSSQL_JDBC_TYPE_FOR.put("smallmoney", Types.DECIMAL);
107110
MSSQL_JDBC_TYPE_FOR.put("ss_timestamp", Types.BINARY);
108111
MSSQL_JDBC_TYPE_FOR.put("text_basic", Types.LONGVARCHAR);
109-
MSSQL_JDBC_TYPE_FOR.put("time", Types.TIMESTAMP); // Treat times as timestamps so we get correct subsecond precision
110112
MSSQL_JDBC_TYPE_FOR.put("uuid", Types.CHAR);
111113
MSSQL_JDBC_TYPE_FOR.put("varchar_max", Types.VARCHAR);
112114
}
@@ -142,6 +144,61 @@ public static RubyBoolean exec_p(ThreadContext context, IRubyObject self, IRubyO
142144
return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
143145
}
144146

147+
// Support multiple result sets for mssql
148+
@Override
149+
@JRubyMethod(name = "execute", required = 1)
150+
public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
151+
final String query = sqlString(sql);
152+
return withConnection(context, new Callable<IRubyObject>() {
153+
public IRubyObject call(final Connection connection) throws SQLException {
154+
Statement statement = null;
155+
try {
156+
statement = createStatement(context, connection);
157+
158+
// For DBs that do support multiple statements, lets return the last result set
159+
// to be consistent with AR
160+
boolean hasResultSet = doExecute(statement, query);
161+
int updateCount = statement.getUpdateCount();
162+
163+
final List<IRubyObject> results = new ArrayList<IRubyObject>();
164+
ResultSet resultSet;
165+
166+
while (hasResultSet || updateCount != -1) {
167+
168+
if (hasResultSet) {
169+
resultSet = statement.getResultSet();
170+
171+
// Unfortunately the result set gets closed when getMoreResults()
172+
// is called, so we have to process the result sets as we get them
173+
// this shouldn't be an issue in most cases since we're only getting 1 result set anyways
174+
results.add(mapExecuteResult(context, connection, resultSet));
175+
} else {
176+
results.add(context.runtime.newFixnum(updateCount));
177+
}
178+
179+
// Check to see if there is another result set
180+
hasResultSet = statement.getMoreResults();
181+
updateCount = statement.getUpdateCount();
182+
}
183+
184+
if (results.size() == 0) {
185+
return context.nil; // If no results, return nil
186+
} else if (results.size() == 1) {
187+
return results.get(0);
188+
} else {
189+
return context.runtime.newArray(results);
190+
}
191+
192+
} catch (final SQLException e) {
193+
debugErrorSQL(context, query);
194+
throw e;
195+
} finally {
196+
close(statement);
197+
}
198+
}
199+
});
200+
}
201+
145202
@Override
146203
protected Integer jdbcTypeFor(final String type) {
147204

@@ -163,16 +220,10 @@ protected void setStringParameter(final ThreadContext context, final Connection
163220
// datetimeoffset values also make it in here
164221
if (type == DATETIMEOFFSET_TYPE) {
165222

166-
RubyTime time = (RubyTime) value;
167-
DateTime dt = ((RubyTime) value).getDateTime();
168-
Timestamp timestamp = new Timestamp(dt.getMillis());
169-
timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
170-
int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
223+
Object dto = convertToDateTimeOffset(context, value);
171224

172225
try {
173226

174-
Object[] dtoArgs = { timestamp, offsetMinutes };
175-
Object dto = DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
176227
Object[] setStatementArgs = { index, dto };
177228
PreparedStatementSetDateTimeOffsetMethod.invoke(statement, setStatementArgs);
178229

@@ -183,10 +234,46 @@ protected void setStringParameter(final ThreadContext context, final Connection
183234
debugMessage(context.runtime, e.getMessage());
184235
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
185236
}
237+
238+
return;
186239
}
187240
super.setStringParameter(context, connection, statement, index, value, attribute, type);
188241
}
189242

243+
// We need higher precision than the default for Time objects (which is milliseconds) so we use a DateTimeOffset object
244+
@Override
245+
protected void setTimeParameter(final ThreadContext context,
246+
final Connection connection, final PreparedStatement statement,
247+
final int index, IRubyObject value,
248+
final IRubyObject attribute, final int type) throws SQLException {
249+
250+
statement.setObject(index, convertToDateTimeOffset(context, value), Types.TIME);
251+
252+
}
253+
254+
private Object convertToDateTimeOffset(final ThreadContext context, final IRubyObject value) {
255+
256+
RubyTime time = (RubyTime) value;
257+
DateTime dt = time.getDateTime();
258+
Timestamp timestamp = new Timestamp(dt.getMillis());
259+
timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
260+
int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
261+
262+
try {
263+
264+
Object[] dtoArgs = { timestamp, offsetMinutes };
265+
return DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
266+
267+
} catch (IllegalAccessException e) {
268+
debugMessage(context.runtime, e.getMessage());
269+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
270+
} catch (InvocationTargetException e) {
271+
debugMessage(context.runtime, e.getMessage());
272+
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
273+
}
274+
}
275+
276+
190277
@Override
191278
protected RubyArray mapTables(final ThreadContext context, final Connection connection,
192279
final String catalog, final String schemaPattern, final String tablePattern,
@@ -294,7 +381,7 @@ protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet
294381
}
295382

296383
/**
297-
* Converts a JDBC time to a Ruby time. We use timestamp because java.sql.Time doesn't support sub-second values
384+
* Converts a JDBC time to a Ruby time. We use timestamp because java.sql.Time doesn't support sub-millisecond values
298385
* @param context current thread context
299386
* @param resultSet the jdbc result set to pull the value from
300387
* @param index the index of the column to convert

test/transaction_test_methods.rb

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,6 @@ def setup
3434

3535
def setup_failed?; @setup_failed ||= false end
3636

37-
def test_supports_transaction_isolation
38-
unless ActiveRecord::Base.connection.supports_transaction_isolation?
39-
omit("transaction isolation not supported")
40-
end
41-
unless defined? JRUBY_VERSION
42-
omit("transaction isolation(:type_arg) not supported by Rails")
43-
end
44-
assert ActiveRecord::Base.connection.supports_transaction_isolation?(:read_uncommitted)
45-
assert ActiveRecord::Base.connection.supports_transaction_isolation?(:read_committed)
46-
assert ActiveRecord::Base.connection.supports_transaction_isolation?(:repeatable_read)
47-
assert ActiveRecord::Base.connection.supports_transaction_isolation?(:serializable)
48-
end
49-
5037
def test_transaction_isolation_read_uncommitted
5138
# It is impossible to properly test read uncommitted. The SQL standard only
5239
# specifies what must not happen at a certain level, not what must happen. At

0 commit comments

Comments
 (0)