activerecord-jdbc-alt-adapter 51.7.0-java → 52.2.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|
}
|