activerecord-jdbc-alt-adapter 51.7.0-java → 52.2.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -3
  3. data/.travis.yml +24 -28
  4. data/Gemfile +2 -3
  5. data/README.md +27 -19
  6. data/Rakefile +4 -30
  7. data/activerecord-jdbc-adapter.gemspec +2 -2
  8. data/activerecord-jdbc-alt-adapter.gemspec +4 -4
  9. data/lib/arel/visitors/sqlserver.rb +2 -1
  10. data/lib/arjdbc/abstract/core.rb +12 -2
  11. data/lib/arjdbc/abstract/database_statements.rb +22 -8
  12. data/lib/arjdbc/abstract/statement_cache.rb +1 -0
  13. data/lib/arjdbc/db2/adapter.rb +77 -8
  14. data/lib/arjdbc/db2/as400.rb +12 -0
  15. data/lib/arjdbc/db2/column.rb +3 -0
  16. data/lib/arjdbc/db2/connection_methods.rb +4 -0
  17. data/lib/arjdbc/jdbc/adapter.rb +4 -6
  18. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  19. data/lib/arjdbc/jdbc/column.rb +14 -6
  20. data/lib/arjdbc/jdbc.rb +7 -0
  21. data/lib/arjdbc/mssql/adapter.rb +20 -32
  22. data/lib/arjdbc/mssql/connection_methods.rb +1 -5
  23. data/lib/arjdbc/mssql/database_limits.rb +29 -0
  24. data/lib/arjdbc/mssql/database_statements.rb +41 -1
  25. data/lib/arjdbc/mssql/explain_support.rb +6 -1
  26. data/lib/arjdbc/mssql/quoting.rb +9 -2
  27. data/lib/arjdbc/mssql/schema_creation.rb +2 -1
  28. data/lib/arjdbc/mssql/schema_dumper.rb +1 -1
  29. data/lib/arjdbc/mssql/schema_statements.rb +11 -3
  30. data/lib/arjdbc/mssql/transaction.rb +3 -3
  31. data/lib/arjdbc/mssql.rb +1 -1
  32. data/lib/arjdbc/mysql/adapter.rb +20 -4
  33. data/lib/arjdbc/mysql/connection_methods.rb +7 -13
  34. data/lib/arjdbc/postgresql/adapter.rb +23 -20
  35. data/lib/arjdbc/postgresql/column.rb +3 -6
  36. data/lib/arjdbc/postgresql/connection_methods.rb +1 -3
  37. data/lib/arjdbc/postgresql/oid_types.rb +6 -11
  38. data/lib/arjdbc/sqlite3/adapter.rb +86 -91
  39. data/lib/arjdbc/sqlite3/connection_methods.rb +0 -1
  40. data/lib/arjdbc/tasks/database_tasks.rb +1 -0
  41. data/lib/arjdbc/tasks/sqlite_database_tasks_patch.rb +17 -0
  42. data/lib/arjdbc/version.rb +1 -1
  43. data/rakelib/01-tomcat.rake +2 -2
  44. data/rakelib/02-test.rake +2 -0
  45. data/rakelib/rails.rake +1 -1
  46. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +10 -65
  47. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +38 -282
  48. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +69 -4
  49. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +69 -13
  50. data/src/java/arjdbc/util/DateTimeUtils.java +17 -28
  51. data/src/java/arjdbc/util/PG.java +8 -0
  52. metadata +15 -12
  53. data/lib/activerecord-jdbc-alt-adapter.rb +0 -1
@@ -29,33 +29,22 @@ 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;
34
32
  import java.sql.Connection;
35
33
  import java.sql.DatabaseMetaData;
36
- import java.sql.Date;
37
34
  import java.sql.PreparedStatement;
38
35
  import java.sql.ResultSet;
39
36
  import java.sql.Savepoint;
40
- import java.sql.Statement;
41
37
  import java.sql.SQLException;
42
- import java.sql.Timestamp;
43
38
  import java.sql.Types;
39
+ import java.sql.Timestamp;
44
40
  import java.util.Locale;
45
- import java.util.ArrayList;
46
- import java.util.HashMap;
47
- import java.util.List;
48
- import java.util.Map;
49
41
 
50
- import org.joda.time.DateTime;
51
- import org.joda.time.DateTimeZone;
52
42
  import org.jruby.Ruby;
53
43
  import org.jruby.RubyArray;
54
44
  import org.jruby.RubyBoolean;
55
45
  import org.jruby.RubyClass;
56
46
  import org.jruby.RubyString;
57
47
  import org.jruby.RubySymbol;
58
- import org.jruby.RubyTime;
59
48
  import org.jruby.anno.JRubyMethod;
60
49
  import org.jruby.runtime.ObjectAllocator;
61
50
  import org.jruby.runtime.ThreadContext;
@@ -69,54 +58,6 @@ import org.jruby.util.ByteList;
69
58
  public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
70
59
  private static final long serialVersionUID = -745716565005219263L;
71
60
 
72
- private static final int DATETIMEOFFSET_TYPE;
73
- private static final Method DateTimeOffsetGetMinutesOffsetMethod;
74
- private static final Method DateTimeOffsetGetTimestampMethod;
75
- private static final Method DateTimeOffsetValueOfMethod;
76
- private static final Method PreparedStatementSetDateTimeOffsetMethod;
77
-
78
- private static final Map<String, Integer> MSSQL_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
79
- static {
80
-
81
- Class<?> DateTimeOffset;
82
- Class<?> MssqlPreparedStatement;
83
- Class<?> MssqlTypes;
84
- try {
85
- DateTimeOffset = Class.forName("microsoft.sql.DateTimeOffset");
86
- MssqlPreparedStatement = Class.forName("com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement");
87
- MssqlTypes = Class.forName("microsoft.sql.Types");
88
- } catch (ClassNotFoundException e) {
89
- System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
90
- throw new RuntimeException("You must require the Microsoft JDBC driver to use this gem");
91
- }
92
-
93
- try {
94
- DATETIMEOFFSET_TYPE = MssqlTypes.getField("DATETIMEOFFSET").getInt(null);
95
- DateTimeOffsetGetMinutesOffsetMethod = DateTimeOffset.getDeclaredMethod("getMinutesOffset");
96
- DateTimeOffsetGetTimestampMethod = DateTimeOffset.getDeclaredMethod("getTimestamp");
97
-
98
- Class<?>[] valueOfArgTypes = { Timestamp.class, int.class };
99
- DateTimeOffsetValueOfMethod = DateTimeOffset.getDeclaredMethod("valueOf", valueOfArgTypes);
100
-
101
- Class<?>[] setOffsetArgTypes = { int.class, DateTimeOffset };
102
- PreparedStatementSetDateTimeOffsetMethod = MssqlPreparedStatement.getDeclaredMethod("setDateTimeOffset", setOffsetArgTypes);
103
- } catch (Exception e) {
104
- System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
105
- throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
106
- }
107
-
108
- MSSQL_JDBC_TYPE_FOR.put("binary_basic", Types.BINARY);
109
- MSSQL_JDBC_TYPE_FOR.put("image", Types.BINARY);
110
- MSSQL_JDBC_TYPE_FOR.put("datetimeoffset", DATETIMEOFFSET_TYPE);
111
- MSSQL_JDBC_TYPE_FOR.put("money", Types.DECIMAL);
112
- MSSQL_JDBC_TYPE_FOR.put("smalldatetime", Types.TIMESTAMP);
113
- MSSQL_JDBC_TYPE_FOR.put("smallmoney", Types.DECIMAL);
114
- MSSQL_JDBC_TYPE_FOR.put("ss_timestamp", Types.BINARY);
115
- MSSQL_JDBC_TYPE_FOR.put("text_basic", Types.LONGVARCHAR);
116
- MSSQL_JDBC_TYPE_FOR.put("uuid", Types.CHAR);
117
- MSSQL_JDBC_TYPE_FOR.put("varchar_max", Types.VARCHAR);
118
- }
119
-
120
61
  public MSSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
121
62
  super(runtime, metaClass);
122
63
  }
@@ -148,176 +89,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
148
89
  return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
149
90
  }
150
91
 
151
- // Support multiple result sets for mssql
152
- @Override
153
- @JRubyMethod(name = "execute", required = 1)
154
- public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
155
- final String query = sqlString(sql);
156
- return withConnection(context, new Callable<IRubyObject>() {
157
- public IRubyObject call(final Connection connection) throws SQLException {
158
- Statement statement = null;
159
- try {
160
- statement = createStatement(context, connection);
161
-
162
- // For DBs that do support multiple statements, lets return the last result set
163
- // to be consistent with AR
164
- boolean hasResultSet = doExecute(statement, query);
165
- int updateCount = statement.getUpdateCount();
166
-
167
- final List<IRubyObject> results = new ArrayList<IRubyObject>();
168
- ResultSet resultSet;
169
-
170
- while (hasResultSet || updateCount != -1) {
171
-
172
- if (hasResultSet) {
173
- resultSet = statement.getResultSet();
174
-
175
- // Unfortunately the result set gets closed when getMoreResults()
176
- // is called, so we have to process the result sets as we get them
177
- // this shouldn't be an issue in most cases since we're only getting 1 result set anyways
178
- results.add(mapExecuteResult(context, connection, resultSet));
179
- } else {
180
- results.add(context.runtime.newFixnum(updateCount));
181
- }
182
-
183
- // Check to see if there is another result set
184
- hasResultSet = statement.getMoreResults();
185
- updateCount = statement.getUpdateCount();
186
- }
187
-
188
- if (results.size() == 0) {
189
- return context.nil; // If no results, return nil
190
- } else if (results.size() == 1) {
191
- return results.get(0);
192
- } else {
193
- return context.runtime.newArray(results);
194
- }
195
-
196
- } catch (final SQLException e) {
197
- debugErrorSQL(context, query);
198
- throw e;
199
- } finally {
200
- close(statement);
201
- }
202
- }
203
- });
204
- }
205
-
206
- /**
207
- * Executes an INSERT SQL statement
208
- * @param context
209
- * @param sql
210
- * @param pk Rails PK
211
- * @return ActiveRecord::Result
212
- * @throws SQLException
213
- */
214
- @Override
215
- @JRubyMethod(name = "execute_insert_pk", required = 2)
216
- public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
217
-
218
- // MSSQL does not like composite primary keys here so chop it if there is more than one column
219
- IRubyObject modifiedPk = pk;
220
-
221
- if (pk instanceof RubyArray) {
222
- RubyArray ary = (RubyArray) pk;
223
- if (ary.size() > 0) {
224
- modifiedPk = ary.eltInternal(0);
225
- }
226
- }
227
-
228
- return super.execute_insert_pk(context, sql, modifiedPk);
229
- }
230
-
231
- /**
232
- * Executes an INSERT SQL statement using a prepared statement
233
- * @param context
234
- * @param sql
235
- * @param binds RubyArray of values to be bound to the query
236
- * @param pk Rails PK
237
- * @return ActiveRecord::Result
238
- * @throws SQLException
239
- */
240
- @Override
241
- @JRubyMethod(name = "execute_insert_pk", required = 3)
242
- public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
243
- final IRubyObject pk) {
244
- // MSSQL does not like composite primary keys here so chop it if there is more than one column
245
- IRubyObject modifiedPk = pk;
246
-
247
- if (pk instanceof RubyArray) {
248
- RubyArray ary = (RubyArray) pk;
249
- if (ary.size() > 0) {
250
- modifiedPk = ary.eltInternal(0);
251
- }
252
- }
253
-
254
- return super.execute_insert_pk(context, sql, binds, modifiedPk);
255
- }
256
-
257
- @Override
258
- protected Integer jdbcTypeFor(final String type) {
259
-
260
- Integer typeValue = MSSQL_JDBC_TYPE_FOR.get(type);
261
-
262
- if ( typeValue != null ) {
263
- return typeValue;
264
- }
265
-
266
- return super.jdbcTypeFor(type);
267
- }
268
-
269
- // Datetimeoffset values also make it into here
270
- @Override
271
- protected void setStringParameter(final ThreadContext context, final Connection connection,
272
- final PreparedStatement statement, final int index, final IRubyObject value,
273
- final IRubyObject attribute, final int type) throws SQLException {
274
-
275
- // datetimeoffset values also make it in here
276
- if (type == DATETIMEOFFSET_TYPE) {
277
-
278
- Object dto = convertToDateTimeOffset(context, value);
279
-
280
- try {
281
-
282
- Object[] setStatementArgs = { index, dto };
283
- PreparedStatementSetDateTimeOffsetMethod.invoke(statement, setStatementArgs);
284
-
285
- } catch (IllegalAccessException 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
- } catch (InvocationTargetException e) {
289
- debugMessage(context.runtime, e.getMessage());
290
- throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
291
- }
292
-
293
- return;
294
- }
295
- super.setStringParameter(context, connection, statement, index, value, attribute, type);
296
- }
297
-
298
- private Object convertToDateTimeOffset(final ThreadContext context, final IRubyObject value) {
299
-
300
- RubyTime time = (RubyTime) value;
301
- DateTime dt = time.getDateTime();
302
- Timestamp timestamp = new Timestamp(dt.getMillis());
303
- timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
304
- int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
305
-
306
- try {
307
-
308
- Object[] dtoArgs = { timestamp, offsetMinutes };
309
- return DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
310
-
311
- } catch (IllegalAccessException e) {
312
- debugMessage(context.runtime, e.getMessage());
313
- throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
314
- } catch (InvocationTargetException e) {
315
- debugMessage(context.runtime, e.getMessage());
316
- throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
317
- }
318
- }
319
-
320
-
321
92
  @Override
322
93
  protected RubyArray mapTables(final ThreadContext context, final Connection connection,
323
94
  final String catalog, final String schemaPattern, final String tablePattern,
@@ -534,6 +305,29 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
534
305
  statement.setObject(index, timeStr, Types.NVARCHAR);
535
306
  }
536
307
 
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
+
537
331
  //----------------------------------------------------------------
538
332
  // read_uncommitted: "READ UNCOMMITTED",
539
333
  // read_committed: "READ COMMITTED",
@@ -659,64 +453,16 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
659
453
 
660
454
  /**
661
455
  * Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
662
- * Also handle datetimeoffset values here
663
456
  */
664
457
  @Override
665
458
  protected IRubyObject jdbcToRuby(
666
459
  final ThreadContext context, final Ruby runtime,
667
460
  final int column, int type, final ResultSet resultSet)
668
461
  throws SQLException {
669
-
670
- if (type == DATETIMEOFFSET_TYPE) {
671
-
672
- Object dto = resultSet.getObject(column); // Returns a microsoft.sql.DateTimeOffset
673
-
674
- if (dto == null) return context.nil;
675
-
676
- try {
677
-
678
- int minutes = (int) DateTimeOffsetGetMinutesOffsetMethod.invoke(dto);
679
- DateTimeZone zone = DateTimeZone.forOffsetHoursMinutes(minutes / 60, minutes % 60);
680
- Timestamp ts = (Timestamp) DateTimeOffsetGetTimestampMethod.invoke(dto);
681
-
682
- int nanos = ts.getNanos(); // max 999-999-999
683
- nanos = nanos % 1000000;
684
-
685
- // We have to do this differently than the newTime helper because the Timestamp loses its zone information when passed around
686
- DateTime dateTime = new DateTime(ts.getTime(), zone);
687
- return RubyTime.newTime(context.runtime, dateTime, nanos);
688
-
689
- } catch (IllegalAccessException e) {
690
- debugMessage(runtime, e.getMessage());
691
- return context.nil;
692
- } catch (InvocationTargetException e) {
693
- debugMessage(runtime, e.getMessage());
694
- return context.nil;
695
- }
696
- }
697
-
698
- if (type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR) type = Types.CLOB;
462
+ if ( type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR ) type = Types.CLOB;
699
463
  return super.jdbcToRuby(context, runtime, column, type, resultSet);
700
464
  }
701
465
 
702
- /**
703
- * Converts a JDBC date object to a Ruby date by referencing Date#civil
704
- * @param context current thread context
705
- * @param resultSet the jdbc result set to pull the value from
706
- * @param index the index of the column to convert
707
- * @return RubyNil if NULL or RubyDate if there is a value
708
- * @throws SQLException if it fails to retrieve the value from the result set
709
- */
710
- @Override
711
- protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
712
-
713
- final Date value = resultSet.getDate(index);
714
-
715
- if (value == null) return context.nil;
716
-
717
- return DateTimeUtils.newDate(context, value);
718
- }
719
-
720
466
  @Override
721
467
  protected ColumnData[] extractColumns(final ThreadContext context,
722
468
  final Connection connection, final ResultSet resultSet,
@@ -746,9 +492,19 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
746
492
  return columns;
747
493
  }
748
494
 
749
- @Override
750
- protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
751
- // MSSQL doesn't support releasing savepoints
495
+ // internal helper not meant as a "public" API - used in one place thus every
496
+ @JRubyMethod(name = "jtds_driver?")
497
+ public RubyBoolean jtds_driver_p(final ThreadContext context) throws SQLException {
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
+ });
752
508
  }
753
509
 
754
510
  }
@@ -8,11 +8,13 @@ import java.sql.ResultSetMetaData;
8
8
  import java.sql.SQLException;
9
9
  import java.sql.Types;
10
10
 
11
+ import arjdbc.util.PG;
11
12
  import org.jruby.Ruby;
12
13
  import org.jruby.RubyArray;
13
14
  import org.jruby.RubyClass;
14
15
  import org.jruby.RubyHash;
15
16
  import org.jruby.RubyModule;
17
+ import org.jruby.RubyNumeric;
16
18
  import org.jruby.RubyString;
17
19
  import org.jruby.anno.JRubyMethod;
18
20
  import org.jruby.runtime.Block;
@@ -22,9 +24,11 @@ import org.jruby.runtime.ThreadContext;
22
24
  import org.jruby.runtime.builtin.IRubyObject;
23
25
 
24
26
  /*
25
- * This class mimics the PG:Result class enough to get by
27
+ * This class mimics the PG::Result class enough to get by. It also adorns common methods useful for
28
+ * gems like mini_sql to consume it similarly to PG::Result
26
29
  */
27
30
  public class PostgreSQLResult extends JdbcResult {
31
+ private RubyArray fields = null; // lazily created if PG fields method is called.
28
32
 
29
33
  // These are needed when generating an AR::Result
30
34
  private final ResultSetMetaData resultSetMetaData;
@@ -107,7 +111,7 @@ public class PostgreSQLResult extends JdbcResult {
107
111
  * @param block which may handle each result
108
112
  * @return this object or RubyNil
109
113
  */
110
- @JRubyMethod
114
+ @PG @JRubyMethod
111
115
  public IRubyObject each(ThreadContext context, Block block) {
112
116
  // At this point we don't support calling this without a block
113
117
  if (block.isGiven()) {
@@ -139,7 +143,7 @@ public class PostgreSQLResult extends JdbcResult {
139
143
  * @param context current thread contect
140
144
  * @return <code>Fixnum</code>
141
145
  */
142
- @JRubyMethod
146
+ @PG @JRubyMethod(name = {"length", "ntuples", "num_tuples"})
143
147
  public IRubyObject length(final ThreadContext context) {
144
148
  return values.length();
145
149
  }
@@ -185,8 +189,69 @@ public class PostgreSQLResult extends JdbcResult {
185
189
  * This is defined in PG::Result and is used by some Rails tests
186
190
  * @return IRubyObject RubyArray of RubyArray of values
187
191
  */
188
- @JRubyMethod
192
+ @PG @JRubyMethod
189
193
  public IRubyObject values() {
190
194
  return values;
191
195
  }
196
+
197
+ /**
198
+ * Do we have any rows of result
199
+ * @param context the thread context
200
+ * @return number of rows
201
+ */
202
+ @JRubyMethod(name = "empty?")
203
+ public IRubyObject isEmpty(ThreadContext context) {
204
+ return context.runtime.newBoolean(values.isEmpty());
205
+ }
206
+
207
+ @PG @JRubyMethod
208
+ public RubyArray fields(ThreadContext context) {
209
+ if (fields == null) fields = RubyArray.newArrayNoCopy(context.runtime, getColumnNames());
210
+
211
+ return fields;
212
+ }
213
+
214
+ @PG @JRubyMethod(name = {"nfields", "num_fields"})
215
+ public IRubyObject nfields(ThreadContext context) {
216
+ return context.runtime.newFixnum(getColumnNames().length);
217
+ }
218
+
219
+ @PG @JRubyMethod
220
+ public IRubyObject getvalue(ThreadContext context, IRubyObject rowArg, IRubyObject columnArg) {
221
+ int rows = values.size();
222
+ int row = RubyNumeric.fix2int(rowArg);
223
+ int column = RubyNumeric.fix2int(columnArg);
224
+
225
+ if (row < 0 || row >= rows) throw context.runtime.newArgumentError("invalid tuple number " + row);
226
+ if (column < 0 || column >= getColumnNames().length) throw context.runtime.newArgumentError("invalid field number " + row);
227
+
228
+ return ((RubyArray) values.eltInternal(row)).eltInternal(column);
229
+ }
230
+
231
+ @PG @JRubyMethod(name = "[]")
232
+ public IRubyObject aref(ThreadContext context, IRubyObject rowArg) {
233
+ int row = RubyNumeric.fix2int(rowArg);
234
+ int rows = values.size();
235
+
236
+ if (row < 0 || row >= rows) throw context.runtime.newArgumentError("Index " + row + " is out of range");
237
+
238
+ RubyArray rowValues = (RubyArray) values.eltOk(row);
239
+ RubyHash resultHash = RubyHash.newSmallHash(context.runtime);
240
+ RubyArray fields = fields(context);
241
+ int length = rowValues.getLength();
242
+ for (int i = 0; i < length; i++) {
243
+ resultHash.op_aset(context, fields.eltOk(i), rowValues.eltOk(i));
244
+ }
245
+
246
+ return resultHash;
247
+ }
248
+
249
+ // Note: this is # of commands (insert/update/selects performed) and not number of rows. In practice,
250
+ // so far users always just check this as to when it is 0 which ends up being the same as an update/insert
251
+ // where no rows were affected...so wrong value but the important value will be the same (I do not see
252
+ // how jdbc can do this).
253
+ @PG @JRubyMethod(name = {"cmdtuples", "cmd_tuples"})
254
+ public IRubyObject cmdtuples(ThreadContext context) {
255
+ return values.isEmpty() ? context.runtime.newFixnum(0) : aref(context, context.runtime.newFixnum(0));
256
+ }
192
257
  }
@@ -1,4 +1,5 @@
1
- /***** BEGIN LICENSE BLOCK *****
1
+ /*
2
+ ***** BEGIN LICENSE BLOCK *****
2
3
  * Copyright (c) 2012-2015 Karol Bucek <self@kares.org>
3
4
  * Copyright (c) 2006-2010 Nick Sieger <nick@nicksieger.com>
4
5
  * Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
@@ -27,8 +28,8 @@ package arjdbc.postgresql;
27
28
 
28
29
  import arjdbc.jdbc.Callable;
29
30
  import arjdbc.jdbc.DriverWrapper;
30
- import arjdbc.postgresql.PostgreSQLResult;
31
31
  import arjdbc.util.DateTimeUtils;
32
+ import arjdbc.util.PG;
32
33
  import arjdbc.util.StringHelper;
33
34
 
34
35
  import java.io.ByteArrayInputStream;
@@ -39,6 +40,7 @@ import java.sql.DatabaseMetaData;
39
40
  import java.sql.Date;
40
41
  import java.sql.PreparedStatement;
41
42
  import java.sql.ResultSet;
43
+ import java.sql.ResultSetMetaData;
42
44
  import java.sql.SQLException;
43
45
  import java.sql.Timestamp;
44
46
  import java.sql.Types;
@@ -50,8 +52,6 @@ import java.util.UUID;
50
52
  import java.util.regex.Pattern;
51
53
  import java.util.regex.Matcher;
52
54
 
53
- import org.joda.time.DateTime;
54
- import org.joda.time.DateTimeZone;
55
55
  import org.jruby.*;
56
56
  import org.jruby.anno.JRubyMethod;
57
57
  import org.jruby.exceptions.RaiseException;
@@ -62,6 +62,7 @@ import org.jruby.runtime.ThreadContext;
62
62
  import org.jruby.runtime.builtin.IRubyObject;
63
63
  import org.jruby.util.ByteList;
64
64
 
65
+ import org.jruby.util.TypeConverter;
65
66
  import org.postgresql.PGConnection;
66
67
  import org.postgresql.PGStatement;
67
68
  import org.postgresql.geometric.PGbox;
@@ -84,7 +85,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
84
85
  private static final Pattern doubleValuePattern = Pattern.compile("(-?\\d+(?:\\.\\d+)?)");
85
86
  private static final Pattern uuidPattern = Pattern.compile("\\{?\\p{XDigit}{4}(?:-?(\\p{XDigit}{4})){7}\\}?"); // Fuzzy match postgres's allowed formats
86
87
 
87
- private static final Map<String, Integer> POSTGRES_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
88
+ private static final Map<String, Integer> POSTGRES_JDBC_TYPE_FOR = new HashMap<>(32, 1);
88
89
  static {
89
90
  POSTGRES_JDBC_TYPE_FOR.put("bit", Types.OTHER);
90
91
  POSTGRES_JDBC_TYPE_FOR.put("bit_varying", Types.OTHER);
@@ -116,6 +117,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
116
117
  private static final Pattern pointCleanerPattern = Pattern.compile("\\.0\\b");
117
118
 
118
119
  private RubyClass resultClass;
120
+ private RubyHash typeMap = null;
119
121
 
120
122
  public PostgreSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
121
123
  super(runtime, metaClass);
@@ -363,13 +365,26 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
363
365
  final int index, IRubyObject value,
364
366
  final IRubyObject attribute, final int type) throws SQLException {
365
367
 
368
+ if ( value instanceof RubyFloat ) {
369
+ final double doubleValue = ( (RubyFloat) value ).getValue();
370
+ if ( Double.isInfinite(doubleValue) ) {
371
+ setTimestampInfinity(statement, index, doubleValue);
372
+ return;
373
+ }
374
+ }
375
+
366
376
  if ( ! "Date".equals(value.getMetaClass().getName()) && value.respondsTo("to_date") ) {
367
377
  value = value.callMethod(context, "to_date");
368
378
  }
369
379
 
370
- // NOTE: Here we rely in ActiveRecord (ActiveSupport) to get
371
- // the date as a string in the database format.
372
- statement.setDate(index, Date.valueOf(value.callMethod(context, "to_s", context.runtime.newSymbol("db")).toString()));
380
+ int year = RubyNumeric.num2int(value.callMethod(context, "year"));
381
+ int month = RubyNumeric.num2int(value.callMethod(context, "month"));
382
+ int day = RubyNumeric.num2int(value.callMethod(context, "day"));
383
+
384
+ @SuppressWarnings("deprecated")
385
+ Date date = new Date(year - 1900, month - 1, day);
386
+
387
+ statement.setDate(index, date);
373
388
  }
374
389
 
375
390
  @Override
@@ -470,6 +485,21 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
470
485
  }
471
486
  }
472
487
 
488
+ protected IRubyObject jdbcToRuby(ThreadContext context, Ruby runtime, int column, int type, ResultSet resultSet) throws SQLException {
489
+ return typeMap != null ?
490
+ convertWithTypeMap(context, runtime, column, type, resultSet) :
491
+ super.jdbcToRuby(context, runtime, column, type, resultSet);
492
+ }
493
+
494
+ private IRubyObject convertWithTypeMap(ThreadContext context, Ruby runtime, int column, int type, ResultSet resultSet) throws SQLException {
495
+ ResultSetMetaData metaData = resultSet.getMetaData();
496
+ IRubyObject decoder = typeMap.op_aref(context, STRING_CACHE.get(context, metaData.getColumnTypeName(column)));
497
+
498
+ if (decoder.isNil()) return super.jdbcToRuby(context, runtime, column, type, resultSet);
499
+
500
+ return decoder.callMethod(context, "decode", StringHelper.newDefaultInternalString(runtime, resultSet.getString(column)));
501
+ }
502
+
473
503
  // The tests won't start if this returns PGpoint[]
474
504
  // it fails with a runtime error: "NativeException: java.lang.reflect.InvocationTargetException: [Lorg/postgresql/geometric/PGpoint"
475
505
  private Object[] convertToPoints(Double[] values) throws SQLException {
@@ -516,7 +546,9 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
516
546
  final PreparedStatement statement, final int index,
517
547
  final IRubyObject value, final String columnType) throws SQLException {
518
548
 
519
- final String rangeValue = value.toString();
549
+ // As of AR 5.2 this is a Range object, I defer to the adapter for encoding because of the edge cases
550
+ // of dealing with the specific types in the range
551
+ final String rangeValue = adapter(context).callMethod(context, "encode_range", value).toString();
520
552
  final Object pgRange;
521
553
 
522
554
  switch ( columnType ) {
@@ -536,7 +568,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
536
568
  pgRange = new Int8RangeType(rangeValue);
537
569
  break;
538
570
  default:
539
- pgRange = new NumRangeType(rangeValue);
571
+ pgRange = new NumRangeType(rangeValue, columnType);
540
572
  }
541
573
 
542
574
  statement.setObject(index, pgRange);
@@ -685,8 +717,16 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
685
717
  protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
686
718
  // NOTE: PostgreSQL adapter under MRI using pg gem returns UTC-d Date/Time values
687
719
  final String value = resultSet.getString(index);
720
+ if (value == null) return context.nil;
688
721
 
689
- return value == null ? context.nil : DateTimeUtils.parseDate(context, value, getDefaultTimeZone(context));
722
+ final int len = value.length();
723
+ if (len < 10 && value.charAt(len - 1) == 'y') { // infinity / -infinity
724
+ IRubyObject infinity = parseInfinity(context.runtime, value);
725
+
726
+ if (infinity != null) return infinity;
727
+ }
728
+
729
+ return DateTimeUtils.parseDate(context, value, getDefaultTimeZone(context));
690
730
  }
691
731
 
692
732
 
@@ -940,11 +980,27 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
940
980
  setType("numrange");
941
981
  }
942
982
 
943
- public NumRangeType(final String value) throws SQLException {
944
- this();
983
+ public NumRangeType(final String value, final String type) throws SQLException {
984
+ setType(type);
945
985
  setValue(value);
946
986
  }
947
987
 
948
988
  }
949
989
 
990
+ @PG @JRubyMethod
991
+ public IRubyObject escape_string(ThreadContext context, IRubyObject string) {
992
+ return PostgreSQLModule.quote_string(context, this, string);
993
+ }
994
+
995
+ @PG @JRubyMethod(name = "typemap=")
996
+ public IRubyObject typemap_set(ThreadContext context, IRubyObject mapArg) {
997
+ if (mapArg.isNil()) {
998
+ typeMap = null;
999
+ return context.nil;
1000
+ }
1001
+
1002
+ TypeConverter.checkHashType(context.runtime, mapArg);
1003
+ this.typeMap = (RubyHash) mapArg;
1004
+ return mapArg;
1005
+ }
950
1006
  }