activerecord-jdbc-adapter 5.0.pre1 → 50.0
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.
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
|