activerecord-jdbc-adapter 50.8-java → 51.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -2
  3. data/.travis.yml +26 -51
  4. data/Gemfile +3 -1
  5. data/README.md +9 -11
  6. data/Rakefile +15 -78
  7. data/activerecord-jdbc-adapter.gemspec +2 -2
  8. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  9. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  10. data/lib/arjdbc/abstract/core.rb +4 -12
  11. data/lib/arjdbc/abstract/database_statements.rb +4 -10
  12. data/lib/arjdbc/abstract/statement_cache.rb +4 -4
  13. data/lib/arjdbc/abstract/transaction_support.rb +2 -9
  14. data/lib/arjdbc/jdbc.rb +4 -0
  15. data/lib/arjdbc/jdbc/column.rb +11 -5
  16. data/lib/arjdbc/jdbc/connection_methods.rb +9 -2
  17. data/lib/arjdbc/jdbc/error.rb +1 -1
  18. data/lib/arjdbc/jdbc/jdbc.rake +4 -0
  19. data/lib/arjdbc/mssql.rb +7 -0
  20. data/lib/arjdbc/mssql/adapter.rb +804 -0
  21. data/lib/arjdbc/mssql/column.rb +200 -0
  22. data/lib/arjdbc/mssql/connection_methods.rb +79 -0
  23. data/lib/arjdbc/mssql/explain_support.rb +99 -0
  24. data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
  25. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  26. data/lib/arjdbc/mssql/types.rb +343 -0
  27. data/lib/arjdbc/mssql/utils.rb +82 -0
  28. data/lib/arjdbc/mysql/adapter.rb +14 -11
  29. data/lib/arjdbc/mysql/connection_methods.rb +9 -18
  30. data/lib/arjdbc/postgresql/adapter.rb +108 -59
  31. data/lib/arjdbc/postgresql/column.rb +3 -6
  32. data/lib/arjdbc/postgresql/connection_methods.rb +3 -12
  33. data/lib/arjdbc/postgresql/oid_types.rb +14 -93
  34. data/lib/arjdbc/sqlite3/adapter.rb +171 -140
  35. data/lib/arjdbc/sqlite3/connection_methods.rb +1 -2
  36. data/lib/arjdbc/tasks/database_tasks.rb +36 -16
  37. data/lib/arjdbc/tasks/databases.rake +75 -32
  38. data/lib/arjdbc/tasks/databases3.rake +215 -0
  39. data/lib/arjdbc/tasks/databases4.rake +39 -0
  40. data/lib/arjdbc/version.rb +1 -1
  41. data/rakelib/01-tomcat.rake +2 -2
  42. data/rakelib/02-test.rake +3 -0
  43. data/rakelib/compile.rake +70 -0
  44. data/rakelib/db.rake +7 -21
  45. data/rakelib/rails.rake +4 -5
  46. data/src/java/arjdbc/ArJdbcModule.java +15 -5
  47. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +2 -2
  48. data/src/java/arjdbc/jdbc/ConnectionFactory.java +87 -0
  49. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +1 -0
  50. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +41 -120
  51. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +14 -310
  52. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +2 -2
  53. data/src/java/arjdbc/postgresql/ByteaUtils.java +1 -0
  54. data/src/java/arjdbc/postgresql/PgResultSetMetaDataWrapper.java +23 -0
  55. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +13 -21
  56. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +46 -41
  57. data/src/java/arjdbc/util/DateTimeUtils.java +5 -141
  58. data/src/java/arjdbc/util/QuotingUtils.java +7 -6
  59. data/src/java/arjdbc/util/StringHelper.java +20 -6
  60. metadata +25 -16
  61. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +0 -61
  62. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +0 -52
@@ -28,32 +28,17 @@ 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;
33
31
  import java.sql.Connection;
34
32
  import java.sql.DatabaseMetaData;
35
- import java.sql.Date;
36
- import java.sql.PreparedStatement;
37
33
  import java.sql.ResultSet;
38
- import java.sql.Savepoint;
39
- import java.sql.Statement;
40
34
  import java.sql.SQLException;
41
- import java.sql.Timestamp;
42
35
  import java.sql.Types;
43
- import java.util.ArrayList;
44
- import java.util.HashMap;
45
- import java.util.List;
46
- import java.util.Map;
47
36
 
48
- import arjdbc.util.DateTimeUtils;
49
- import org.joda.time.DateTime;
50
- import org.joda.time.DateTimeZone;
51
37
  import org.jruby.Ruby;
52
38
  import org.jruby.RubyArray;
53
39
  import org.jruby.RubyBoolean;
54
40
  import org.jruby.RubyClass;
55
41
  import org.jruby.RubyString;
56
- import org.jruby.RubyTime;
57
42
  import org.jruby.anno.JRubyMethod;
58
43
  import org.jruby.runtime.ObjectAllocator;
59
44
  import org.jruby.runtime.ThreadContext;
@@ -67,53 +52,6 @@ import org.jruby.util.ByteList;
67
52
  public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
68
53
  private static final long serialVersionUID = -745716565005219263L;
69
54
 
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
-
117
55
  public MSSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
118
56
  super(runtime, metaClass);
119
57
  }
@@ -145,187 +83,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
145
83
  return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
146
84
  }
147
85
 
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
-
329
86
  @Override
330
87
  protected RubyArray mapTables(final ThreadContext context, final Connection connection,
331
88
  final String catalog, final String schemaPattern, final String tablePattern,
@@ -367,79 +124,16 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
367
124
 
368
125
  /**
369
126
  * Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
370
- * Also handle datetimeoffset values here
371
127
  */
372
128
  @Override
373
129
  protected IRubyObject jdbcToRuby(
374
130
  final ThreadContext context, final Ruby runtime,
375
131
  final int column, int type, final ResultSet resultSet)
376
132
  throws SQLException {
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;
133
+ if ( type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR ) type = Types.CLOB;
407
134
  return super.jdbcToRuby(context, runtime, column, type, resultSet);
408
135
  }
409
136
 
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
-
443
137
  @Override
444
138
  protected ColumnData[] extractColumns(final ThreadContext context,
445
139
  final Connection connection, final ResultSet resultSet,
@@ -469,9 +163,19 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
469
163
  return columns;
470
164
  }
471
165
 
472
- @Override
473
- protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
474
- // MSSQL doesn't support releasing savepoints
166
+ // internal helper not meant as a "public" API - used in one place thus every
167
+ @JRubyMethod(name = "jtds_driver?")
168
+ public RubyBoolean jtds_driver_p(final ThreadContext context) throws SQLException {
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
+ });
475
179
  }
476
180
 
477
181
  }
@@ -107,8 +107,8 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
107
107
  final int minor = jdbcDriver.getMinorVersion();
108
108
  if ( major < 5 ) {
109
109
  final RubyClass errorClass = getConnectionNotEstablished(context.runtime);
110
- throw context.runtime.newRaiseException(errorClass,
111
- "MySQL adapter requires driver >= 5.0 got: " + major + "." + minor + "");
110
+ throw new RaiseException(context.runtime, errorClass,
111
+ "MySQL adapter requires driver >= 5.0 got: " + major + "." + minor + "", false);
112
112
  }
113
113
  if ( major == 5 && minor < 1 ) { // need 5.1 for JDBC 4.0
114
114
  // lightweight validation query: "/* ping */ SELECT 1"
@@ -88,6 +88,7 @@ abstract class ByteaUtils {
88
88
  }
89
89
  else {
90
90
  correctSize -= 3;
91
+ i += 2;
91
92
  }
92
93
  }
93
94
  }
@@ -0,0 +1,23 @@
1
+ /*
2
+ * A class to loosen restrictions on the PgResultSetMetaData class,
3
+ * we need to be able to get the field and the method is currently set to "package".
4
+ */
5
+ package org.postgresql.jdbc;
6
+
7
+ import java.sql.SQLException;
8
+ import org.postgresql.core.BaseConnection;
9
+ import org.postgresql.core.Field;
10
+ import org.postgresql.jdbc.PgResultSetMetaData;
11
+
12
+ public class PgResultSetMetaDataWrapper {
13
+
14
+ private final PgResultSetMetaData metaData;
15
+
16
+ public PgResultSetMetaDataWrapper(PgResultSetMetaData metaData) {
17
+ this.metaData = metaData;
18
+ }
19
+
20
+ public Field getField(int i) throws SQLException {
21
+ return this.metaData.getField(i);
22
+ }
23
+ }
@@ -4,7 +4,6 @@ import arjdbc.jdbc.JdbcResult;
4
4
  import arjdbc.jdbc.RubyJdbcConnection;
5
5
 
6
6
  import java.sql.ResultSet;
7
- import java.sql.ResultSetMetaData;
8
7
  import java.sql.SQLException;
9
8
  import java.sql.Types;
10
9
 
@@ -12,6 +11,7 @@ import org.jruby.Ruby;
12
11
  import org.jruby.RubyArray;
13
12
  import org.jruby.RubyClass;
14
13
  import org.jruby.RubyHash;
14
+ import org.jruby.RubyMethod;
15
15
  import org.jruby.RubyModule;
16
16
  import org.jruby.RubyString;
17
17
  import org.jruby.anno.JRubyMethod;
@@ -21,13 +21,17 @@ import org.jruby.runtime.ObjectAllocator;
21
21
  import org.jruby.runtime.ThreadContext;
22
22
  import org.jruby.runtime.builtin.IRubyObject;
23
23
 
24
+ import org.postgresql.core.Field;
25
+ import org.postgresql.jdbc.PgResultSetMetaData;
26
+ import org.postgresql.jdbc.PgResultSetMetaDataWrapper; // This is a hack unfortunately to get around method scoping
27
+
24
28
  /*
25
29
  * This class mimics the PG:Result class enough to get by
26
30
  */
27
31
  public class PostgreSQLResult extends JdbcResult {
28
32
 
29
33
  // These are needed when generating an AR::Result
30
- private final ResultSetMetaData resultSetMetaData;
34
+ private final PgResultSetMetaData resultSetMetaData;
31
35
 
32
36
  /********* JRuby compat methods ***********/
33
37
 
@@ -57,7 +61,7 @@ public class PostgreSQLResult extends JdbcResult {
57
61
  ResultSet resultSet) throws SQLException {
58
62
  super(context, clazz, connection, resultSet);
59
63
 
60
- resultSetMetaData = resultSet.getMetaData();
64
+ resultSetMetaData = (PgResultSetMetaData) resultSet.getMetaData();
61
65
  }
62
66
 
63
67
  /**
@@ -70,28 +74,16 @@ public class PostgreSQLResult extends JdbcResult {
70
74
  protected IRubyObject columnTypeMap(final ThreadContext context) throws SQLException {
71
75
  Ruby runtime = context.runtime;
72
76
  RubyHash types = RubyHash.newHash(runtime);
77
+ PgResultSetMetaDataWrapper mdWrapper = new PgResultSetMetaDataWrapper(resultSetMetaData);
73
78
  int columnCount = columnNames.length;
74
79
 
75
80
  IRubyObject adapter = connection.adapter(context);
76
81
  for (int i = 0; i < columnCount; i++) {
77
- int col = i + 1;
78
- String typeName = resultSetMetaData.getColumnTypeName(col);
79
-
80
- int mod = 0;
81
- if ("numeric".equals(typeName)) {
82
- // this field is only relevant for "numeric" type in AR
83
- // AR checks (fmod - 4 & 0xffff).zero?
84
- // pgjdbc:
85
- // - for typmod == -1, getScale() and getPrecision() return 0
86
- // - for typmod != -1, getScale() returns "(typmod - 4) & 0xFFFF;"
87
- mod = resultSetMetaData.getScale(col);
88
- mod = mod == 0 && resultSetMetaData.getPrecision(col) == 0 ? -1 : mod + 4;
89
- }
90
-
82
+ final Field field = mdWrapper.getField(i + 1);
91
83
  final RubyString name = columnNames[i];
92
84
  final IRubyObject type = Helpers.invoke(context, adapter, "get_oid_type",
93
- runtime.newString(typeName),
94
- runtime.newFixnum(mod),
85
+ runtime.newFixnum(field.getOID()),
86
+ runtime.newFixnum(field.getMod()),
95
87
  name);
96
88
 
97
89
  if (!type.isNil()) types.fastASet(name, type);
@@ -152,7 +144,7 @@ public class PostgreSQLResult extends JdbcResult {
152
144
  * @return ActiveRecord::Result object with the data from this result set
153
145
  * @throws SQLException can be caused by postgres generating its type map
154
146
  */
155
- @Override @SuppressWarnings("unchecked")
147
+ @Override
156
148
  public IRubyObject toARResult(final ThreadContext context) throws SQLException {
157
149
  RubyClass BinaryDataClass = null;
158
150
  int rowCount = 0;
@@ -171,7 +163,7 @@ public class PostgreSQLResult extends JdbcResult {
171
163
  RubyArray row = (RubyArray) values.eltInternal(rowIndex);
172
164
  IRubyObject value = row.eltInternal(columnIndex);
173
165
  if (value != context.nil) {
174
- row.eltInternalSet(columnIndex, BinaryDataClass.newInstance(context, value, Block.NULL_BLOCK));
166
+ row.eltInternalSet(columnIndex, (IRubyObject) BinaryDataClass.newInstance(context, value, Block.NULL_BLOCK));
175
167
  }
176
168
  }
177
169
  }