activerecord-jdbc-alt-adapter 51.7.0-java → 52.2.0-java

Sign up to get free protection for your applications and to get access to all the features.
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
  }