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.
- checksums.yaml +5 -5
- data/.travis.yml +34 -15
- data/Gemfile +1 -2
- data/README.md +10 -3
- data/lib/arjdbc/abstract/core.rb +12 -2
- data/lib/arjdbc/abstract/database_statements.rb +1 -1
- data/lib/arjdbc/abstract/statement_cache.rb +4 -4
- data/lib/arjdbc/db2/adapter.rb +68 -60
- data/lib/arjdbc/db2/as400.rb +12 -0
- data/lib/arjdbc/db2/column.rb +3 -0
- data/lib/arjdbc/db2/connection_methods.rb +4 -0
- data/lib/arjdbc/jdbc.rb +3 -0
- data/lib/arjdbc/jdbc/adapter.rb +0 -6
- data/lib/arjdbc/jdbc/column.rb +4 -2
- data/lib/arjdbc/mysql/adapter.rb +8 -0
- data/lib/arjdbc/postgresql/adapter.rb +5 -72
- data/lib/arjdbc/postgresql/oid_types.rb +82 -14
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/rails.rake +4 -3
- data/src/java/arjdbc/ArJdbcModule.java +5 -15
- data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +2 -2
- data/src/java/arjdbc/jdbc/ConnectionFactory.java +0 -87
- data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +0 -1
- data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +46 -18
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +2 -2
- data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
- data/src/java/arjdbc/postgresql/PostgreSQLResult.java +90 -17
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +68 -49
- data/src/java/arjdbc/util/DateTimeUtils.java +119 -0
- data/src/java/arjdbc/util/PG.java +8 -0
- data/src/java/arjdbc/util/QuotingUtils.java +6 -7
- metadata +6 -4
- 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
|
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
|
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
|
-
|
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 =
|
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
|
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
|
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 =
|
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
|
111
|
-
"MySQL adapter requires driver >= 5.0 got: " + major + "." + minor + ""
|
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
|
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
|
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 =
|
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
|
-
|
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.
|
86
|
-
runtime.newFixnum(
|
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,
|
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
|
-
|
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
|
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
|
-
|
310
|
-
|
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
|
-
|
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
|
-
|
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
|
}
|