activerecord-jdbc-adapter 52.1-java → 52.2-java

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