activerecord-jdbc-alt-adapter 51.7.0-java → 52.2.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -3
- data/.travis.yml +24 -28
- data/Gemfile +2 -3
- data/README.md +27 -19
- data/Rakefile +4 -30
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/activerecord-jdbc-alt-adapter.gemspec +4 -4
- data/lib/arel/visitors/sqlserver.rb +2 -1
- data/lib/arjdbc/abstract/core.rb +12 -2
- data/lib/arjdbc/abstract/database_statements.rb +22 -8
- data/lib/arjdbc/abstract/statement_cache.rb +1 -0
- data/lib/arjdbc/db2/adapter.rb +77 -8
- 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/adapter.rb +4 -6
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/column.rb +14 -6
- data/lib/arjdbc/jdbc.rb +7 -0
- data/lib/arjdbc/mssql/adapter.rb +20 -32
- data/lib/arjdbc/mssql/connection_methods.rb +1 -5
- data/lib/arjdbc/mssql/database_limits.rb +29 -0
- data/lib/arjdbc/mssql/database_statements.rb +41 -1
- data/lib/arjdbc/mssql/explain_support.rb +6 -1
- data/lib/arjdbc/mssql/quoting.rb +9 -2
- data/lib/arjdbc/mssql/schema_creation.rb +2 -1
- data/lib/arjdbc/mssql/schema_dumper.rb +1 -1
- data/lib/arjdbc/mssql/schema_statements.rb +11 -3
- data/lib/arjdbc/mssql/transaction.rb +3 -3
- data/lib/arjdbc/mssql.rb +1 -1
- data/lib/arjdbc/mysql/adapter.rb +20 -4
- data/lib/arjdbc/mysql/connection_methods.rb +7 -13
- data/lib/arjdbc/postgresql/adapter.rb +23 -20
- data/lib/arjdbc/postgresql/column.rb +3 -6
- data/lib/arjdbc/postgresql/connection_methods.rb +1 -3
- data/lib/arjdbc/postgresql/oid_types.rb +6 -11
- data/lib/arjdbc/sqlite3/adapter.rb +86 -91
- data/lib/arjdbc/sqlite3/connection_methods.rb +0 -1
- data/lib/arjdbc/tasks/database_tasks.rb +1 -0
- data/lib/arjdbc/tasks/sqlite_database_tasks_patch.rb +17 -0
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/01-tomcat.rake +2 -2
- data/rakelib/02-test.rake +2 -0
- data/rakelib/rails.rake +1 -1
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +10 -65
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +38 -282
- data/src/java/arjdbc/postgresql/PostgreSQLResult.java +69 -4
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +69 -13
- data/src/java/arjdbc/util/DateTimeUtils.java +17 -28
- data/src/java/arjdbc/util/PG.java +8 -0
- metadata +15 -12
- data/lib/activerecord-jdbc-alt-adapter.rb +0 -1
@@ -29,33 +29,22 @@ import arjdbc.jdbc.Callable;
|
|
29
29
|
import arjdbc.jdbc.RubyJdbcConnection;
|
30
30
|
import arjdbc.util.DateTimeUtils;
|
31
31
|
|
32
|
-
import java.lang.reflect.InvocationTargetException;
|
33
|
-
import java.lang.reflect.Method;
|
34
32
|
import java.sql.Connection;
|
35
33
|
import java.sql.DatabaseMetaData;
|
36
|
-
import java.sql.Date;
|
37
34
|
import java.sql.PreparedStatement;
|
38
35
|
import java.sql.ResultSet;
|
39
36
|
import java.sql.Savepoint;
|
40
|
-
import java.sql.Statement;
|
41
37
|
import java.sql.SQLException;
|
42
|
-
import java.sql.Timestamp;
|
43
38
|
import java.sql.Types;
|
39
|
+
import java.sql.Timestamp;
|
44
40
|
import java.util.Locale;
|
45
|
-
import java.util.ArrayList;
|
46
|
-
import java.util.HashMap;
|
47
|
-
import java.util.List;
|
48
|
-
import java.util.Map;
|
49
41
|
|
50
|
-
import org.joda.time.DateTime;
|
51
|
-
import org.joda.time.DateTimeZone;
|
52
42
|
import org.jruby.Ruby;
|
53
43
|
import org.jruby.RubyArray;
|
54
44
|
import org.jruby.RubyBoolean;
|
55
45
|
import org.jruby.RubyClass;
|
56
46
|
import org.jruby.RubyString;
|
57
47
|
import org.jruby.RubySymbol;
|
58
|
-
import org.jruby.RubyTime;
|
59
48
|
import org.jruby.anno.JRubyMethod;
|
60
49
|
import org.jruby.runtime.ObjectAllocator;
|
61
50
|
import org.jruby.runtime.ThreadContext;
|
@@ -69,54 +58,6 @@ import org.jruby.util.ByteList;
|
|
69
58
|
public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
70
59
|
private static final long serialVersionUID = -745716565005219263L;
|
71
60
|
|
72
|
-
private static final int DATETIMEOFFSET_TYPE;
|
73
|
-
private static final Method DateTimeOffsetGetMinutesOffsetMethod;
|
74
|
-
private static final Method DateTimeOffsetGetTimestampMethod;
|
75
|
-
private static final Method DateTimeOffsetValueOfMethod;
|
76
|
-
private static final Method PreparedStatementSetDateTimeOffsetMethod;
|
77
|
-
|
78
|
-
private static final Map<String, Integer> MSSQL_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
|
79
|
-
static {
|
80
|
-
|
81
|
-
Class<?> DateTimeOffset;
|
82
|
-
Class<?> MssqlPreparedStatement;
|
83
|
-
Class<?> MssqlTypes;
|
84
|
-
try {
|
85
|
-
DateTimeOffset = Class.forName("microsoft.sql.DateTimeOffset");
|
86
|
-
MssqlPreparedStatement = Class.forName("com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement");
|
87
|
-
MssqlTypes = Class.forName("microsoft.sql.Types");
|
88
|
-
} catch (ClassNotFoundException e) {
|
89
|
-
System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
|
90
|
-
throw new RuntimeException("You must require the Microsoft JDBC driver to use this gem");
|
91
|
-
}
|
92
|
-
|
93
|
-
try {
|
94
|
-
DATETIMEOFFSET_TYPE = MssqlTypes.getField("DATETIMEOFFSET").getInt(null);
|
95
|
-
DateTimeOffsetGetMinutesOffsetMethod = DateTimeOffset.getDeclaredMethod("getMinutesOffset");
|
96
|
-
DateTimeOffsetGetTimestampMethod = DateTimeOffset.getDeclaredMethod("getTimestamp");
|
97
|
-
|
98
|
-
Class<?>[] valueOfArgTypes = { Timestamp.class, int.class };
|
99
|
-
DateTimeOffsetValueOfMethod = DateTimeOffset.getDeclaredMethod("valueOf", valueOfArgTypes);
|
100
|
-
|
101
|
-
Class<?>[] setOffsetArgTypes = { int.class, DateTimeOffset };
|
102
|
-
PreparedStatementSetDateTimeOffsetMethod = MssqlPreparedStatement.getDeclaredMethod("setDateTimeOffset", setOffsetArgTypes);
|
103
|
-
} catch (Exception e) {
|
104
|
-
System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
|
105
|
-
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
106
|
-
}
|
107
|
-
|
108
|
-
MSSQL_JDBC_TYPE_FOR.put("binary_basic", Types.BINARY);
|
109
|
-
MSSQL_JDBC_TYPE_FOR.put("image", Types.BINARY);
|
110
|
-
MSSQL_JDBC_TYPE_FOR.put("datetimeoffset", DATETIMEOFFSET_TYPE);
|
111
|
-
MSSQL_JDBC_TYPE_FOR.put("money", Types.DECIMAL);
|
112
|
-
MSSQL_JDBC_TYPE_FOR.put("smalldatetime", Types.TIMESTAMP);
|
113
|
-
MSSQL_JDBC_TYPE_FOR.put("smallmoney", Types.DECIMAL);
|
114
|
-
MSSQL_JDBC_TYPE_FOR.put("ss_timestamp", Types.BINARY);
|
115
|
-
MSSQL_JDBC_TYPE_FOR.put("text_basic", Types.LONGVARCHAR);
|
116
|
-
MSSQL_JDBC_TYPE_FOR.put("uuid", Types.CHAR);
|
117
|
-
MSSQL_JDBC_TYPE_FOR.put("varchar_max", Types.VARCHAR);
|
118
|
-
}
|
119
|
-
|
120
61
|
public MSSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
|
121
62
|
super(runtime, metaClass);
|
122
63
|
}
|
@@ -148,176 +89,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
148
89
|
return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
|
149
90
|
}
|
150
91
|
|
151
|
-
// Support multiple result sets for mssql
|
152
|
-
@Override
|
153
|
-
@JRubyMethod(name = "execute", required = 1)
|
154
|
-
public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
|
155
|
-
final String query = sqlString(sql);
|
156
|
-
return withConnection(context, new Callable<IRubyObject>() {
|
157
|
-
public IRubyObject call(final Connection connection) throws SQLException {
|
158
|
-
Statement statement = null;
|
159
|
-
try {
|
160
|
-
statement = createStatement(context, connection);
|
161
|
-
|
162
|
-
// For DBs that do support multiple statements, lets return the last result set
|
163
|
-
// to be consistent with AR
|
164
|
-
boolean hasResultSet = doExecute(statement, query);
|
165
|
-
int updateCount = statement.getUpdateCount();
|
166
|
-
|
167
|
-
final List<IRubyObject> results = new ArrayList<IRubyObject>();
|
168
|
-
ResultSet resultSet;
|
169
|
-
|
170
|
-
while (hasResultSet || updateCount != -1) {
|
171
|
-
|
172
|
-
if (hasResultSet) {
|
173
|
-
resultSet = statement.getResultSet();
|
174
|
-
|
175
|
-
// Unfortunately the result set gets closed when getMoreResults()
|
176
|
-
// is called, so we have to process the result sets as we get them
|
177
|
-
// this shouldn't be an issue in most cases since we're only getting 1 result set anyways
|
178
|
-
results.add(mapExecuteResult(context, connection, resultSet));
|
179
|
-
} else {
|
180
|
-
results.add(context.runtime.newFixnum(updateCount));
|
181
|
-
}
|
182
|
-
|
183
|
-
// Check to see if there is another result set
|
184
|
-
hasResultSet = statement.getMoreResults();
|
185
|
-
updateCount = statement.getUpdateCount();
|
186
|
-
}
|
187
|
-
|
188
|
-
if (results.size() == 0) {
|
189
|
-
return context.nil; // If no results, return nil
|
190
|
-
} else if (results.size() == 1) {
|
191
|
-
return results.get(0);
|
192
|
-
} else {
|
193
|
-
return context.runtime.newArray(results);
|
194
|
-
}
|
195
|
-
|
196
|
-
} catch (final SQLException e) {
|
197
|
-
debugErrorSQL(context, query);
|
198
|
-
throw e;
|
199
|
-
} finally {
|
200
|
-
close(statement);
|
201
|
-
}
|
202
|
-
}
|
203
|
-
});
|
204
|
-
}
|
205
|
-
|
206
|
-
/**
|
207
|
-
* Executes an INSERT SQL statement
|
208
|
-
* @param context
|
209
|
-
* @param sql
|
210
|
-
* @param pk Rails PK
|
211
|
-
* @return ActiveRecord::Result
|
212
|
-
* @throws SQLException
|
213
|
-
*/
|
214
|
-
@Override
|
215
|
-
@JRubyMethod(name = "execute_insert_pk", required = 2)
|
216
|
-
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
|
217
|
-
|
218
|
-
// MSSQL does not like composite primary keys here so chop it if there is more than one column
|
219
|
-
IRubyObject modifiedPk = pk;
|
220
|
-
|
221
|
-
if (pk instanceof RubyArray) {
|
222
|
-
RubyArray ary = (RubyArray) pk;
|
223
|
-
if (ary.size() > 0) {
|
224
|
-
modifiedPk = ary.eltInternal(0);
|
225
|
-
}
|
226
|
-
}
|
227
|
-
|
228
|
-
return super.execute_insert_pk(context, sql, modifiedPk);
|
229
|
-
}
|
230
|
-
|
231
|
-
/**
|
232
|
-
* Executes an INSERT SQL statement using a prepared statement
|
233
|
-
* @param context
|
234
|
-
* @param sql
|
235
|
-
* @param binds RubyArray of values to be bound to the query
|
236
|
-
* @param pk Rails PK
|
237
|
-
* @return ActiveRecord::Result
|
238
|
-
* @throws SQLException
|
239
|
-
*/
|
240
|
-
@Override
|
241
|
-
@JRubyMethod(name = "execute_insert_pk", required = 3)
|
242
|
-
public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
|
243
|
-
final IRubyObject pk) {
|
244
|
-
// MSSQL does not like composite primary keys here so chop it if there is more than one column
|
245
|
-
IRubyObject modifiedPk = pk;
|
246
|
-
|
247
|
-
if (pk instanceof RubyArray) {
|
248
|
-
RubyArray ary = (RubyArray) pk;
|
249
|
-
if (ary.size() > 0) {
|
250
|
-
modifiedPk = ary.eltInternal(0);
|
251
|
-
}
|
252
|
-
}
|
253
|
-
|
254
|
-
return super.execute_insert_pk(context, sql, binds, modifiedPk);
|
255
|
-
}
|
256
|
-
|
257
|
-
@Override
|
258
|
-
protected Integer jdbcTypeFor(final String type) {
|
259
|
-
|
260
|
-
Integer typeValue = MSSQL_JDBC_TYPE_FOR.get(type);
|
261
|
-
|
262
|
-
if ( typeValue != null ) {
|
263
|
-
return typeValue;
|
264
|
-
}
|
265
|
-
|
266
|
-
return super.jdbcTypeFor(type);
|
267
|
-
}
|
268
|
-
|
269
|
-
// Datetimeoffset values also make it into here
|
270
|
-
@Override
|
271
|
-
protected void setStringParameter(final ThreadContext context, final Connection connection,
|
272
|
-
final PreparedStatement statement, final int index, final IRubyObject value,
|
273
|
-
final IRubyObject attribute, final int type) throws SQLException {
|
274
|
-
|
275
|
-
// datetimeoffset values also make it in here
|
276
|
-
if (type == DATETIMEOFFSET_TYPE) {
|
277
|
-
|
278
|
-
Object dto = convertToDateTimeOffset(context, value);
|
279
|
-
|
280
|
-
try {
|
281
|
-
|
282
|
-
Object[] setStatementArgs = { index, dto };
|
283
|
-
PreparedStatementSetDateTimeOffsetMethod.invoke(statement, setStatementArgs);
|
284
|
-
|
285
|
-
} catch (IllegalAccessException e) {
|
286
|
-
debugMessage(context.runtime, e.getMessage());
|
287
|
-
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
288
|
-
} catch (InvocationTargetException e) {
|
289
|
-
debugMessage(context.runtime, e.getMessage());
|
290
|
-
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
291
|
-
}
|
292
|
-
|
293
|
-
return;
|
294
|
-
}
|
295
|
-
super.setStringParameter(context, connection, statement, index, value, attribute, type);
|
296
|
-
}
|
297
|
-
|
298
|
-
private Object convertToDateTimeOffset(final ThreadContext context, final IRubyObject value) {
|
299
|
-
|
300
|
-
RubyTime time = (RubyTime) value;
|
301
|
-
DateTime dt = time.getDateTime();
|
302
|
-
Timestamp timestamp = new Timestamp(dt.getMillis());
|
303
|
-
timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
|
304
|
-
int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
|
305
|
-
|
306
|
-
try {
|
307
|
-
|
308
|
-
Object[] dtoArgs = { timestamp, offsetMinutes };
|
309
|
-
return DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
|
310
|
-
|
311
|
-
} catch (IllegalAccessException e) {
|
312
|
-
debugMessage(context.runtime, e.getMessage());
|
313
|
-
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
314
|
-
} catch (InvocationTargetException e) {
|
315
|
-
debugMessage(context.runtime, e.getMessage());
|
316
|
-
throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
|
317
|
-
}
|
318
|
-
}
|
319
|
-
|
320
|
-
|
321
92
|
@Override
|
322
93
|
protected RubyArray mapTables(final ThreadContext context, final Connection connection,
|
323
94
|
final String catalog, final String schemaPattern, final String tablePattern,
|
@@ -534,6 +305,29 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
534
305
|
statement.setObject(index, timeStr, Types.NVARCHAR);
|
535
306
|
}
|
536
307
|
|
308
|
+
// Overrides the method in parent, we only remove the savepoint
|
309
|
+
// from the getSavepoints Map
|
310
|
+
@JRubyMethod(name = "release_savepoint", required = 1)
|
311
|
+
public IRubyObject release_savepoint(final ThreadContext context, final IRubyObject name) {
|
312
|
+
if (name == context.nil) throw context.runtime.newArgumentError("nil savepoint name given");
|
313
|
+
|
314
|
+
final Connection connection = getConnection(true);
|
315
|
+
|
316
|
+
Object savepoint = getSavepoints(context).remove(name);
|
317
|
+
|
318
|
+
if (savepoint == null) throw newSavepointNotSetError(context, name, "release");
|
319
|
+
|
320
|
+
// NOTE: RubyHash.remove does not convert to Java as get does :
|
321
|
+
if (!(savepoint instanceof Savepoint)) {
|
322
|
+
savepoint = ((IRubyObject) savepoint).toJava(Savepoint.class);
|
323
|
+
}
|
324
|
+
|
325
|
+
// The 'releaseSavepoint' method is not currently supported
|
326
|
+
// by the Microsoft SQL Server JDBC Driver
|
327
|
+
// connection.releaseSavepoint((Savepoint) savepoint);
|
328
|
+
return context.nil;
|
329
|
+
}
|
330
|
+
|
537
331
|
//----------------------------------------------------------------
|
538
332
|
// read_uncommitted: "READ UNCOMMITTED",
|
539
333
|
// read_committed: "READ COMMITTED",
|
@@ -659,64 +453,16 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
659
453
|
|
660
454
|
/**
|
661
455
|
* Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
|
662
|
-
* Also handle datetimeoffset values here
|
663
456
|
*/
|
664
457
|
@Override
|
665
458
|
protected IRubyObject jdbcToRuby(
|
666
459
|
final ThreadContext context, final Ruby runtime,
|
667
460
|
final int column, int type, final ResultSet resultSet)
|
668
461
|
throws SQLException {
|
669
|
-
|
670
|
-
if (type == DATETIMEOFFSET_TYPE) {
|
671
|
-
|
672
|
-
Object dto = resultSet.getObject(column); // Returns a microsoft.sql.DateTimeOffset
|
673
|
-
|
674
|
-
if (dto == null) return context.nil;
|
675
|
-
|
676
|
-
try {
|
677
|
-
|
678
|
-
int minutes = (int) DateTimeOffsetGetMinutesOffsetMethod.invoke(dto);
|
679
|
-
DateTimeZone zone = DateTimeZone.forOffsetHoursMinutes(minutes / 60, minutes % 60);
|
680
|
-
Timestamp ts = (Timestamp) DateTimeOffsetGetTimestampMethod.invoke(dto);
|
681
|
-
|
682
|
-
int nanos = ts.getNanos(); // max 999-999-999
|
683
|
-
nanos = nanos % 1000000;
|
684
|
-
|
685
|
-
// We have to do this differently than the newTime helper because the Timestamp loses its zone information when passed around
|
686
|
-
DateTime dateTime = new DateTime(ts.getTime(), zone);
|
687
|
-
return RubyTime.newTime(context.runtime, dateTime, nanos);
|
688
|
-
|
689
|
-
} catch (IllegalAccessException e) {
|
690
|
-
debugMessage(runtime, e.getMessage());
|
691
|
-
return context.nil;
|
692
|
-
} catch (InvocationTargetException e) {
|
693
|
-
debugMessage(runtime, e.getMessage());
|
694
|
-
return context.nil;
|
695
|
-
}
|
696
|
-
}
|
697
|
-
|
698
|
-
if (type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR) type = Types.CLOB;
|
462
|
+
if ( type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR ) type = Types.CLOB;
|
699
463
|
return super.jdbcToRuby(context, runtime, column, type, resultSet);
|
700
464
|
}
|
701
465
|
|
702
|
-
/**
|
703
|
-
* Converts a JDBC date object to a Ruby date by referencing Date#civil
|
704
|
-
* @param context current thread context
|
705
|
-
* @param resultSet the jdbc result set to pull the value from
|
706
|
-
* @param index the index of the column to convert
|
707
|
-
* @return RubyNil if NULL or RubyDate if there is a value
|
708
|
-
* @throws SQLException if it fails to retrieve the value from the result set
|
709
|
-
*/
|
710
|
-
@Override
|
711
|
-
protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
|
712
|
-
|
713
|
-
final Date value = resultSet.getDate(index);
|
714
|
-
|
715
|
-
if (value == null) return context.nil;
|
716
|
-
|
717
|
-
return DateTimeUtils.newDate(context, value);
|
718
|
-
}
|
719
|
-
|
720
466
|
@Override
|
721
467
|
protected ColumnData[] extractColumns(final ThreadContext context,
|
722
468
|
final Connection connection, final ResultSet resultSet,
|
@@ -746,9 +492,19 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
|
|
746
492
|
return columns;
|
747
493
|
}
|
748
494
|
|
749
|
-
|
750
|
-
|
751
|
-
|
495
|
+
// internal helper not meant as a "public" API - used in one place thus every
|
496
|
+
@JRubyMethod(name = "jtds_driver?")
|
497
|
+
public RubyBoolean jtds_driver_p(final ThreadContext context) throws SQLException {
|
498
|
+
// "jTDS Type 4 JDBC Driver for MS SQL Server and Sybase"
|
499
|
+
// SQLJDBC: "Microsoft JDBC Driver 4.0 for SQL Server"
|
500
|
+
return withConnection(context, new Callable<RubyBoolean>() {
|
501
|
+
// NOTE: only used in one place for now (on release_savepoint) ...
|
502
|
+
// might get optimized to only happen once since driver won't change
|
503
|
+
public RubyBoolean call(final Connection connection) throws SQLException {
|
504
|
+
final String driver = connection.getMetaData().getDriverName();
|
505
|
+
return context.getRuntime().newBoolean( driver.indexOf("jTDS") >= 0 );
|
506
|
+
}
|
507
|
+
});
|
752
508
|
}
|
753
509
|
|
754
510
|
}
|
@@ -8,11 +8,13 @@ import java.sql.ResultSetMetaData;
|
|
8
8
|
import java.sql.SQLException;
|
9
9
|
import java.sql.Types;
|
10
10
|
|
11
|
+
import arjdbc.util.PG;
|
11
12
|
import org.jruby.Ruby;
|
12
13
|
import org.jruby.RubyArray;
|
13
14
|
import org.jruby.RubyClass;
|
14
15
|
import org.jruby.RubyHash;
|
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;
|
@@ -22,9 +24,11 @@ import org.jruby.runtime.ThreadContext;
|
|
22
24
|
import org.jruby.runtime.builtin.IRubyObject;
|
23
25
|
|
24
26
|
/*
|
25
|
-
* 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
|
26
29
|
*/
|
27
30
|
public class PostgreSQLResult extends JdbcResult {
|
31
|
+
private RubyArray fields = null; // lazily created if PG fields method is called.
|
28
32
|
|
29
33
|
// These are needed when generating an AR::Result
|
30
34
|
private final ResultSetMetaData resultSetMetaData;
|
@@ -107,7 +111,7 @@ public class PostgreSQLResult extends JdbcResult {
|
|
107
111
|
* @param block which may handle each result
|
108
112
|
* @return this object or RubyNil
|
109
113
|
*/
|
110
|
-
@JRubyMethod
|
114
|
+
@PG @JRubyMethod
|
111
115
|
public IRubyObject each(ThreadContext context, Block block) {
|
112
116
|
// At this point we don't support calling this without a block
|
113
117
|
if (block.isGiven()) {
|
@@ -139,7 +143,7 @@ public class PostgreSQLResult extends JdbcResult {
|
|
139
143
|
* @param context current thread contect
|
140
144
|
* @return <code>Fixnum</code>
|
141
145
|
*/
|
142
|
-
@JRubyMethod
|
146
|
+
@PG @JRubyMethod(name = {"length", "ntuples", "num_tuples"})
|
143
147
|
public IRubyObject length(final ThreadContext context) {
|
144
148
|
return values.length();
|
145
149
|
}
|
@@ -185,8 +189,69 @@ public class PostgreSQLResult extends JdbcResult {
|
|
185
189
|
* This is defined in PG::Result and is used by some Rails tests
|
186
190
|
* @return IRubyObject RubyArray of RubyArray of values
|
187
191
|
*/
|
188
|
-
@JRubyMethod
|
192
|
+
@PG @JRubyMethod
|
189
193
|
public IRubyObject values() {
|
190
194
|
return values;
|
191
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
|
+
}
|
192
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);
|
@@ -363,13 +365,26 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
363
365
|
final int index, IRubyObject value,
|
364
366
|
final IRubyObject attribute, final int type) throws SQLException {
|
365
367
|
|
368
|
+
if ( value instanceof RubyFloat ) {
|
369
|
+
final double doubleValue = ( (RubyFloat) value ).getValue();
|
370
|
+
if ( Double.isInfinite(doubleValue) ) {
|
371
|
+
setTimestampInfinity(statement, index, doubleValue);
|
372
|
+
return;
|
373
|
+
}
|
374
|
+
}
|
375
|
+
|
366
376
|
if ( ! "Date".equals(value.getMetaClass().getName()) && value.respondsTo("to_date") ) {
|
367
377
|
value = value.callMethod(context, "to_date");
|
368
378
|
}
|
369
379
|
|
370
|
-
|
371
|
-
|
372
|
-
|
380
|
+
int year = RubyNumeric.num2int(value.callMethod(context, "year"));
|
381
|
+
int month = RubyNumeric.num2int(value.callMethod(context, "month"));
|
382
|
+
int day = RubyNumeric.num2int(value.callMethod(context, "day"));
|
383
|
+
|
384
|
+
@SuppressWarnings("deprecated")
|
385
|
+
Date date = new Date(year - 1900, month - 1, day);
|
386
|
+
|
387
|
+
statement.setDate(index, date);
|
373
388
|
}
|
374
389
|
|
375
390
|
@Override
|
@@ -470,6 +485,21 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
470
485
|
}
|
471
486
|
}
|
472
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
|
+
|
473
503
|
// The tests won't start if this returns PGpoint[]
|
474
504
|
// it fails with a runtime error: "NativeException: java.lang.reflect.InvocationTargetException: [Lorg/postgresql/geometric/PGpoint"
|
475
505
|
private Object[] convertToPoints(Double[] values) throws SQLException {
|
@@ -516,7 +546,9 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
516
546
|
final PreparedStatement statement, final int index,
|
517
547
|
final IRubyObject value, final String columnType) throws SQLException {
|
518
548
|
|
519
|
-
|
549
|
+
// As of AR 5.2 this is a Range object, I defer to the adapter for encoding because of the edge cases
|
550
|
+
// of dealing with the specific types in the range
|
551
|
+
final String rangeValue = adapter(context).callMethod(context, "encode_range", value).toString();
|
520
552
|
final Object pgRange;
|
521
553
|
|
522
554
|
switch ( columnType ) {
|
@@ -536,7 +568,7 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
536
568
|
pgRange = new Int8RangeType(rangeValue);
|
537
569
|
break;
|
538
570
|
default:
|
539
|
-
pgRange = new NumRangeType(rangeValue);
|
571
|
+
pgRange = new NumRangeType(rangeValue, columnType);
|
540
572
|
}
|
541
573
|
|
542
574
|
statement.setObject(index, pgRange);
|
@@ -685,8 +717,16 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
685
717
|
protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
|
686
718
|
// NOTE: PostgreSQL adapter under MRI using pg gem returns UTC-d Date/Time values
|
687
719
|
final String value = resultSet.getString(index);
|
720
|
+
if (value == null) return context.nil;
|
688
721
|
|
689
|
-
|
722
|
+
final int len = value.length();
|
723
|
+
if (len < 10 && value.charAt(len - 1) == 'y') { // infinity / -infinity
|
724
|
+
IRubyObject infinity = parseInfinity(context.runtime, value);
|
725
|
+
|
726
|
+
if (infinity != null) return infinity;
|
727
|
+
}
|
728
|
+
|
729
|
+
return DateTimeUtils.parseDate(context, value, getDefaultTimeZone(context));
|
690
730
|
}
|
691
731
|
|
692
732
|
|
@@ -940,11 +980,27 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
|
|
940
980
|
setType("numrange");
|
941
981
|
}
|
942
982
|
|
943
|
-
public NumRangeType(final String value) throws SQLException {
|
944
|
-
|
983
|
+
public NumRangeType(final String value, final String type) throws SQLException {
|
984
|
+
setType(type);
|
945
985
|
setValue(value);
|
946
986
|
}
|
947
987
|
|
948
988
|
}
|
949
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
|
+
}
|
950
1006
|
}
|