activerecord-jdbc-adapter 52.1-java → 52.2-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 (34) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +34 -15
  3. data/Gemfile +1 -2
  4. data/README.md +10 -3
  5. data/lib/arjdbc/abstract/core.rb +12 -2
  6. data/lib/arjdbc/abstract/database_statements.rb +1 -1
  7. data/lib/arjdbc/abstract/statement_cache.rb +4 -4
  8. data/lib/arjdbc/db2/adapter.rb +68 -60
  9. data/lib/arjdbc/db2/as400.rb +12 -0
  10. data/lib/arjdbc/db2/column.rb +3 -0
  11. data/lib/arjdbc/db2/connection_methods.rb +4 -0
  12. data/lib/arjdbc/jdbc.rb +3 -0
  13. data/lib/arjdbc/jdbc/adapter.rb +0 -6
  14. data/lib/arjdbc/jdbc/column.rb +4 -2
  15. data/lib/arjdbc/mysql/adapter.rb +8 -0
  16. data/lib/arjdbc/postgresql/adapter.rb +5 -72
  17. data/lib/arjdbc/postgresql/oid_types.rb +82 -14
  18. data/lib/arjdbc/version.rb +1 -1
  19. data/rakelib/rails.rake +4 -3
  20. data/src/java/arjdbc/ArJdbcModule.java +5 -15
  21. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +2 -2
  22. data/src/java/arjdbc/jdbc/ConnectionFactory.java +0 -87
  23. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +0 -1
  24. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
  25. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +46 -18
  26. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +2 -2
  27. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
  28. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +90 -17
  29. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +68 -49
  30. data/src/java/arjdbc/util/DateTimeUtils.java +119 -0
  31. data/src/java/arjdbc/util/PG.java +8 -0
  32. data/src/java/arjdbc/util/QuotingUtils.java +6 -7
  33. metadata +6 -4
  34. data/src/java/arjdbc/postgresql/PgResultSetMetaDataWrapper.java +0 -23
@@ -128,6 +128,7 @@ public class RubyJdbcConnection extends RubyObject {
128
128
  private boolean lazy = false; // final once set on initialize
129
129
  private boolean jndi; // final once set on initialize
130
130
  private boolean configureConnection = true; // final once initialized
131
+ private int fetchSize = 0; // 0 = JDBC default
131
132
 
132
133
  protected RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
133
134
  super(runtime, metaClass);
@@ -571,6 +572,11 @@ public class RubyJdbcConnection extends RubyObject {
571
572
  else {
572
573
  this.configureConnection = value != context.runtime.getFalse();
573
574
  }
575
+
576
+ IRubyObject jdbcFetchSize = getConfigValue(context, "jdbc_fetch_size");
577
+ if (jdbcFetchSize != context.nil) {
578
+ this.fetchSize = RubyNumeric.fix2int(jdbcFetchSize);
579
+ }
574
580
  }
575
581
 
576
582
  @JRubyMethod(name = "adapter")
@@ -818,6 +824,7 @@ public class RubyJdbcConnection extends RubyObject {
818
824
  // is called, so we have to process the result sets as we get them
819
825
  // this shouldn't be an issue in most cases since we're only getting 1 result set anyways
820
826
  result = mapExecuteResult(context, connection, resultSet);
827
+ resultSet.close();
821
828
  } else {
822
829
  result = context.runtime.newFixnum(updateCount);
823
830
  }
@@ -851,6 +858,7 @@ public class RubyJdbcConnection extends RubyObject {
851
858
  else {
852
859
  statement.setEscapeProcessing(escapeProcessing.isTrue());
853
860
  }
861
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
854
862
  return statement;
855
863
  }
856
864
 
@@ -1045,6 +1053,7 @@ public class RubyJdbcConnection extends RubyObject {
1045
1053
  else {
1046
1054
  final PreparedStatement prepStatement;
1047
1055
  statement = prepStatement = connection.prepareStatement(query);
1056
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
1048
1057
  statement.setMaxRows(maxRows); // zero means there is no limit
1049
1058
  setStatementParameters(context, connection, prepStatement, binds);
1050
1059
  hasResult = prepStatement.execute();
@@ -1112,6 +1121,24 @@ public class RubyJdbcConnection extends RubyObject {
1112
1121
  });
1113
1122
  }
1114
1123
 
1124
+ /**
1125
+ * Prepares a query, returns a wrapped PreparedStatement. This takes care of exception wrapping
1126
+ * @param context which context this method is executing on.
1127
+ * @param sql the query to prepare-
1128
+ * @return a Ruby <code>PreparedStatement</code>
1129
+ */
1130
+ @JRubyMethod(required = 1)
1131
+ public IRubyObject prepare_statement(final ThreadContext context, final IRubyObject sql) {
1132
+ return withConnection(context, new Callable<IRubyObject>() {
1133
+ public IRubyObject call(Connection connection) throws SQLException {
1134
+ final String query = sql.convertToString().getUnicodeValue();
1135
+ PreparedStatement statement = connection.prepareStatement(query);
1136
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
1137
+ return JavaUtil.convertJavaToRuby(context.runtime, statement);
1138
+ }
1139
+ });
1140
+ }
1141
+
1115
1142
  // Called from exec_query in abstract/database_statements
1116
1143
  /**
1117
1144
  * Executes a query and returns the (AR) result. There are three parameters:
@@ -1142,6 +1169,7 @@ public class RubyJdbcConnection extends RubyObject {
1142
1169
  statement = (PreparedStatement) JavaEmbedUtils.rubyToJava(cachedStatement);
1143
1170
  } else {
1144
1171
  statement = connection.prepareStatement(query);
1172
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
1145
1173
  }
1146
1174
 
1147
1175
  setStatementParameters(context, connection, statement, (RubyArray) binds);
@@ -1149,12 +1177,7 @@ public class RubyJdbcConnection extends RubyObject {
1149
1177
  if (statement.execute()) {
1150
1178
  ResultSet resultSet = statement.getResultSet();
1151
1179
  IRubyObject results = mapQueryResult(context, connection, resultSet);
1152
-
1153
- if (cached) {
1154
- // Make sure we free the result set if we are caching the statement
1155
- // It gets closed automatically when the statement is closed if we aren't caching
1156
- resultSet.close();
1157
- }
1180
+ resultSet.close();
1158
1181
 
1159
1182
  return results;
1160
1183
  } else {
@@ -1807,7 +1830,7 @@ public class RubyJdbcConnection extends RubyObject {
1807
1830
  if ( url.isNil() || ( driver.isNil() && driver_instance.isNil() ) ) {
1808
1831
  final Ruby runtime = context.runtime;
1809
1832
  final RubyClass errorClass = getConnectionNotEstablished( runtime );
1810
- throw new RaiseException(runtime, errorClass, "adapter requires :driver class and jdbc :url", false);
1833
+ throw runtime.newRaiseException(errorClass, "adapter requires :driver class and jdbc :url");
1811
1834
  }
1812
1835
 
1813
1836
  final String jdbcURL = buildURL(context, url);
@@ -1824,7 +1847,7 @@ public class RubyJdbcConnection extends RubyObject {
1824
1847
  return factory;
1825
1848
  }
1826
1849
  else {
1827
- setConnectionFactory(factory = new RubyConnectionFactoryImpl(
1850
+ setConnectionFactory(factory = new RubyConnectionFactory(
1828
1851
  driver_instance, context.runtime.newString(jdbcURL),
1829
1852
  ( username.isNil() ? username : username.asString() ),
1830
1853
  ( password.isNil() ? password : password.asString() )
@@ -2485,6 +2508,8 @@ public class RubyJdbcConnection extends RubyObject {
2485
2508
  while ( arrayResult.next() ) {
2486
2509
  array.append( jdbcToRuby(context, runtime, 2, baseType, arrayResult) );
2487
2510
  }
2511
+ arrayResult.close();
2512
+
2488
2513
  return array;
2489
2514
  }
2490
2515
  finally { if ( value != null ) value.free(); }
@@ -2687,7 +2712,7 @@ public class RubyJdbcConnection extends RubyObject {
2687
2712
  }
2688
2713
 
2689
2714
  protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final IRubyObject value) {
2690
- return timeInDefaultTimeZone(context, toTime(context, value));
2715
+ return timeInDefaultTimeZone(context, DateTimeUtils.toTime(context, value));
2691
2716
  }
2692
2717
 
2693
2718
  protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final RubyTime time) {
@@ -2699,11 +2724,14 @@ public class RubyJdbcConnection extends RubyObject {
2699
2724
  return timeInDefaultTZ;
2700
2725
  }
2701
2726
 
2727
+ protected final DateTime dateTimeInDefaultTimeZone(final ThreadContext context, final DateTime dateTime) {
2728
+ final DateTimeZone defaultZone = getDefaultTimeZone(context);
2729
+ if (defaultZone == dateTime.getZone()) return dateTime;
2730
+ return dateTime.withZone(defaultZone);
2731
+ }
2732
+
2702
2733
  public static RubyTime toTime(final ThreadContext context, final IRubyObject value) {
2703
- if ( ! ( value instanceof RubyTime ) ) { // unlikely
2704
- return (RubyTime) TypeConverter.convertToTypeWithCheck(value, context.runtime.getTime(), "to_time");
2705
- }
2706
- return (RubyTime) value;
2734
+ return DateTimeUtils.toTime(context, value);
2707
2735
  }
2708
2736
 
2709
2737
  protected boolean isDefaultTimeZoneUTC(final ThreadContext context) {
@@ -2803,8 +2831,8 @@ public class RubyJdbcConnection extends RubyObject {
2803
2831
  final int index, IRubyObject value,
2804
2832
  final IRubyObject attribute, final int type) throws SQLException {
2805
2833
 
2806
- final RubyTime timeValue = timeInDefaultTimeZone(context, value);
2807
- final DateTime dateTime = timeValue.getDateTime();
2834
+ final RubyTime timeValue = DateTimeUtils.toTime(context, value);
2835
+ final DateTime dateTime = dateTimeInDefaultTimeZone(context, timeValue.getDateTime());
2808
2836
  final Timestamp timestamp = new Timestamp(dateTime.getMillis());
2809
2837
  // 1942-11-30T01:02:03.123_456
2810
2838
  if (timeValue.getNSec() > 0) timestamp.setNanos((int) (timestamp.getNanos() + timeValue.getNSec()));
@@ -3005,7 +3033,7 @@ public class RubyJdbcConnection extends RubyObject {
3005
3033
  private void handleNotConnected() {
3006
3034
  final Ruby runtime = getRuntime();
3007
3035
  final RubyClass errorClass = getConnectionNotEstablished( runtime );
3008
- throw new RaiseException(runtime, errorClass, "no connection available", false);
3036
+ throw runtime.newRaiseException(errorClass, "no connection available");
3009
3037
  }
3010
3038
 
3011
3039
  /**
@@ -3542,7 +3570,7 @@ public class RubyJdbcConnection extends RubyObject {
3542
3570
  return (RaiseException) exception;
3543
3571
  }
3544
3572
  if ( exception instanceof RuntimeException ) {
3545
- return RaiseException.createNativeRaiseException(runtime, exception);
3573
+ return wrapException(context, context.runtime.getRuntimeError(), exception);
3546
3574
  }
3547
3575
  // NOTE: compat - maybe makes sense or maybe not (e.g. IOException) :
3548
3576
  return wrapException(context, getJDBCError(runtime), exception);
@@ -3555,7 +3583,7 @@ public class RubyJdbcConnection extends RubyObject {
3555
3583
 
3556
3584
  public static RaiseException wrapException(final ThreadContext context,
3557
3585
  final RubyClass errorClass, final Throwable exception, final String message) {
3558
- final RaiseException error = new RaiseException(context.runtime, errorClass, message, true);
3586
+ final RaiseException error = context.runtime.newRaiseException(errorClass, message);
3559
3587
  error.initCause(exception);
3560
3588
  return error;
3561
3589
  }
@@ -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 new RaiseException(context.runtime, errorClass,
111
- "MySQL adapter requires driver >= 5.0 got: " + major + "." + minor + "", false);
110
+ throw context.runtime.newRaiseException(errorClass,
111
+ "MySQL adapter requires driver >= 5.0 got: " + major + "." + minor + "");
112
112
  }
113
113
  if ( major == 5 && minor < 1 ) { // need 5.1 for JDBC 4.0
114
114
  // lightweight validation query: "/* ping */ SELECT 1"
@@ -0,0 +1,52 @@
1
+ package arjdbc.postgresql;
2
+
3
+ import arjdbc.util.DateTimeUtils;
4
+ import org.joda.time.DateTimeZone;
5
+ import org.jruby.RubyArray;
6
+ import org.jruby.RubyFloat;
7
+ import org.jruby.runtime.ThreadContext;
8
+ import org.jruby.runtime.builtin.IRubyObject;
9
+
10
+ /**
11
+ * PostgreSQL specific DateTime/Timestamp helpers
12
+ * @author dritz
13
+ */
14
+ public abstract class PgDateTimeUtils extends DateTimeUtils {
15
+ /**
16
+ * Convert a ruby value to a PostgreSQL timestamp string
17
+ * @param context
18
+ * @param value Ruby value, typically a Time instance
19
+ * @param zone DateTimeZone to adjust to, optional
20
+ * @param withZone include timezone in string?
21
+ * @return A string fit for PostgreSQL
22
+ */
23
+ public static String timestampValueToString(final ThreadContext context, IRubyObject value, DateTimeZone zone,
24
+ boolean withZone) {
25
+ if (value instanceof RubyFloat) {
26
+ final double dv = ((RubyFloat) value).getValue();
27
+ if (dv == Double.POSITIVE_INFINITY) {
28
+ return "infinity";
29
+ } else if (dv == Double.NEGATIVE_INFINITY) {
30
+ return "-infinity";
31
+ }
32
+ }
33
+ return timestampTimeToString(context, value, zone, withZone);
34
+ }
35
+
36
+ /**
37
+ * Converts a RubyArray with timestamp values to a java array of PostgreSQL timestamp strings
38
+ * @param context
39
+ * @param rubyArray
40
+ * @return Array of timestamp strings
41
+ */
42
+ public static String[] timestampStringArray(final ThreadContext context, final RubyArray rubyArray) {
43
+ int size = rubyArray.size();
44
+ String[] values = new String[size];
45
+
46
+ for (int i = 0; i < size; i++) {
47
+ IRubyObject elem = rubyArray.eltInternal(i);
48
+ values[i] = timestampValueToString(context, elem, DateTimeZone.UTC, false);
49
+ }
50
+ return values;
51
+ }
52
+ }
@@ -4,15 +4,17 @@ import arjdbc.jdbc.JdbcResult;
4
4
  import arjdbc.jdbc.RubyJdbcConnection;
5
5
 
6
6
  import java.sql.ResultSet;
7
+ import java.sql.ResultSetMetaData;
7
8
  import java.sql.SQLException;
8
9
  import java.sql.Types;
9
10
 
11
+ import arjdbc.util.PG;
10
12
  import org.jruby.Ruby;
11
13
  import org.jruby.RubyArray;
12
14
  import org.jruby.RubyClass;
13
15
  import org.jruby.RubyHash;
14
- import org.jruby.RubyMethod;
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;
@@ -21,17 +23,15 @@ import org.jruby.runtime.ObjectAllocator;
21
23
  import org.jruby.runtime.ThreadContext;
22
24
  import org.jruby.runtime.builtin.IRubyObject;
23
25
 
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
-
28
26
  /*
29
- * 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
30
29
  */
31
30
  public class PostgreSQLResult extends JdbcResult {
31
+ private RubyArray fields = null; // lazily created if PG fields method is called.
32
32
 
33
33
  // These are needed when generating an AR::Result
34
- private final PgResultSetMetaData resultSetMetaData;
34
+ private final ResultSetMetaData resultSetMetaData;
35
35
 
36
36
  /********* JRuby compat methods ***********/
37
37
 
@@ -61,7 +61,7 @@ public class PostgreSQLResult extends JdbcResult {
61
61
  ResultSet resultSet) throws SQLException {
62
62
  super(context, clazz, connection, resultSet);
63
63
 
64
- resultSetMetaData = (PgResultSetMetaData) resultSet.getMetaData();
64
+ resultSetMetaData = resultSet.getMetaData();
65
65
  }
66
66
 
67
67
  /**
@@ -74,16 +74,28 @@ public class PostgreSQLResult extends JdbcResult {
74
74
  protected IRubyObject columnTypeMap(final ThreadContext context) throws SQLException {
75
75
  Ruby runtime = context.runtime;
76
76
  RubyHash types = RubyHash.newHash(runtime);
77
- PgResultSetMetaDataWrapper mdWrapper = new PgResultSetMetaDataWrapper(resultSetMetaData);
78
77
  int columnCount = columnNames.length;
79
78
 
80
79
  IRubyObject adapter = connection.adapter(context);
81
80
  for (int i = 0; i < columnCount; i++) {
82
- final Field field = mdWrapper.getField(i + 1);
81
+ int col = i + 1;
82
+ String typeName = resultSetMetaData.getColumnTypeName(col);
83
+
84
+ int mod = 0;
85
+ if ("numeric".equals(typeName)) {
86
+ // this field is only relevant for "numeric" type in AR
87
+ // AR checks (fmod - 4 & 0xffff).zero?
88
+ // pgjdbc:
89
+ // - for typmod == -1, getScale() and getPrecision() return 0
90
+ // - for typmod != -1, getScale() returns "(typmod - 4) & 0xFFFF;"
91
+ mod = resultSetMetaData.getScale(col);
92
+ mod = mod == 0 && resultSetMetaData.getPrecision(col) == 0 ? -1 : mod + 4;
93
+ }
94
+
83
95
  final RubyString name = columnNames[i];
84
96
  final IRubyObject type = Helpers.invoke(context, adapter, "get_oid_type",
85
- runtime.newFixnum(field.getOID()),
86
- runtime.newFixnum(field.getMod()),
97
+ runtime.newString(typeName),
98
+ runtime.newFixnum(mod),
87
99
  name);
88
100
 
89
101
  if (!type.isNil()) types.fastASet(name, type);
@@ -99,7 +111,7 @@ public class PostgreSQLResult extends JdbcResult {
99
111
  * @param block which may handle each result
100
112
  * @return this object or RubyNil
101
113
  */
102
- @JRubyMethod
114
+ @PG @JRubyMethod
103
115
  public IRubyObject each(ThreadContext context, Block block) {
104
116
  // At this point we don't support calling this without a block
105
117
  if (block.isGiven()) {
@@ -131,7 +143,7 @@ public class PostgreSQLResult extends JdbcResult {
131
143
  * @param context current thread contect
132
144
  * @return <code>Fixnum</code>
133
145
  */
134
- @JRubyMethod
146
+ @PG @JRubyMethod(name = {"length", "ntuples", "num_tuples"})
135
147
  public IRubyObject length(final ThreadContext context) {
136
148
  return values.length();
137
149
  }
@@ -144,7 +156,7 @@ public class PostgreSQLResult extends JdbcResult {
144
156
  * @return ActiveRecord::Result object with the data from this result set
145
157
  * @throws SQLException can be caused by postgres generating its type map
146
158
  */
147
- @Override
159
+ @Override @SuppressWarnings("unchecked")
148
160
  public IRubyObject toARResult(final ThreadContext context) throws SQLException {
149
161
  RubyClass BinaryDataClass = null;
150
162
  int rowCount = 0;
@@ -163,7 +175,7 @@ public class PostgreSQLResult extends JdbcResult {
163
175
  RubyArray row = (RubyArray) values.eltInternal(rowIndex);
164
176
  IRubyObject value = row.eltInternal(columnIndex);
165
177
  if (value != context.nil) {
166
- row.eltInternalSet(columnIndex, (IRubyObject) BinaryDataClass.newInstance(context, value, Block.NULL_BLOCK));
178
+ row.eltInternalSet(columnIndex, BinaryDataClass.newInstance(context, value, Block.NULL_BLOCK));
167
179
  }
168
180
  }
169
181
  }
@@ -177,8 +189,69 @@ public class PostgreSQLResult extends JdbcResult {
177
189
  * This is defined in PG::Result and is used by some Rails tests
178
190
  * @return IRubyObject RubyArray of RubyArray of values
179
191
  */
180
- @JRubyMethod
192
+ @PG @JRubyMethod
181
193
  public IRubyObject values() {
182
194
  return values;
183
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
+ }
184
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);
@@ -282,6 +284,30 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
282
284
  return mapExecuteResult(context, connection, resultSet).toARResult(context);
283
285
  }
284
286
 
287
+ @Override
288
+ protected void setArrayParameter(final ThreadContext context,
289
+ final Connection connection, final PreparedStatement statement,
290
+ final int index, final IRubyObject value,
291
+ final IRubyObject attribute, final int type) throws SQLException {
292
+
293
+ final String typeName = resolveArrayBaseTypeName(context, attribute);
294
+ final RubyArray valueForDB = (RubyArray) value.callMethod(context, "values");
295
+
296
+ Object[] values;
297
+ switch (typeName) {
298
+ case "datetime":
299
+ case "timestamp": {
300
+ values = PgDateTimeUtils.timestampStringArray(context, valueForDB);
301
+ break;
302
+ }
303
+ default:
304
+ values = valueForDB.toArray();
305
+ break;
306
+ }
307
+
308
+ statement.setArray(index, connection.createArrayOf(typeName, values));
309
+ }
310
+
285
311
  @Override
286
312
  protected void setBlobParameter(final ThreadContext context,
287
313
  final Connection connection, final PreparedStatement statement,
@@ -305,47 +331,9 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
305
331
  final Connection connection, final PreparedStatement statement,
306
332
  final int index, IRubyObject value,
307
333
  final IRubyObject attribute, final int type) throws SQLException {
308
-
309
- if ( value instanceof RubyFloat ) {
310
- final double doubleValue = ( (RubyFloat) value ).getValue();
311
- if ( Double.isInfinite(doubleValue) ) {
312
- setTimestampInfinity(statement, index, doubleValue);
313
- return;
314
- }
315
- }
316
-
317
- RubyTime timeValue = toTime(context, value);
318
-
319
- final Timestamp timestamp;
320
-
321
- if (timeValue.getDateTime().getYear() > 0) {
322
- timeValue = timeInDefaultTimeZone(context, timeValue);
323
- DateTime dateTime = timeValue.getDateTime();
324
- timestamp = new Timestamp(dateTime.getMillis());
325
-
326
- if (timeValue.getNSec() > 0) timestamp.setNanos((int) (timestamp.getNanos() + timeValue.getNSec()));
327
-
328
- statement.setTimestamp(index, timestamp, getCalendar(dateTime.getZone()));
329
- }
330
- else {
331
- setTimestampBC(statement, index, timeValue);
332
- }
333
- }
334
-
335
- private static void setTimestampBC(final PreparedStatement statement,
336
- final int index, final RubyTime timeValue) throws SQLException {
337
- DateTime dateTime = timeValue.getDateTime();
338
- @SuppressWarnings("deprecated")
339
- Timestamp timestamp = new Timestamp(dateTime.getYear() - 1900,
340
- dateTime.getMonthOfYear() - 1,
341
- dateTime.getDayOfMonth(),
342
- dateTime.getHourOfDay(),
343
- dateTime.getMinuteOfHour(),
344
- dateTime.getSecondOfMinute(),
345
- dateTime.getMillisOfSecond() * 1_000_000 + (int) timeValue.getNSec()
346
- );
347
-
348
- statement.setObject(index, timestamp);
334
+ // PGJDBC uses strings internally anyway, so using Timestamp doesn't do any good
335
+ String tsString = PgDateTimeUtils.timestampValueToString(context, value, null, true);
336
+ statement.setObject(index, tsString, Types.OTHER);
349
337
  }
350
338
 
351
339
  private static void setTimestampInfinity(final PreparedStatement statement,
@@ -367,7 +355,8 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
367
355
  final int index, IRubyObject value,
368
356
  final IRubyObject attribute, final int type) throws SQLException {
369
357
  // to handle more fractional second precision than (default) 59.123 only
370
- super.setTimestampParameter(context, connection, statement, index, value, attribute, type);
358
+ String timeStr = DateTimeUtils.timeString(context, value, getDefaultTimeZone(context), true);
359
+ statement.setObject(index, timeStr, Types.OTHER);
371
360
  }
372
361
 
373
362
  @Override
@@ -435,8 +424,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
435
424
  break;
436
425
 
437
426
  case "enum":
438
- // FIXME: This doesn't work but it gives a better error message than letting it be treated as a PGobject
439
- statement.setObject(index, value.toString());
427
+ statement.setObject(index, value.toString(), Types.OTHER);
440
428
  break;
441
429
 
442
430
  case "interval":
@@ -497,6 +485,21 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
497
485
  }
498
486
  }
499
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
+
500
503
  // The tests won't start if this returns PGpoint[]
501
504
  // it fails with a runtime error: "NativeException: java.lang.reflect.InvocationTargetException: [Lorg/postgresql/geometric/PGpoint"
502
505
  private Object[] convertToPoints(Double[] values) throws SQLException {
@@ -984,4 +987,20 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
984
987
 
985
988
  }
986
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
+ }
987
1006
  }