activerecord-jdbc-adapter 5.0.pre1 → 50.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord-jdbc-adapter might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.gitignore +1 -2
- data/.travis.yml +15 -416
- data/Gemfile +35 -37
- data/README.md +23 -118
- data/RUNNING_TESTS.md +31 -26
- data/Rakefile +2 -3
- data/lib/arjdbc/abstract/connection_management.rb +21 -0
- data/lib/arjdbc/abstract/core.rb +62 -0
- data/lib/arjdbc/abstract/database_statements.rb +46 -0
- data/lib/arjdbc/abstract/statement_cache.rb +58 -0
- data/lib/arjdbc/abstract/transaction_support.rb +86 -0
- data/lib/arjdbc/derby/adapter.rb +6 -1
- data/lib/arjdbc/discover.rb +0 -7
- data/lib/arjdbc/firebird/adapter.rb +2 -2
- data/lib/arjdbc/jdbc.rb +2 -2
- data/lib/arjdbc/jdbc/adapter.rb +10 -252
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/connection.rb +6 -0
- data/lib/arjdbc/mysql/adapter.rb +82 -946
- data/lib/arjdbc/mysql/connection_methods.rb +4 -2
- data/lib/arjdbc/postgresql/adapter.rb +270 -970
- data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
- data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
- data/lib/arjdbc/postgresql/base/pgconn.rb +8 -5
- data/lib/arjdbc/postgresql/column.rb +10 -599
- data/lib/arjdbc/postgresql/connection_methods.rb +9 -0
- data/lib/arjdbc/postgresql/name.rb +24 -0
- data/lib/arjdbc/postgresql/oid_types.rb +28 -109
- data/lib/arjdbc/sqlite3/adapter.rb +18 -42
- data/lib/arjdbc/tasks/database_tasks.rb +1 -3
- data/lib/arjdbc/tasks/db2_database_tasks.rb +2 -2
- data/lib/arjdbc/version.rb +1 -1
- data/pom.xml +3 -3
- data/rakelib/02-test.rake +0 -12
- data/rakelib/compile.rake +1 -1
- data/rakelib/db.rake +7 -5
- data/rakelib/rails.rake +67 -64
- data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +1 -17
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +518 -1260
- data/src/java/arjdbc/mysql/MySQLModule.java +3 -3
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +53 -134
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +214 -240
- data/src/java/arjdbc/sqlite3/SQLite3Module.java +0 -20
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +85 -10
- metadata +16 -29
- data/Appraisals +0 -41
- data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -1
- data/lib/arjdbc/common_jdbc_methods.rb +0 -89
- data/lib/arjdbc/mysql/bulk_change_table.rb +0 -150
- data/lib/arjdbc/mysql/column.rb +0 -162
- data/lib/arjdbc/mysql/explain_support.rb +0 -82
- data/lib/arjdbc/mysql/schema_creation.rb +0 -58
- data/lib/arjdbc/oracle.rb +0 -4
- data/lib/arjdbc/oracle/adapter.rb +0 -952
- data/lib/arjdbc/oracle/column.rb +0 -126
- data/lib/arjdbc/oracle/connection_methods.rb +0 -21
- data/lib/arjdbc/postgresql/base/oid.rb +0 -412
- data/lib/arjdbc/postgresql/base/schema_definitions.rb +0 -131
- data/lib/arjdbc/postgresql/explain_support.rb +0 -53
- data/lib/arjdbc/postgresql/oid/bytea.rb +0 -2
- data/lib/arjdbc/postgresql/schema_creation.rb +0 -60
- data/lib/arjdbc/tasks/oracle/enhanced_structure_dump.rb +0 -297
- data/lib/arjdbc/tasks/oracle_database_tasks.rb +0 -65
- data/src/java/arjdbc/oracle/OracleModule.java +0 -75
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +0 -465
@@ -100,9 +100,9 @@ public class MySQLModule {
|
|
100
100
|
|
101
101
|
final Ruby runtime = context.getRuntime();
|
102
102
|
final RubyString quoted = runtime.newString(quotedBytes);
|
103
|
-
|
104
|
-
|
105
|
-
|
103
|
+
|
104
|
+
quoted.associateEncoding(UTF8Encoding.INSTANCE);
|
105
|
+
|
106
106
|
return quoted;
|
107
107
|
}
|
108
108
|
|
@@ -26,7 +26,6 @@
|
|
26
26
|
package arjdbc.mysql;
|
27
27
|
|
28
28
|
import arjdbc.jdbc.RubyJdbcConnection;
|
29
|
-
import arjdbc.jdbc.Callable;
|
30
29
|
|
31
30
|
import java.lang.reflect.Field;
|
32
31
|
import java.lang.reflect.InvocationTargetException;
|
@@ -43,17 +42,18 @@ import java.util.TimeZone;
|
|
43
42
|
import java.util.regex.Matcher;
|
44
43
|
import java.util.regex.Pattern;
|
45
44
|
|
45
|
+
import org.joda.time.DateTime;
|
46
46
|
import org.jruby.Ruby;
|
47
|
-
import org.jruby.RubyArray;
|
48
47
|
import org.jruby.RubyClass;
|
49
|
-
import org.jruby.RubyFloat;
|
50
48
|
import org.jruby.RubyInteger;
|
51
|
-
import org.jruby.RubyModule;
|
52
49
|
import org.jruby.RubyString;
|
50
|
+
import org.jruby.RubyTime;
|
51
|
+
import org.jruby.anno.JRubyMethod;
|
53
52
|
import org.jruby.exceptions.RaiseException;
|
54
53
|
import org.jruby.runtime.ObjectAllocator;
|
55
54
|
import org.jruby.runtime.ThreadContext;
|
56
55
|
import org.jruby.runtime.builtin.IRubyObject;
|
56
|
+
import org.jruby.util.TypeConverter;
|
57
57
|
|
58
58
|
/**
|
59
59
|
*
|
@@ -79,25 +79,20 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
79
79
|
return clazz;
|
80
80
|
}
|
81
81
|
|
82
|
-
@
|
83
|
-
|
84
|
-
|
85
|
-
return
|
82
|
+
@JRubyMethod
|
83
|
+
public IRubyObject query(final ThreadContext context, final IRubyObject sql) throws SQLException {
|
84
|
+
final String query = sql.convertToString().getUnicodeValue(); // sql
|
85
|
+
return executeUpdate(context, query, false);
|
86
86
|
}
|
87
87
|
|
88
88
|
@Override
|
89
|
-
protected
|
90
|
-
|
91
|
-
final Ruby runtime = context.getRuntime();
|
92
|
-
final IRubyObject key = mapGeneratedKeys(runtime, connection, statement);
|
93
|
-
return ( key == null || key.isNil() ) ? runtime.newFixnum( statement.getUpdateCount() ) : key;
|
89
|
+
protected boolean doExecute(final Statement statement, final String query) throws SQLException {
|
90
|
+
return statement.execute(query, Statement.RETURN_GENERATED_KEYS);
|
94
91
|
}
|
95
92
|
|
96
93
|
@Override
|
97
|
-
protected IRubyObject jdbcToRuby(
|
98
|
-
final
|
99
|
-
final int column, final int type, final ResultSet resultSet)
|
100
|
-
throws SQLException {
|
94
|
+
protected IRubyObject jdbcToRuby(final ThreadContext context, final Ruby runtime,
|
95
|
+
final int column, final int type, final ResultSet resultSet) throws SQLException {
|
101
96
|
if ( type == Types.BIT ) {
|
102
97
|
final int value = resultSet.getInt(column);
|
103
98
|
return resultSet.wasNull() ? runtime.getNil() : runtime.newFixnum(value);
|
@@ -106,53 +101,50 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
106
101
|
}
|
107
102
|
|
108
103
|
@Override // can not use statement.setTimestamp( int, Timestamp, Calendar )
|
109
|
-
protected void setTimestampParameter(
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
else {
|
115
|
-
value = getTimeInDefaultTimeZone(context, value);
|
116
|
-
if ( value instanceof RubyString ) { // yyyy-[m]m-[d]d hh:mm:ss[.f...]
|
117
|
-
final Timestamp timestamp = Timestamp.valueOf( value.toString() );
|
118
|
-
statement.setTimestamp( index, timestamp ); // assume local time-zone
|
119
|
-
}
|
120
|
-
else { // Time or DateTime ( ActiveSupport::TimeWithZone.to_time )
|
121
|
-
final double time = adjustTimeFromDefaultZone(value);
|
122
|
-
final RubyFloat timeValue = context.getRuntime().newFloat( time );
|
123
|
-
statement.setTimestamp( index, convertToTimestamp(timeValue) );
|
124
|
-
}
|
125
|
-
}
|
104
|
+
protected void setTimestampParameter(ThreadContext context, Connection connection, PreparedStatement statement,
|
105
|
+
int index, IRubyObject value, IRubyObject column, int type) throws SQLException {
|
106
|
+
value = callMethod(context, "time_in_default_timezone", value);
|
107
|
+
TypeConverter.checkType(context, value, context.runtime.getTime());
|
108
|
+
setTimestamp(statement, index, (RubyTime) value, type);
|
126
109
|
}
|
127
110
|
|
128
|
-
@Override
|
129
|
-
protected void setTimeParameter(
|
130
|
-
|
131
|
-
|
132
|
-
final IRubyObject column, final int type) throws SQLException {
|
133
|
-
if ( value.isNil() ) statement.setNull(index, Types.TIME);
|
134
|
-
else {
|
135
|
-
value = getTimeInDefaultTimeZone(context, value);
|
136
|
-
if ( value instanceof RubyString ) {
|
137
|
-
final Time time = Time.valueOf( value.toString() );
|
138
|
-
statement.setTime( index, time ); // assume local time-zone
|
139
|
-
}
|
140
|
-
else { // Time or DateTime ( ActiveSupport::TimeWithZone.to_time )
|
141
|
-
final double timeValue = adjustTimeFromDefaultZone(value);
|
142
|
-
final Time time = new Time(( (long) timeValue ) * 1000); // millis
|
143
|
-
// java.sql.Time is expected to be only up to second precision
|
144
|
-
statement.setTime( index, time );
|
145
|
-
}
|
146
|
-
}
|
111
|
+
@Override
|
112
|
+
protected void setTimeParameter(ThreadContext context, Connection connection, PreparedStatement statement,
|
113
|
+
int index, IRubyObject value, IRubyObject column, int type) throws SQLException {
|
114
|
+
setTimestampParameter(context, connection, statement, index, value, column, type);
|
147
115
|
}
|
148
116
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
117
|
+
// FIXME: we should detect adapter and not do this timezone offset calculation is it is jdbc version 6+.
|
118
|
+
private void setTimestamp(PreparedStatement statement, int index, RubyTime value, int type) throws SQLException {
|
119
|
+
DateTime dateTime = value.getDateTime();
|
120
|
+
int offset = TimeZone.getDefault().getOffset(dateTime.getMillis()); // JDBC <6.x ignores time zone info (we adjust manually).
|
121
|
+
Timestamp timestamp = new Timestamp(dateTime.getMillis() - offset);
|
122
|
+
|
123
|
+
// 1942-11-30T01:02:03.123_456
|
124
|
+
if (type != Types.DATE && value.getNSec() >= 0) timestamp.setNanos((int) (timestamp.getNanos() + value.getNSec()));
|
125
|
+
|
126
|
+
statement.setTimestamp(index, timestamp);
|
127
|
+
}
|
128
|
+
|
129
|
+
// FIXME: I think we can unify this back to main adapter code since previous conflict involved not using
|
130
|
+
// the raw string return type and not the extra formatting logic.
|
131
|
+
@Override
|
132
|
+
protected IRubyObject timeToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int column) throws SQLException {
|
133
|
+
Time value = resultSet.getTime(column);
|
134
|
+
|
135
|
+
if (value == null) return resultSet.wasNull() ? runtime.getNil() : runtime.newString();
|
136
|
+
|
137
|
+
String strValue = value.toString();
|
138
|
+
|
139
|
+
// If time is column type but that time had a precision which included
|
140
|
+
// nanoseconds we used timestamp to save the data. Since this is conditional
|
141
|
+
// we grab data a second time as a timestamp to look for nsecs.
|
142
|
+
Timestamp nsecTimeHack = resultSet.getTimestamp(column);
|
143
|
+
if (nsecTimeHack.getNanos() != 0) {
|
144
|
+
strValue = String.format("%s.%09d", strValue, nsecTimeHack.getNanos());
|
145
|
+
}
|
146
|
+
|
147
|
+
return RubyString.newUnicodeString(runtime,strValue);
|
156
148
|
}
|
157
149
|
|
158
150
|
@Override
|
@@ -192,80 +184,7 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
192
184
|
}
|
193
185
|
|
194
186
|
@Override
|
195
|
-
protected
|
196
|
-
return withConnection(context, new Callable<IRubyObject>() {
|
197
|
-
public IRubyObject call(final Connection connection) throws SQLException {
|
198
|
-
final Ruby runtime = context.getRuntime();
|
199
|
-
final RubyModule indexDefinition = getIndexDefinition(runtime);
|
200
|
-
final String jdbcTableName = caseConvertIdentifierForJdbc(connection, tableName);
|
201
|
-
final String jdbcSchemaName = caseConvertIdentifierForJdbc(connection, schemaName);
|
202
|
-
final IRubyObject rubyTableName = RubyString.newUnicodeString(
|
203
|
-
runtime, caseConvertIdentifierForJdbc(connection, tableName)
|
204
|
-
);
|
205
|
-
|
206
|
-
StringBuilder query = new StringBuilder("SHOW KEYS FROM ");
|
207
|
-
if (jdbcSchemaName != null) {
|
208
|
-
query.append(jdbcSchemaName).append(".");
|
209
|
-
}
|
210
|
-
query.append(jdbcTableName);
|
211
|
-
query.append(" WHERE key_name != 'PRIMARY'");
|
212
|
-
|
213
|
-
final RubyArray indexes = runtime.newArray(8);
|
214
|
-
PreparedStatement statement = null;
|
215
|
-
ResultSet keySet = null;
|
216
|
-
|
217
|
-
try {
|
218
|
-
statement = connection.prepareStatement(query.toString());
|
219
|
-
keySet = statement.executeQuery();
|
220
|
-
|
221
|
-
String currentKeyName = null;
|
222
|
-
|
223
|
-
while ( keySet.next() ) {
|
224
|
-
final String keyName = caseConvertIdentifierForRails(connection, keySet.getString("key_name"));
|
225
|
-
|
226
|
-
if ( ! keyName.equals(currentKeyName) ) {
|
227
|
-
currentKeyName = keyName;
|
228
|
-
|
229
|
-
final boolean nonUnique = keySet.getBoolean("non_unique");
|
230
|
-
|
231
|
-
IRubyObject[] args = new IRubyObject[] {
|
232
|
-
rubyTableName, // table_name
|
233
|
-
RubyString.newUnicodeString(runtime, keyName), // index_name
|
234
|
-
runtime.newBoolean( ! nonUnique ), // unique
|
235
|
-
runtime.newArray(), // [] for column names, we'll add to that in just a bit
|
236
|
-
runtime.newArray() // lengths
|
237
|
-
};
|
238
|
-
|
239
|
-
indexes.append( indexDefinition.callMethod(context, "new", args) ); // IndexDefinition.new
|
240
|
-
}
|
241
|
-
|
242
|
-
IRubyObject lastIndexDef = indexes.isEmpty() ? null : indexes.entry(-1);
|
243
|
-
if ( lastIndexDef != null ) {
|
244
|
-
final String columnName = caseConvertIdentifierForRails(connection, keySet.getString("column_name"));
|
245
|
-
final int length = keySet.getInt("sub_part");
|
246
|
-
final boolean nullLength = keySet.wasNull();
|
247
|
-
|
248
|
-
lastIndexDef.callMethod(context, "columns").callMethod(context,
|
249
|
-
"<<", RubyString.newUnicodeString(runtime, columnName));
|
250
|
-
lastIndexDef.callMethod(context, "lengths").callMethod(context,
|
251
|
-
"<<", nullLength ? runtime.getNil() : runtime.newFixnum(length));
|
252
|
-
}
|
253
|
-
}
|
254
|
-
|
255
|
-
return indexes;
|
256
|
-
}
|
257
|
-
finally {
|
258
|
-
close(keySet);
|
259
|
-
close(statement);
|
260
|
-
}
|
261
|
-
}
|
262
|
-
});
|
263
|
-
}
|
264
|
-
|
265
|
-
@Override
|
266
|
-
protected String caseConvertIdentifierForRails(final Connection connection, final String value)
|
267
|
-
throws SQLException {
|
268
|
-
if ( value == null ) return null;
|
187
|
+
protected String caseConvertIdentifierForRails(Connection connection, String value) throws SQLException {
|
269
188
|
return value; // MySQL does not storesUpperCaseIdentifiers() :
|
270
189
|
}
|
271
190
|
|
@@ -27,6 +27,7 @@ package arjdbc.postgresql;
|
|
27
27
|
|
28
28
|
import java.io.ByteArrayInputStream;
|
29
29
|
import java.io.InputStream;
|
30
|
+
import java.lang.StringBuilder;
|
30
31
|
import java.sql.Array;
|
31
32
|
import java.sql.Connection;
|
32
33
|
import java.sql.PreparedStatement;
|
@@ -35,8 +36,10 @@ import java.sql.SQLException;
|
|
35
36
|
import java.sql.Statement;
|
36
37
|
import java.sql.Timestamp;
|
37
38
|
import java.sql.Types;
|
39
|
+
import java.util.HashMap;
|
38
40
|
import java.util.Map;
|
39
41
|
import java.util.UUID;
|
42
|
+
import java.util.regex.Pattern;
|
40
43
|
|
41
44
|
import org.jruby.Ruby;
|
42
45
|
import org.jruby.RubyArray;
|
@@ -46,6 +49,7 @@ import org.jruby.RubyFixnum;
|
|
46
49
|
import org.jruby.RubyFloat;
|
47
50
|
import org.jruby.RubyHash;
|
48
51
|
import org.jruby.RubyIO;
|
52
|
+
import org.jruby.RubyModule;
|
49
53
|
import org.jruby.RubyString;
|
50
54
|
import org.jruby.anno.JRubyMethod;
|
51
55
|
import org.jruby.javasupport.JavaUtil;
|
@@ -57,7 +61,9 @@ import org.jruby.util.ByteList;
|
|
57
61
|
import org.postgresql.PGConnection;
|
58
62
|
import org.postgresql.PGStatement;
|
59
63
|
import org.postgresql.core.BaseConnection;
|
60
|
-
import org.postgresql.
|
64
|
+
import org.postgresql.geometric.PGline;
|
65
|
+
import org.postgresql.geometric.PGlseg;
|
66
|
+
import org.postgresql.geometric.PGpoint;
|
61
67
|
import org.postgresql.util.PGInterval;
|
62
68
|
import org.postgresql.util.PGobject;
|
63
69
|
|
@@ -67,6 +73,52 @@ import org.postgresql.util.PGobject;
|
|
67
73
|
*/
|
68
74
|
public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection {
|
69
75
|
private static final long serialVersionUID = 7235537759545717760L;
|
76
|
+
private static final int HSTORE_TYPE = 100000 + 1111;
|
77
|
+
private static final Pattern binaryStringPattern = Pattern.compile("^[01]+$");
|
78
|
+
private static final Pattern uuidPattern = Pattern.compile("^\\p{XDigit}{8}-(?:\\p{XDigit}{4}-){3}\\p{XDigit}{12}$");
|
79
|
+
|
80
|
+
private static final String[] binaryStrings = {
|
81
|
+
"0000",
|
82
|
+
"0001",
|
83
|
+
"0010",
|
84
|
+
"0011",
|
85
|
+
"0100",
|
86
|
+
"0101",
|
87
|
+
"0110",
|
88
|
+
"0111",
|
89
|
+
"1000",
|
90
|
+
"1001",
|
91
|
+
"1010",
|
92
|
+
"1011",
|
93
|
+
"1100",
|
94
|
+
"1101",
|
95
|
+
"1110",
|
96
|
+
"1111"
|
97
|
+
};
|
98
|
+
|
99
|
+
private static final Map<String, Integer> POSTGRES_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
|
100
|
+
static {
|
101
|
+
POSTGRES_JDBC_TYPE_FOR.put("bit", Types.OTHER);
|
102
|
+
POSTGRES_JDBC_TYPE_FOR.put("bit_varying", Types.OTHER);
|
103
|
+
POSTGRES_JDBC_TYPE_FOR.put("citext", Types.OTHER);
|
104
|
+
POSTGRES_JDBC_TYPE_FOR.put("daterange", Types.OTHER);
|
105
|
+
POSTGRES_JDBC_TYPE_FOR.put("hstore", Types.OTHER);
|
106
|
+
POSTGRES_JDBC_TYPE_FOR.put("int4range", Types.OTHER);
|
107
|
+
POSTGRES_JDBC_TYPE_FOR.put("int8range", Types.OTHER);
|
108
|
+
POSTGRES_JDBC_TYPE_FOR.put("interval", Types.OTHER);
|
109
|
+
POSTGRES_JDBC_TYPE_FOR.put("json", Types.OTHER);
|
110
|
+
POSTGRES_JDBC_TYPE_FOR.put("jsonb", Types.OTHER);
|
111
|
+
POSTGRES_JDBC_TYPE_FOR.put("line", Types.OTHER);
|
112
|
+
POSTGRES_JDBC_TYPE_FOR.put("lseg", Types.OTHER);
|
113
|
+
POSTGRES_JDBC_TYPE_FOR.put("ltree", Types.OTHER);
|
114
|
+
POSTGRES_JDBC_TYPE_FOR.put("numrange", Types.OTHER);
|
115
|
+
POSTGRES_JDBC_TYPE_FOR.put("point", Types.OTHER);
|
116
|
+
POSTGRES_JDBC_TYPE_FOR.put("tsrange", Types.OTHER);
|
117
|
+
POSTGRES_JDBC_TYPE_FOR.put("tstzrange", Types.OTHER);
|
118
|
+
POSTGRES_JDBC_TYPE_FOR.put("tsvector", Types.OTHER);
|
119
|
+
POSTGRES_JDBC_TYPE_FOR.put("uuid", Types.OTHER);
|
120
|
+
}
|
121
|
+
|
70
122
|
|
71
123
|
protected PostgreSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
|
72
124
|
super(runtime, metaClass);
|
@@ -128,6 +180,20 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
128
180
|
return value;
|
129
181
|
}
|
130
182
|
|
183
|
+
@Override
|
184
|
+
protected String internedTypeFor(final ThreadContext context, final IRubyObject attribute) throws SQLException {
|
185
|
+
|
186
|
+
final RubyModule postgreSQL = (RubyModule) getConnectionAdapters(context.runtime).getConstant("PostgreSQL");
|
187
|
+
final RubyModule oid = (RubyModule) postgreSQL.getConstant("OID");
|
188
|
+
final RubyClass arrayClass = oid.getClass("Array");
|
189
|
+
|
190
|
+
if ( arrayClass.isInstance(attributeType(context, attribute)) ) {
|
191
|
+
return "array";
|
192
|
+
}
|
193
|
+
|
194
|
+
return super.internedTypeFor(context, attribute);
|
195
|
+
}
|
196
|
+
|
131
197
|
@Override
|
132
198
|
protected Connection newConnection() throws SQLException {
|
133
199
|
final Connection connection = getConnectionFactory().newConnection();
|
@@ -147,43 +213,27 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
147
213
|
return connection;
|
148
214
|
}
|
149
215
|
|
150
|
-
@Override // due statement.setNull(index, Types.BLOB) not working :
|
151
|
-
// org.postgresql.util.PSQLException: ERROR: column "sample_binary" is of type bytea but expression is of type oid
|
152
|
-
protected void setBlobParameter(final ThreadContext context,
|
153
|
-
final Connection connection, final PreparedStatement statement,
|
154
|
-
final int index, final Object value,
|
155
|
-
final IRubyObject column, final int type) throws SQLException {
|
156
|
-
if ( value instanceof IRubyObject ) {
|
157
|
-
setBlobParameter(context, connection, statement, index, (IRubyObject) value, column, type);
|
158
|
-
}
|
159
|
-
else {
|
160
|
-
if ( value == null ) statement.setNull(index, Types.BINARY);
|
161
|
-
else {
|
162
|
-
statement.setBinaryStream(index, (InputStream) value);
|
163
|
-
}
|
164
|
-
}
|
165
|
-
}
|
166
|
-
|
167
216
|
@Override // due statement.setNull(index, Types.BLOB) not working :
|
168
217
|
// org.postgresql.util.PSQLException: ERROR: column "sample_binary" is of type bytea but expression is of type oid
|
169
218
|
protected void setBlobParameter(final ThreadContext context,
|
170
219
|
final Connection connection, final PreparedStatement statement,
|
171
220
|
final int index, final IRubyObject value,
|
172
|
-
final IRubyObject
|
221
|
+
final IRubyObject attribute, final int type) throws SQLException {
|
222
|
+
|
223
|
+
// TODO: Somewhere in the process of storing binary data, we lose about 50 bytes in one of the tests
|
224
|
+
|
173
225
|
if ( value.isNil() ) {
|
174
226
|
statement.setNull(index, Types.BINARY);
|
175
227
|
}
|
176
|
-
else {
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
);
|
186
|
-
}
|
228
|
+
else if ( value instanceof RubyIO ) { // IO/File
|
229
|
+
statement.setBinaryStream(index, ((RubyIO) value).getInStream());
|
230
|
+
}
|
231
|
+
else { // should be a RubyString
|
232
|
+
final ByteList blob = value.asString().getByteList();
|
233
|
+
statement.setBinaryStream(index,
|
234
|
+
new ByteArrayInputStream(blob.unsafeBytes(), blob.getBegin(), blob.getRealSize()),
|
235
|
+
blob.getRealSize() // length
|
236
|
+
);
|
187
237
|
}
|
188
238
|
}
|
189
239
|
|
@@ -211,250 +261,178 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
211
261
|
super.setTimestampParameter(context, connection, statement, index, value, column, type);
|
212
262
|
}
|
213
263
|
|
214
|
-
private static final ByteList INTERVAL =
|
215
|
-
new ByteList( new byte[] { 'i','n','t','e','r','v','a','l' }, false );
|
216
|
-
|
217
|
-
private static final ByteList ARRAY_END = new ByteList( new byte[] { '[',']' }, false );
|
218
|
-
|
219
264
|
@Override
|
220
|
-
protected void
|
265
|
+
protected void setObjectParameter(final ThreadContext context,
|
221
266
|
final Connection connection, final PreparedStatement statement,
|
222
|
-
final int index,
|
223
|
-
final IRubyObject
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
267
|
+
final int index, IRubyObject value,
|
268
|
+
final IRubyObject attribute, final int type) throws SQLException {
|
269
|
+
|
270
|
+
final String columnType = attributeSQLType(context, attribute).asJavaString();
|
271
|
+
|
272
|
+
switch ( columnType ) {
|
273
|
+
case "bit":
|
274
|
+
case "bit_varying":
|
275
|
+
setBitStringParameter(statement, index, value);
|
276
|
+
break;
|
277
|
+
|
278
|
+
case "cidr":
|
279
|
+
case "citext":
|
280
|
+
case "hstore":
|
281
|
+
case "inet":
|
282
|
+
case "ltree":
|
283
|
+
case "macaddr":
|
284
|
+
case "tsvector":
|
285
|
+
setPGobjectParameter(statement, index, value, columnType);
|
286
|
+
break;
|
287
|
+
|
288
|
+
case "interval":
|
289
|
+
statement.setObject(index, new PGInterval(value.toString()));
|
290
|
+
break;
|
291
|
+
|
292
|
+
case "json":
|
293
|
+
case "jsonb":
|
294
|
+
setJsonParameter(context, statement, index, value, columnType);
|
295
|
+
break;
|
296
|
+
|
297
|
+
case "line":
|
298
|
+
statement.setObject(index, new PGline(value.toString()));
|
299
|
+
break;
|
300
|
+
|
301
|
+
case "lseg":
|
302
|
+
statement.setObject(index, new PGlseg(value.toString()));
|
303
|
+
break;
|
304
|
+
|
305
|
+
case "point":
|
306
|
+
statement.setObject(index, new PGpoint(value.toString()));
|
307
|
+
break;
|
308
|
+
|
309
|
+
case "uuid":
|
310
|
+
setUUIDParameter(statement, index, value);
|
311
|
+
break;
|
312
|
+
|
313
|
+
default:
|
314
|
+
if ( columnType.endsWith("range") ) {
|
315
|
+
setRangeParameter(context, statement, index, value, columnType);
|
248
316
|
}
|
249
|
-
|
250
|
-
|
317
|
+
else {
|
318
|
+
super.setObjectParameter(context, connection, statement, index, value, attribute, type);
|
251
319
|
}
|
252
|
-
}
|
253
|
-
statement.setString( index, valueStr );
|
254
320
|
}
|
255
321
|
}
|
256
322
|
|
257
|
-
|
258
|
-
|
259
|
-
IRubyObject
|
260
|
-
if ( oid == null || oid.isNil() ) { // only for user instantiated Column
|
261
|
-
throw new IllegalStateException("missing @oid for column: " + column.inspect());
|
262
|
-
}
|
263
|
-
return RubyFixnum.fix2int(oid);
|
264
|
-
}
|
265
|
-
|
266
|
-
@Override
|
267
|
-
protected void setObjectParameter(final ThreadContext context,
|
268
|
-
final Connection connection, final PreparedStatement statement,
|
269
|
-
final int index, Object value,
|
270
|
-
final IRubyObject column, final int type) throws SQLException {
|
271
|
-
|
272
|
-
final String columnType = column.callMethod(context, "type").asJavaString();
|
273
|
-
|
274
|
-
if ( columnType == (Object) "uuid" ) {
|
275
|
-
setUUIDParameter(statement, index, value);
|
276
|
-
return;
|
277
|
-
}
|
278
|
-
|
279
|
-
if ( columnType == (Object) "json" ) {
|
280
|
-
setJsonParameter(context, statement, index, value, column);
|
281
|
-
return;
|
282
|
-
}
|
283
|
-
|
284
|
-
if ( columnType == (Object) "tsvector" ) {
|
285
|
-
setTsVectorParameter(statement, index, value);
|
286
|
-
return;
|
287
|
-
}
|
288
|
-
|
289
|
-
if ( columnType == (Object) "cidr" || columnType == (Object) "inet"
|
290
|
-
|| columnType == (Object) "macaddr" ) {
|
291
|
-
setAddressParameter(context, statement, index, value, column, columnType);
|
292
|
-
return;
|
293
|
-
}
|
294
|
-
|
295
|
-
if ( columnType != null && columnType.endsWith("range") ) {
|
296
|
-
setRangeParameter(context, statement, index, value, column, columnType);
|
297
|
-
return;
|
298
|
-
}
|
323
|
+
// value should be a ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bit::Data
|
324
|
+
private void setBitStringParameter(final PreparedStatement statement, final int index,
|
325
|
+
final IRubyObject value) throws SQLException {
|
299
326
|
|
300
|
-
|
301
|
-
|
327
|
+
String valueForDB = value.toString();
|
328
|
+
int length = valueForDB.length();
|
302
329
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
330
|
+
/*
|
331
|
+
This means that if somebody sets their binary string to be "111" it
|
332
|
+
will always be assumed to be in binary. This matches the AR (5.0) functionality.
|
333
|
+
If it is meant to be a hex string they should use "0x111".
|
334
|
+
*/
|
335
|
+
if (length > 0 && !binaryStringPattern.matcher(valueForDB).matches()) {
|
336
|
+
StringBuilder builder = new StringBuilder(length * 4);
|
337
|
+
for (int i = 0; i < length; i++) {
|
338
|
+
builder.append(binaryStrings[Character.digit(valueForDB.charAt(i), 16)]);
|
311
339
|
}
|
312
|
-
|
313
|
-
else if ( value == null ) {
|
314
|
-
statement.setNull(index, Types.OTHER); return;
|
340
|
+
valueForDB = builder.toString();
|
315
341
|
}
|
316
342
|
|
317
|
-
|
318
|
-
statement.setObject(index, uuid);
|
343
|
+
setPGobjectParameter(statement, index, valueForDB, "bit");
|
319
344
|
}
|
320
345
|
|
321
346
|
private void setJsonParameter(final ThreadContext context,
|
322
347
|
final PreparedStatement statement, final int index,
|
323
|
-
|
324
|
-
|
325
|
-
if ( value instanceof IRubyObject ) {
|
326
|
-
final IRubyObject rubyValue = (IRubyObject) value;
|
327
|
-
if ( rubyValue.isNil() ) {
|
328
|
-
statement.setNull(index, Types.OTHER); return;
|
329
|
-
}
|
330
|
-
if (!isAr42(column)) { // Value has already been cast for AR42
|
331
|
-
value = column.getMetaClass().callMethod(context, "json_to_string", rubyValue);
|
332
|
-
}
|
333
|
-
}
|
334
|
-
else if ( value == null ) {
|
335
|
-
statement.setNull(index, Types.OTHER); return;
|
336
|
-
}
|
348
|
+
final IRubyObject value, final String columnType) throws SQLException {
|
337
349
|
|
338
350
|
final PGobject pgJson = new PGobject();
|
339
|
-
pgJson.setType(
|
351
|
+
pgJson.setType(columnType);
|
340
352
|
pgJson.setValue(value.toString());
|
341
353
|
statement.setObject(index, pgJson);
|
342
354
|
}
|
343
355
|
|
344
|
-
private void
|
345
|
-
final
|
346
|
-
Object value) throws SQLException {
|
347
|
-
|
348
|
-
if ( value instanceof IRubyObject ) {
|
349
|
-
final IRubyObject rubyValue = (IRubyObject) value;
|
350
|
-
if ( rubyValue.isNil() ) {
|
351
|
-
statement.setNull(index, Types.OTHER); return;
|
352
|
-
}
|
353
|
-
}
|
354
|
-
else if ( value == null ) {
|
355
|
-
statement.setNull(index, Types.OTHER); return;
|
356
|
-
}
|
357
|
-
|
358
|
-
final PGobject pgTsVector = new PGobject();
|
359
|
-
pgTsVector.setType("tsvector");
|
360
|
-
pgTsVector.setValue(value.toString());
|
361
|
-
statement.setObject(index, pgTsVector);
|
362
|
-
}
|
363
|
-
|
364
|
-
private void setAddressParameter(final ThreadContext context,
|
365
|
-
final PreparedStatement statement, final int index,
|
366
|
-
Object value, final IRubyObject column,
|
367
|
-
final String columnType) throws SQLException {
|
368
|
-
|
369
|
-
if ( value instanceof IRubyObject ) {
|
370
|
-
final IRubyObject rubyValue = (IRubyObject) value;
|
371
|
-
if ( rubyValue.isNil() ) {
|
372
|
-
statement.setNull(index, Types.OTHER); return;
|
373
|
-
}
|
374
|
-
if (!isAr42(column)) { // Value has already been cast for AR42
|
375
|
-
value = column.getMetaClass().callMethod(context, "cidr_to_string", rubyValue);
|
376
|
-
}
|
377
|
-
}
|
378
|
-
else if ( value == null ) {
|
379
|
-
statement.setNull(index, Types.OTHER); return;
|
380
|
-
}
|
356
|
+
private void setPGobjectParameter(final PreparedStatement statement, final int index,
|
357
|
+
final Object value, final String columnType) throws SQLException {
|
381
358
|
|
382
|
-
final PGobject
|
383
|
-
|
384
|
-
|
385
|
-
statement.setObject(index,
|
359
|
+
final PGobject param = new PGobject();
|
360
|
+
param.setType(columnType);
|
361
|
+
param.setValue(value.toString());
|
362
|
+
statement.setObject(index, param);
|
386
363
|
}
|
387
364
|
|
388
365
|
private void setRangeParameter(final ThreadContext context,
|
389
366
|
final PreparedStatement statement, final int index,
|
390
|
-
final
|
391
|
-
final String columnType) throws SQLException {
|
367
|
+
final IRubyObject value, final String columnType) throws SQLException {
|
392
368
|
|
393
|
-
final String rangeValue;
|
369
|
+
final String rangeValue = value.toString();
|
370
|
+
final Object pgRange;
|
394
371
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
372
|
+
switch ( columnType ) {
|
373
|
+
case "daterange":
|
374
|
+
pgRange = new DateRangeType(rangeValue);
|
375
|
+
break;
|
376
|
+
case "tsrange":
|
377
|
+
pgRange = new TsRangeType(rangeValue);
|
378
|
+
break;
|
379
|
+
case "tstzrange":
|
380
|
+
pgRange = new TstzRangeType(rangeValue);
|
381
|
+
break;
|
382
|
+
case "int4range":
|
383
|
+
pgRange = new Int4RangeType(rangeValue);
|
384
|
+
break;
|
385
|
+
case "int8range":
|
386
|
+
pgRange = new Int8RangeType(rangeValue);
|
387
|
+
break;
|
388
|
+
default:
|
389
|
+
pgRange = new NumRangeType(rangeValue);
|
407
390
|
}
|
408
391
|
|
409
|
-
final Object pgRange;
|
410
|
-
if ( columnType == (Object) "daterange" ) {
|
411
|
-
pgRange = new DateRangeType(rangeValue);
|
412
|
-
}
|
413
|
-
else if ( columnType == (Object) "tsrange" ) {
|
414
|
-
pgRange = new TsRangeType(rangeValue);
|
415
|
-
}
|
416
|
-
else if ( columnType == (Object) "tstzrange" ) {
|
417
|
-
pgRange = new TstzRangeType(rangeValue);
|
418
|
-
}
|
419
|
-
else if ( columnType == (Object) "int4range" ) {
|
420
|
-
pgRange = new Int4RangeType(rangeValue);
|
421
|
-
}
|
422
|
-
else if ( columnType == (Object) "int8range" ) {
|
423
|
-
pgRange = new Int8RangeType(rangeValue);
|
424
|
-
}
|
425
|
-
else { // if ( columnType == (Object) "numrange" )
|
426
|
-
pgRange = new NumRangeType(rangeValue);
|
427
|
-
}
|
428
392
|
statement.setObject(index, pgRange);
|
429
393
|
}
|
430
394
|
|
431
395
|
@Override
|
432
|
-
protected
|
433
|
-
final
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
if (
|
438
|
-
|
396
|
+
protected void setStringParameter(final ThreadContext context,
|
397
|
+
final Connection connection, final PreparedStatement statement,
|
398
|
+
final int index, final IRubyObject value,
|
399
|
+
final IRubyObject attribute, final int type) throws SQLException {
|
400
|
+
|
401
|
+
if ( attributeSQLType(context, attribute).isNil() ) {
|
402
|
+
/*
|
403
|
+
We have to check for a uuid here because in some cases
|
404
|
+
(for example, when doing "exists?" checks, or with legacy binds)
|
405
|
+
ActiveRecord doesn't send us the actual type of the attribute
|
406
|
+
and Postgres won't compare a uuid column with a string
|
407
|
+
*/
|
408
|
+
final String uuid = value.toString();
|
409
|
+
|
410
|
+
// Checking the length so we don't have the overhead of the regex unless it "looks" like a UUID
|
411
|
+
if ( uuid.length() == 36 && uuidPattern.matcher(uuid).matches() ) {
|
412
|
+
setUUIDParameter(statement, index, value);
|
413
|
+
return;
|
414
|
+
}
|
415
|
+
}
|
416
|
+
|
417
|
+
super.setStringParameter(context, connection, statement, index, value, attribute, type);
|
439
418
|
}
|
440
419
|
|
441
|
-
private
|
420
|
+
private void setUUIDParameter(final PreparedStatement statement,
|
421
|
+
final int index, final IRubyObject value) throws SQLException {
|
422
|
+
|
423
|
+
statement.setObject(index, UUID.fromString(value.toString()));
|
424
|
+
}
|
442
425
|
|
443
426
|
@Override
|
444
|
-
protected
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
if (
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
if ( "hstore" == (Object) columnType.asJavaString() ) {
|
454
|
-
return HSTORE_TYPE;
|
455
|
-
}
|
456
|
-
} */
|
457
|
-
return type;
|
427
|
+
protected Integer jdbcTypeFor(final String type) {
|
428
|
+
|
429
|
+
Integer typeValue = POSTGRES_JDBC_TYPE_FOR.get(type);
|
430
|
+
|
431
|
+
if ( typeValue != null ) {
|
432
|
+
return typeValue;
|
433
|
+
}
|
434
|
+
|
435
|
+
return super.jdbcTypeFor(type);
|
458
436
|
}
|
459
437
|
|
460
438
|
/**
|
@@ -498,12 +476,8 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
498
476
|
if ( rawDateTime != null && rawDateTime.booleanValue() ) return strValue;
|
499
477
|
|
500
478
|
final IRubyObject adapter = callMethod(context, "adapter"); // self.adapter
|
501
|
-
if ( usesType(runtime) ) {
|
502
|
-
return typeCastFromDatabase(context, adapter, runtime.newSymbol("timestamp"), strValue);
|
503
|
-
}
|
504
479
|
|
505
|
-
|
506
|
-
return adapter.callMethod(context, "_string_to_timestamp", strValue);
|
480
|
+
return typeCastFromDatabase(context, adapter, runtime.newSymbol("timestamp"), strValue);
|
507
481
|
}
|
508
482
|
|
509
483
|
@Override
|