36
36
import java .sql .PreparedStatement ;
37
37
import java .sql .ResultSet ;
38
38
import java .sql .Savepoint ;
39
+ import java .sql .Statement ;
39
40
import java .sql .SQLException ;
40
41
import java .sql .Timestamp ;
41
42
import java .sql .Types ;
43
+ import java .util .ArrayList ;
42
44
import java .util .HashMap ;
45
+ import java .util .List ;
43
46
import java .util .Map ;
44
47
45
48
import org .joda .time .DateTime ;
@@ -106,7 +109,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
106
109
MSSQL_JDBC_TYPE_FOR .put ("smallmoney" , Types .DECIMAL );
107
110
MSSQL_JDBC_TYPE_FOR .put ("ss_timestamp" , Types .BINARY );
108
111
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
110
112
MSSQL_JDBC_TYPE_FOR .put ("uuid" , Types .CHAR );
111
113
MSSQL_JDBC_TYPE_FOR .put ("varchar_max" , Types .VARCHAR );
112
114
}
@@ -142,6 +144,61 @@ public static RubyBoolean exec_p(ThreadContext context, IRubyObject self, IRubyO
142
144
return context .runtime .newBoolean ( startsWithIgnoreCase (sqlBytes , EXEC ) );
143
145
}
144
146
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
+
145
202
@ Override
146
203
protected Integer jdbcTypeFor (final String type ) {
147
204
@@ -163,16 +220,10 @@ protected void setStringParameter(final ThreadContext context, final Connection
163
220
// datetimeoffset values also make it in here
164
221
if (type == DATETIMEOFFSET_TYPE ) {
165
222
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 );
171
224
172
225
try {
173
226
174
- Object [] dtoArgs = { timestamp , offsetMinutes };
175
- Object dto = DateTimeOffsetValueOfMethod .invoke (null , dtoArgs );
176
227
Object [] setStatementArgs = { index , dto };
177
228
PreparedStatementSetDateTimeOffsetMethod .invoke (statement , setStatementArgs );
178
229
@@ -183,10 +234,46 @@ protected void setStringParameter(final ThreadContext context, final Connection
183
234
debugMessage (context .runtime , e .getMessage ());
184
235
throw new RuntimeException ("Please make sure you are using the latest version of the Microsoft JDBC driver" );
185
236
}
237
+
238
+ return ;
186
239
}
187
240
super .setStringParameter (context , connection , statement , index , value , attribute , type );
188
241
}
189
242
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
+
190
277
@ Override
191
278
protected RubyArray mapTables (final ThreadContext context , final Connection connection ,
192
279
final String catalog , final String schemaPattern , final String tablePattern ,
@@ -294,7 +381,7 @@ protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet
294
381
}
295
382
296
383
/**
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
298
385
* @param context current thread context
299
386
* @param resultSet the jdbc result set to pull the value from
300
387
* @param index the index of the column to convert
0 commit comments