activerecord-jdbc-adapter 1.3.0.beta1 → 1.3.0.beta2

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.
Files changed (161) hide show
  1. data/.gitignore +12 -11
  2. data/.travis.yml +36 -7
  3. data/Appraisals +3 -3
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +13 -6
  6. data/History.txt +64 -0
  7. data/README.md +8 -1
  8. data/Rakefile +3 -1
  9. data/gemfiles/rails23.gemfile +1 -1
  10. data/gemfiles/rails23.gemfile.lock +6 -5
  11. data/gemfiles/rails30.gemfile +1 -1
  12. data/gemfiles/rails30.gemfile.lock +7 -6
  13. data/gemfiles/rails31.gemfile +1 -1
  14. data/gemfiles/rails31.gemfile.lock +6 -5
  15. data/gemfiles/rails32.gemfile +1 -1
  16. data/gemfiles/rails32.gemfile.lock +6 -5
  17. data/gemfiles/rails40.gemfile +2 -4
  18. data/gemfiles/rails40.gemfile.lock +37 -51
  19. data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
  20. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -1
  21. data/lib/arel/visitors/db2.rb +5 -1
  22. data/lib/arel/visitors/hsqldb.rb +1 -0
  23. data/lib/arel/visitors/sql_server.rb +55 -13
  24. data/lib/arjdbc/db2/adapter.rb +197 -227
  25. data/lib/arjdbc/db2/as400.rb +124 -0
  26. data/lib/arjdbc/db2/connection_methods.rb +20 -1
  27. data/lib/arjdbc/derby/adapter.rb +17 -85
  28. data/lib/arjdbc/derby/connection_methods.rb +2 -1
  29. data/lib/arjdbc/discover.rb +55 -47
  30. data/lib/arjdbc/h2/adapter.rb +52 -18
  31. data/lib/arjdbc/h2/connection_methods.rb +10 -2
  32. data/lib/arjdbc/hsqldb/adapter.rb +33 -9
  33. data/lib/arjdbc/hsqldb/connection_methods.rb +10 -2
  34. data/lib/arjdbc/informix.rb +2 -1
  35. data/lib/arjdbc/jdbc.rb +5 -1
  36. data/lib/arjdbc/jdbc/adapter.rb +167 -89
  37. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  38. data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
  39. data/lib/arjdbc/jdbc/base_ext.rb +25 -3
  40. data/lib/arjdbc/jdbc/callbacks.rb +9 -8
  41. data/lib/arjdbc/jdbc/column.rb +8 -20
  42. data/lib/arjdbc/jdbc/connection.rb +69 -80
  43. data/lib/arjdbc/jdbc/extension.rb +6 -8
  44. data/lib/arjdbc/jdbc/jdbc.rake +3 -141
  45. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -10
  46. data/lib/arjdbc/mssql/adapter.rb +108 -34
  47. data/lib/arjdbc/mssql/connection_methods.rb +3 -1
  48. data/lib/arjdbc/mssql/limit_helpers.rb +3 -2
  49. data/lib/arjdbc/mssql/lock_helpers.rb +5 -1
  50. data/lib/arjdbc/mysql/adapter.rb +127 -70
  51. data/lib/arjdbc/mysql/connection_methods.rb +5 -2
  52. data/lib/arjdbc/oracle/adapter.rb +124 -94
  53. data/lib/arjdbc/oracle/connection_methods.rb +2 -1
  54. data/lib/arjdbc/postgresql/adapter.rb +99 -67
  55. data/lib/arjdbc/postgresql/column_cast.rb +3 -5
  56. data/lib/arjdbc/postgresql/connection_methods.rb +6 -6
  57. data/lib/arjdbc/railtie.rb +3 -1
  58. data/lib/arjdbc/sqlite3/adapter.rb +60 -43
  59. data/lib/arjdbc/sqlite3/connection_methods.rb +9 -9
  60. data/lib/arjdbc/sybase.rb +1 -1
  61. data/lib/arjdbc/tasks.rb +13 -0
  62. data/lib/arjdbc/tasks/database_tasks.rb +50 -0
  63. data/lib/arjdbc/tasks/databases.rake +89 -0
  64. data/lib/arjdbc/tasks/databases3.rake +203 -0
  65. data/lib/arjdbc/tasks/databases4.rake +39 -0
  66. data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
  67. data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
  68. data/lib/arjdbc/tasks/h2_database_tasks.rb +29 -0
  69. data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
  70. data/lib/arjdbc/tasks/jdbc_database_tasks.rb +122 -0
  71. data/lib/arjdbc/tasks/mssql_database_tasks.rb +36 -0
  72. data/lib/arjdbc/tasks/oracle/enhanced_structure_dump.rb +297 -0
  73. data/lib/arjdbc/tasks/oracle_database_tasks.rb +62 -0
  74. data/lib/arjdbc/version.rb +1 -1
  75. data/pom.xml +11 -12
  76. data/rails_generators/jdbc_generator.rb +1 -1
  77. data/rails_generators/templates/config/initializers/jdbc.rb +8 -5
  78. data/rails_generators/templates/lib/tasks/jdbc.rake +7 -4
  79. data/rakelib/02-test.rake +42 -15
  80. data/rakelib/compile.rake +29 -2
  81. data/rakelib/db.rake +2 -1
  82. data/rakelib/rails.rake +23 -6
  83. data/src/java/arjdbc/ArJdbcModule.java +175 -0
  84. data/src/java/arjdbc/db2/DB2Module.java +2 -1
  85. data/src/java/arjdbc/derby/DerbyModule.java +5 -24
  86. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +3 -2
  87. data/src/java/arjdbc/jdbc/AdapterJavaService.java +3 -46
  88. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1001 -259
  89. data/src/java/arjdbc/mssql/MSSQLModule.java +2 -1
  90. data/src/java/arjdbc/mysql/MySQLModule.java +4 -3
  91. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +12 -7
  92. data/src/java/arjdbc/oracle/OracleModule.java +2 -1
  93. data/src/java/arjdbc/sqlite3/SQLite3Module.java +2 -1
  94. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +12 -0
  95. data/test/db/db2.rb +14 -7
  96. data/test/db/db2/rake_test.rb +82 -0
  97. data/test/db/db2/rake_test_data.sql +35 -0
  98. data/test/db/db2/simple_test.rb +20 -0
  99. data/test/db/db2/unit_test.rb +3 -1
  100. data/test/db/derby.rb +7 -5
  101. data/test/db/derby/rake_test.rb +96 -0
  102. data/test/db/derby/simple_test.rb +10 -2
  103. data/test/db/h2.rb +6 -8
  104. data/test/db/h2/identity_column_test.rb +35 -0
  105. data/test/db/h2/offset_test.rb +49 -0
  106. data/test/db/h2/rake_test.rb +98 -0
  107. data/test/db/h2/schema_dump_test.rb +5 -1
  108. data/test/db/hsqldb.rb +6 -10
  109. data/test/db/hsqldb/rake_test.rb +101 -0
  110. data/test/db/hsqldb/schema_dump_test.rb +5 -1
  111. data/test/db/hsqldb/simple_test.rb +8 -0
  112. data/test/db/jndi_config.rb +1 -3
  113. data/test/db/jndi_pooled_config.rb +1 -3
  114. data/test/db/mssql/limit_offset_test.rb +23 -14
  115. data/test/db/mssql/rake_test.rb +143 -0
  116. data/test/db/mysql/_rails_test_mysql.32.out +1069 -1252
  117. data/test/db/mysql/nonstandard_primary_key_test.rb +21 -24
  118. data/test/db/mysql/rake_test.rb +97 -0
  119. data/test/db/mysql/schema_dump_test.rb +11 -11
  120. data/test/db/mysql/simple_test.rb +52 -3
  121. data/test/db/mysql/statement_escaping_test.rb +46 -0
  122. data/test/db/oracle/rake_test.rb +100 -0
  123. data/test/db/oracle/simple_test.rb +48 -0
  124. data/test/db/postgres/_rails_test_postgres.32.out +998 -1370
  125. data/test/db/postgres/active_schema_unit_test.rb +68 -0
  126. data/test/db/postgres/connection_test.rb +10 -2
  127. data/test/db/postgres/data_types_test.rb +2 -2
  128. data/test/db/postgres/ltree_test.rb +6 -5
  129. data/test/db/postgres/native_types_test.rb +1 -5
  130. data/test/db/postgres/rake_test.rb +117 -0
  131. data/test/db/postgres/schema_dump_test.rb +9 -2
  132. data/test/db/postgres/schema_test.rb +4 -2
  133. data/test/db/postgres/simple_test.rb +57 -16
  134. data/test/db/sqlite3.rb +3 -10
  135. data/test/db/sqlite3/_rails_test_sqlite3.32.out +1070 -1298
  136. data/test/db/sqlite3/rake_test.rb +71 -0
  137. data/test/db/sqlite3/simple_test.rb +9 -9
  138. data/test/has_many_through.rb +4 -1
  139. data/test/jdbc/db2.rb +14 -1
  140. data/test/jdbc_column_test.rb +23 -0
  141. data/test/{generic_jdbc_connection_test.rb → jdbc_connection_test.rb} +22 -17
  142. data/test/jndi_callbacks_test.rb +26 -28
  143. data/test/jndi_test.rb +7 -16
  144. data/test/models/data_types.rb +2 -1
  145. data/test/models/thing.rb +1 -0
  146. data/test/rails/mysql.rb +13 -0
  147. data/test/rails/sqlite3/version.rb +6 -0
  148. data/test/rails_stub.rb +31 -0
  149. data/test/rake_test_support.rb +298 -0
  150. data/test/serialize.rb +2 -4
  151. data/test/{helper.rb → shared_helper.rb} +0 -0
  152. data/test/simple.rb +167 -93
  153. data/test/test_helper.rb +52 -16
  154. metadata +388 -354
  155. data/lib/pg.rb +0 -26
  156. data/test/abstract_db_create.rb +0 -139
  157. data/test/activerecord/connection_adapters/type_conversion_test.rb +0 -36
  158. data/test/db/mssql/db_create_test.rb +0 -29
  159. data/test/db/mysql/db_create_test.rb +0 -33
  160. data/test/db/postgres/db_create_test.rb +0 -44
  161. data/test/db/postgres/db_drop_test.rb +0 -17
@@ -36,9 +36,10 @@ import org.jruby.runtime.builtin.IRubyObject;
36
36
 
37
37
  public class HSQLDBModule {
38
38
 
39
- public static void load(final RubyModule arJdbc) {
39
+ public static RubyModule load(final RubyModule arJdbc) {
40
40
  RubyModule hsqldb = arJdbc.defineModuleUnder("HSQLDB");
41
- hsqldb.defineAnnotatedMethods(HSQLDBModule.class);
41
+ hsqldb.defineAnnotatedMethods( HSQLDBModule.class );
42
+ return hsqldb;
42
43
  }
43
44
 
44
45
  @JRubyMethod(name = "quote_string", required = 1, frame = false)
@@ -28,60 +28,17 @@ package arjdbc.jdbc;
28
28
 
29
29
  import java.io.IOException;
30
30
 
31
- import arjdbc.db2.DB2Module;
32
- import arjdbc.db2.DB2RubyJdbcConnection;
33
- import arjdbc.derby.DerbyModule;
34
- import arjdbc.derby.DerbyRubyJdbcConnection;
35
- import arjdbc.h2.H2RubyJdbcConnection;
36
- import arjdbc.hsqldb.HSQLDBModule;
37
- import arjdbc.informix.InformixRubyJdbcConnection;
38
- import arjdbc.mssql.MSSQLModule;
39
- import arjdbc.mssql.MSSQLRubyJdbcConnection;
40
- import arjdbc.mysql.MySQLModule;
41
- import arjdbc.mysql.MySQLRubyJdbcConnection;
42
- import arjdbc.oracle.OracleModule;
43
- import arjdbc.oracle.OracleRubyJdbcConnection;
44
- import arjdbc.postgresql.PostgreSQLRubyJdbcConnection;
45
- import arjdbc.sqlite3.SQLite3Module;
46
- import arjdbc.sqlite3.SQLite3RubyJdbcConnection;
31
+ import arjdbc.ArJdbcModule;
47
32
 
48
33
  import org.jruby.Ruby;
49
- import org.jruby.RubyClass;
50
- import org.jruby.RubyModule;
51
34
  import org.jruby.runtime.load.BasicLibraryService;
52
35
 
53
36
  public class AdapterJavaService implements BasicLibraryService {
54
37
 
55
38
  public boolean basicLoad(final Ruby runtime) throws IOException {
56
39
  // ActiveRecord::ConnectionAdapter-s :
57
- final RubyClass jdbcConnection = RubyJdbcConnection.createJdbcConnectionClass(runtime);
58
- final RubyModule arJdbc = runtime.getOrCreateModule("ArJdbc");
59
- // MySQL
60
- MySQLModule.load(arJdbc);
61
- MySQLRubyJdbcConnection.createMySQLJdbcConnectionClass(runtime, jdbcConnection);
62
- // PostgreSQL
63
- PostgreSQLRubyJdbcConnection.createPostgreSQLJdbcConnectionClass(runtime, jdbcConnection);
64
- // SQLite3
65
- SQLite3Module.load(arJdbc);
66
- SQLite3RubyJdbcConnection.createSQLite3JdbcConnectionClass(runtime, jdbcConnection);
67
- // Oracle
68
- OracleModule.load(arJdbc);
69
- OracleRubyJdbcConnection.createOracleJdbcConnectionClass(runtime, jdbcConnection);
70
- // MS-SQL
71
- MSSQLModule.load(arJdbc);
72
- MSSQLRubyJdbcConnection.createMSSQLJdbcConnectionClass(runtime, jdbcConnection);
73
- // DB2
74
- DB2Module.load(arJdbc);
75
- DB2RubyJdbcConnection.createDB2JdbcConnectionClass(runtime, jdbcConnection);
76
- // Derby
77
- DerbyModule.load(arJdbc);
78
- DerbyRubyJdbcConnection.createDerbyJdbcConnectionClass(runtime, jdbcConnection);
79
- // HSQLDB
80
- HSQLDBModule.load(arJdbc);
81
- // H2
82
- H2RubyJdbcConnection.createH2JdbcConnectionClass(runtime, jdbcConnection);
83
- // Informix
84
- InformixRubyJdbcConnection.createInformixJdbcConnectionClass(runtime, jdbcConnection);
40
+ RubyJdbcConnection.createJdbcConnectionClass(runtime);
41
+ ArJdbcModule.load(runtime);
85
42
  return true;
86
43
  }
87
44
 
@@ -28,8 +28,12 @@ package arjdbc.jdbc;
28
28
  import java.io.ByteArrayInputStream;
29
29
  import java.io.IOException;
30
30
  import java.io.InputStream;
31
+ import java.io.InputStreamReader;
31
32
  import java.io.Reader;
32
33
  import java.io.StringReader;
34
+ import java.lang.reflect.InvocationTargetException;
35
+ import java.lang.reflect.Method;
36
+ import java.math.BigDecimal;
33
37
  import java.math.BigInteger;
34
38
  import java.sql.Array;
35
39
  import java.sql.Connection;
@@ -44,8 +48,6 @@ import java.sql.Date;
44
48
  import java.sql.Time;
45
49
  import java.sql.Timestamp;
46
50
  import java.sql.Types;
47
- import java.text.DateFormat;
48
- import java.text.SimpleDateFormat;
49
51
  import java.util.ArrayList;
50
52
  import java.util.Calendar;
51
53
  import java.util.List;
@@ -55,7 +57,10 @@ import org.jruby.RubyArray;
55
57
  import org.jruby.RubyBignum;
56
58
  import org.jruby.RubyBoolean;
57
59
  import org.jruby.RubyClass;
60
+ import org.jruby.RubyException;
61
+ import org.jruby.RubyFixnum;
58
62
  import org.jruby.RubyHash;
63
+ import org.jruby.RubyIO;
59
64
  import org.jruby.RubyInteger;
60
65
  import org.jruby.RubyModule;
61
66
  import org.jruby.RubyNumeric;
@@ -67,11 +72,11 @@ import org.jruby.anno.JRubyMethod;
67
72
  import org.jruby.exceptions.RaiseException;
68
73
  import org.jruby.javasupport.JavaEmbedUtils;
69
74
  import org.jruby.javasupport.JavaUtil;
70
- import org.jruby.javasupport.util.RuntimeHelpers;
71
75
  import org.jruby.runtime.Arity;
72
76
  import org.jruby.runtime.Block;
73
77
  import org.jruby.runtime.ObjectAllocator;
74
78
  import org.jruby.runtime.ThreadContext;
79
+ import org.jruby.runtime.backtrace.RubyStackTraceElement;
75
80
  import org.jruby.runtime.builtin.IRubyObject;
76
81
  import org.jruby.util.ByteList;
77
82
 
@@ -99,7 +104,11 @@ public class RubyJdbcConnection extends RubyObject {
99
104
  jdbcConnection.defineAnnotatedMethods(RubyJdbcConnection.class);
100
105
  return jdbcConnection;
101
106
  }
102
-
107
+
108
+ public static RubyClass getJdbcConnectionClass(final Ruby runtime) {
109
+ return getConnectionAdapters(runtime).getClass("JdbcConnection");
110
+ }
111
+
103
112
  /**
104
113
  * @param runtime
105
114
  * @return <code>ActiveRecord::ConnectionAdapters</code>
@@ -185,7 +194,7 @@ public class RubyJdbcConnection extends RubyObject {
185
194
  }
186
195
 
187
196
  @JRubyMethod(name = "supports_transaction_isolation?", optional = 1)
188
- public IRubyObject supports_transaction_isolation_p(final ThreadContext context,
197
+ public IRubyObject supports_transaction_isolation_p(final ThreadContext context,
189
198
  final IRubyObject[] args) throws SQLException {
190
199
  final IRubyObject isolation = args.length > 0 ? args[0] : null;
191
200
 
@@ -284,14 +293,24 @@ public class RubyJdbcConnection extends RubyObject {
284
293
  return getInstanceVariable("@connection");
285
294
  }
286
295
 
296
+ @JRubyMethod(name = "active?")
297
+ public IRubyObject active_p(final ThreadContext context) {
298
+ IRubyObject connection = getInstanceVariable("@connection");
299
+ if ( connection != null && ! connection.isNil() ) {
300
+ return isConnectionValid(context, getConnection(false)) ?
301
+ context.getRuntime().getTrue() : context.getRuntime().getFalse();
302
+ }
303
+ return context.getRuntime().getFalse();
304
+ }
305
+
287
306
  @JRubyMethod(name = "disconnect!")
288
307
  public IRubyObject disconnect(final ThreadContext context) {
289
308
  // TODO: only here to try resolving multi-thread issues :
290
309
  // https://github.com/jruby/activerecord-jdbc-adapter/issues/197
291
310
  // https://github.com/jruby/activerecord-jdbc-adapter/issues/198
292
311
  if ( Boolean.getBoolean("arjdbc.disconnect.debug") ) {
312
+ final List<?> backtrace = createCallerBacktrace(context);
293
313
  final Ruby runtime = context.getRuntime();
294
- List backtrace = (List) context.createCallerBacktrace(runtime, 0);
295
314
  runtime.getOut().println(this + " connection.disconnect! occured: ");
296
315
  for ( Object element : backtrace ) {
297
316
  runtime.getOut().println(element);
@@ -300,11 +319,22 @@ public class RubyJdbcConnection extends RubyObject {
300
319
  }
301
320
  return setConnection(null);
302
321
  }
303
-
322
+
304
323
  @JRubyMethod(name = "reconnect!")
305
324
  public IRubyObject reconnect(final ThreadContext context) {
306
325
  try {
307
- return setConnection( getConnectionFactory().newConnection() );
326
+ final Connection connection = getConnectionFactory().newConnection();
327
+ final IRubyObject result = setConnection( connection );
328
+ final IRubyObject adapter = this.callMethod("adapter");
329
+ if ( ! adapter.isNil() ) {
330
+ if ( adapter.respondsTo("configure_connection") ) {
331
+ adapter.callMethod(context, "configure_connection");
332
+ }
333
+ }
334
+ else {
335
+ // NOTE: we should probably warn here about adapter not set ?!?
336
+ }
337
+ return result;
308
338
  }
309
339
  catch (SQLException e) {
310
340
  return handleException(context, e);
@@ -312,14 +342,13 @@ public class RubyJdbcConnection extends RubyObject {
312
342
  }
313
343
 
314
344
  @JRubyMethod(name = "database_name")
315
- public IRubyObject database_name(ThreadContext context) throws SQLException {
316
- Connection connection = getConnection(true);
345
+ public IRubyObject database_name(final ThreadContext context) throws SQLException {
346
+ final Connection connection = getConnection(true);
317
347
  String name = connection.getCatalog();
318
348
 
319
- if (null == name) {
349
+ if (name == null) {
320
350
  name = connection.getMetaData().getUserName();
321
-
322
- if (null == name) name = "db1";
351
+ if (name == null) name = "db1"; // TODO why ?
323
352
  }
324
353
 
325
354
  return context.getRuntime().newString(name);
@@ -332,7 +361,7 @@ public class RubyJdbcConnection extends RubyObject {
332
361
  Statement statement = null;
333
362
  final String query = sql.convertToString().getUnicodeValue();
334
363
  try {
335
- statement = connection.createStatement();
364
+ statement = createStatement(context, connection);
336
365
  if ( doExecute(statement, query) ) {
337
366
  return unmarshalResults(context, connection.getMetaData(), statement, false);
338
367
  } else {
@@ -348,6 +377,21 @@ public class RubyJdbcConnection extends RubyObject {
348
377
  });
349
378
  }
350
379
 
380
+ protected Statement createStatement(final ThreadContext context, final Connection connection)
381
+ throws SQLException {
382
+ final Statement statement = connection.createStatement();
383
+ IRubyObject statementEscapeProcessing = getConfigValue(context, "statement_escape_processing");
384
+ // NOTE: disable (driver) escape processing by default, it's not really
385
+ // needed for AR statements ... if users need it they might configure :
386
+ if ( statementEscapeProcessing.isNil() ) {
387
+ statement.setEscapeProcessing(false);
388
+ }
389
+ else {
390
+ statement.setEscapeProcessing(statementEscapeProcessing.isTrue());
391
+ }
392
+ return statement;
393
+ }
394
+
351
395
  /**
352
396
  * Execute a query using the given statement.
353
397
  * @param statement
@@ -379,58 +423,58 @@ public class RubyJdbcConnection extends RubyObject {
379
423
  key = runtime.getNil();
380
424
  }
381
425
  return key.isNil() ? runtime.newFixnum( statement.getUpdateCount() ) : key;
382
- }
426
+ }
383
427
 
384
- @JRubyMethod(name = "execute_id_insert", required = 2)
385
- public IRubyObject execute_id_insert(final ThreadContext context,
386
- final IRubyObject sql, final IRubyObject id) throws SQLException {
428
+ @JRubyMethod(name = "execute_insert", required = 1)
429
+ public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql)
430
+ throws SQLException {
387
431
  return withConnection(context, new Callable<IRubyObject>() {
388
432
  public IRubyObject call(final Connection connection) throws SQLException {
389
- PreparedStatement statement = null;
433
+ Statement statement = null;
390
434
  final String insertSQL = sql.convertToString().getUnicodeValue();
391
435
  try {
392
- statement = connection.prepareStatement(insertSQL);
393
- statement.setLong(1, RubyNumeric.fix2long(id));
394
- statement.executeUpdate();
436
+ statement = createStatement(context, connection);
437
+ statement.executeUpdate(insertSQL, Statement.RETURN_GENERATED_KEYS);
438
+ return unmarshalIdResult(context.getRuntime(), statement);
395
439
  }
396
440
  catch (final SQLException e) {
397
441
  debugErrorSQL(context, insertSQL);
398
442
  throw e;
399
443
  }
400
444
  finally { close(statement); }
401
- return id;
402
445
  }
403
446
  });
404
447
  }
405
448
 
406
- @JRubyMethod(name = "execute_insert", required = 1)
407
- public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql)
449
+ @JRubyMethod(name = {"execute_update", "execute_delete"}, required = 1)
450
+ public IRubyObject execute_update(final ThreadContext context, final IRubyObject sql)
408
451
  throws SQLException {
409
- return withConnection(context, new Callable<IRubyObject>() {
410
- public IRubyObject call(final Connection connection) throws SQLException {
452
+ return withConnection(context, new Callable<RubyInteger>() {
453
+ public RubyInteger call(final Connection connection) throws SQLException {
411
454
  Statement statement = null;
412
- final String insertSQL = sql.convertToString().getUnicodeValue();
455
+ final String updateSQL = sql.convertToString().getUnicodeValue();
413
456
  try {
414
- statement = connection.createStatement();
415
- statement.executeUpdate(insertSQL, Statement.RETURN_GENERATED_KEYS);
416
- return unmarshalIdResult(context.getRuntime(), statement);
457
+ statement = createStatement(context, connection);
458
+ final int rowCount = statement.executeUpdate(updateSQL);
459
+ return context.getRuntime().newFixnum(rowCount);
417
460
  }
418
461
  catch (final SQLException e) {
419
- debugErrorSQL(context, insertSQL);
462
+ debugErrorSQL(context, updateSQL);
420
463
  throw e;
421
464
  }
422
465
  finally { close(statement); }
423
466
  }
424
467
  });
425
468
  }
426
-
469
+
427
470
  /**
428
471
  * NOTE: since 1.3 this behaves like <code>execute_query</code> in AR-JDBC 1.2
429
472
  * @param context
430
473
  * @param sql
431
- * @param block
432
- * @return raw query result in case no block given
474
+ * @param block (optional) block to yield row values
475
+ * @return raw query result as a name => value Hash (unless block given)
433
476
  * @throws SQLException
477
+ * @see #execute_query_raw(ThreadContext, IRubyObject[], Block)
434
478
  */
435
479
  @JRubyMethod(name = "execute_query_raw", required = 1) // optional block
436
480
  public IRubyObject execute_query_raw(final ThreadContext context,
@@ -442,20 +486,44 @@ public class RubyJdbcConnection extends RubyObject {
442
486
  /**
443
487
  * NOTE: since 1.3 this behaves like <code>execute_query</code> in AR-JDBC 1.2
444
488
  * @param context
445
- * @param sql
446
- * @param maxRows
447
- * @param block
448
- * @return raw query result in case no block given
489
+ * @param args
490
+ * @param block (optional) block to yield row values
491
+ * @return raw query result as a name => value Hash (unless block given)
449
492
  * @throws SQLException
450
493
  */
451
- @JRubyMethod(name = "execute_query_raw", required = 2)
494
+ @JRubyMethod(name = "execute_query_raw", required = 2, optional = 1)
495
+ // @JRubyMethod(name = "execute_query_raw", required = 1, optional = 2)
452
496
  public IRubyObject execute_query_raw(final ThreadContext context,
453
- final IRubyObject sql, final IRubyObject maxRows, final Block block)
454
- throws SQLException {
455
- final String query = sql.convertToString().getUnicodeValue();
456
- return executeQueryRaw(context, query, RubyNumeric.fix2int(maxRows), block);
497
+ final IRubyObject[] args, final Block block) throws SQLException {
498
+ // args: (sql), (sql, max_rows), (sql, binds), (sql, max_rows, binds)
499
+ final String query = args[0].convertToString().getUnicodeValue(); // sql
500
+ IRubyObject max_rows = args.length > 1 ? args[1] : null;
501
+ IRubyObject binds = args.length > 2 ? args[2] : null;
502
+ final int maxRows;
503
+ if ( max_rows == null || max_rows.isNil() ) maxRows = 0;
504
+ else {
505
+ if ( binds instanceof RubyNumeric ) { // (sql, max_rows)
506
+ maxRows = RubyNumeric.fix2int(binds); binds = null;
507
+ }
508
+ else {
509
+ if ( max_rows instanceof RubyNumeric ) {
510
+ maxRows = RubyNumeric.fix2int(max_rows);
511
+ }
512
+ else {
513
+ if ( binds == null ) binds = max_rows; // (sql, binds)
514
+ maxRows = 0;
515
+ }
516
+ }
517
+ }
518
+
519
+ if ( binds == null || binds.isNil() ) { // no prepared statements
520
+ return executeQueryRaw(context, query, maxRows, block);
521
+ }
522
+ else { // we allow prepared statements with empty binds parameters
523
+ return executePreparedQueryRaw(context, query, (List) binds, maxRows, block);
524
+ }
457
525
  }
458
-
526
+
459
527
  /**
460
528
  * @param context
461
529
  * @param query
@@ -463,20 +531,46 @@ public class RubyJdbcConnection extends RubyObject {
463
531
  * @param block
464
532
  * @return raw query result (in case no block was given)
465
533
  *
466
- * @see #execute_raw_query(ThreadContext, IRubyObject, Block)
467
- * @see #execute_raw_query(ThreadContext, IRubyObject, IRubyObject, Block)
534
+ * @see #execute_query_raw(ThreadContext, IRubyObject[], Block)
468
535
  */
469
- protected IRubyObject executeQueryRaw(final ThreadContext context, final String query, final int maxRows,
470
- final Block block) { // TODO implement block support
536
+ protected IRubyObject executeQueryRaw(final ThreadContext context,
537
+ final String query, final int maxRows, final Block block) {
538
+ return doExecuteQueryRaw(context, query, maxRows, block, null); // binds == null
539
+ }
540
+
541
+ protected IRubyObject executePreparedQueryRaw(final ThreadContext context,
542
+ final String query, final List<?> binds, final int maxRows, final Block block) {
543
+ return doExecuteQueryRaw(context, query, maxRows, block, binds);
544
+ }
545
+
546
+ private IRubyObject doExecuteQueryRaw(final ThreadContext context,
547
+ final String query, final int maxRows, final Block block, final List<?> binds) {
471
548
  return withConnection(context, new Callable<IRubyObject>() {
472
549
  public IRubyObject call(final Connection connection) throws SQLException {
473
550
  final Ruby runtime = context.getRuntime();
551
+ final DatabaseMetaData metaData = connection.getMetaData();
552
+
474
553
  Statement statement = null; ResultSet resultSet = null;
475
554
  try {
476
- final DatabaseMetaData metaData = connection.getMetaData();
477
- statement = connection.createStatement();
478
- statement.setMaxRows(maxRows); // zero means there is no limit
479
- resultSet = statement.executeQuery(query);
555
+ if ( binds == null ) { // plain statement
556
+ statement = createStatement(context, connection);
557
+ statement.setMaxRows(maxRows); // zero means there is no limit
558
+ resultSet = statement.executeQuery(query);
559
+ }
560
+ else {
561
+ final PreparedStatement prepStatement;
562
+ statement = prepStatement = connection.prepareStatement(query);
563
+ statement.setMaxRows(maxRows); // zero means there is no limit
564
+ setStatementParameters(context, connection, prepStatement, binds);
565
+ resultSet = prepStatement.executeQuery();
566
+ }
567
+
568
+ if ( block != null && block.isGiven() ) {
569
+ // yield(id1, name1) ... row 1 result data
570
+ // yield(id2, name2) ... row 2 result data
571
+ return yieldResultRows(context, runtime, metaData, resultSet, block);
572
+ }
573
+
480
574
  return mapToRawResult(context, runtime, metaData, resultSet, false);
481
575
  }
482
576
  catch (final SQLException e) {
@@ -487,38 +581,62 @@ public class RubyJdbcConnection extends RubyObject {
487
581
  }
488
582
  });
489
583
  }
490
-
584
+
491
585
  /**
492
586
  * Executes a query and returns the (AR) result.
493
587
  * @param context
494
588
  * @param sql
495
- * @return
496
- * @throws SQLException
497
- *
498
- * @see #execute_raw_query(ThreadContext, IRubyObject, Block)
589
+ * @return raw query result as a name => value Hash (unless block given)
590
+ * @throws SQLException
591
+ * @see #execute_query(ThreadContext, IRubyObject[], Block)
499
592
  */
500
593
  @JRubyMethod(name = "execute_query", required = 1)
501
- public IRubyObject execute_query(final ThreadContext context, final IRubyObject sql)
502
- throws SQLException {
594
+ public IRubyObject execute_query(final ThreadContext context,
595
+ final IRubyObject sql) throws SQLException {
503
596
  final String query = sql.convertToString().getUnicodeValue();
504
597
  return executeQuery(context, query, 0);
505
598
  }
506
-
599
+
507
600
  /**
508
601
  * Executes a query and returns the (AR) result.
509
602
  * @param context
510
- * @param sql
511
- * @param maxRows
512
- * @return
603
+ * @param args
604
+ * @return and <code>ActiveRecord::Result</code>
513
605
  * @throws SQLException
514
606
  *
515
- * @see #execute_raw_query(ThreadContext, IRubyObject, IRubyObject, Block)
607
+ * @see #execute_query(ThreadContext, IRubyObject, IRubyObject, Block)
516
608
  */
517
- @JRubyMethod(name = "execute_query", required = 2)
609
+ @JRubyMethod(name = "execute_query", required = 2, optional = 1)
610
+ // @JRubyMethod(name = "execute_query", required = 1, optional = 2)
518
611
  public IRubyObject execute_query(final ThreadContext context,
519
- final IRubyObject sql, final IRubyObject maxRows) throws SQLException {
520
- final String query = sql.convertToString().getUnicodeValue();
521
- return executeQuery(context, query, RubyNumeric.fix2int(maxRows));
612
+ final IRubyObject[] args) throws SQLException {
613
+ // args: (sql), (sql, max_rows), (sql, binds), (sql, max_rows, binds)
614
+ final String query = args[0].convertToString().getUnicodeValue(); // sql
615
+ IRubyObject max_rows = args.length > 1 ? args[1] : null;
616
+ IRubyObject binds = args.length > 2 ? args[2] : null;
617
+ final int maxRows;
618
+ if ( max_rows == null || max_rows.isNil() ) maxRows = 0;
619
+ else {
620
+ if ( binds instanceof RubyNumeric ) { // (sql, max_rows)
621
+ maxRows = RubyNumeric.fix2int(binds); binds = null;
622
+ }
623
+ else {
624
+ if ( max_rows instanceof RubyNumeric ) {
625
+ maxRows = RubyNumeric.fix2int(max_rows);
626
+ }
627
+ else {
628
+ if ( binds == null ) binds = max_rows; // (sql, binds)
629
+ maxRows = 0;
630
+ }
631
+ }
632
+ }
633
+
634
+ if ( binds == null || binds.isNil() ) { // no prepared statements
635
+ return executeQuery(context, query, maxRows);
636
+ }
637
+ else { // we allow prepared statements with empty binds parameters
638
+ return executePreparedQuery(context, query, (List) binds, maxRows);
639
+ }
522
640
  }
523
641
 
524
642
  /**
@@ -537,15 +655,12 @@ public class RubyJdbcConnection extends RubyObject {
537
655
  protected IRubyObject executeQuery(final ThreadContext context, final String query, final int maxRows) {
538
656
  return withConnection(context, new Callable<IRubyObject>() {
539
657
  public IRubyObject call(final Connection connection) throws SQLException {
540
- final Ruby runtime = context.getRuntime();
541
658
  Statement statement = null; ResultSet resultSet = null;
542
659
  try {
543
- final DatabaseMetaData metaData = connection.getMetaData();
544
- statement = connection.createStatement();
660
+ statement = createStatement(context, connection);
545
661
  statement.setMaxRows(maxRows); // zero means there is no limit
546
662
  resultSet = statement.executeQuery(query);
547
- final ColumnData[] columns = setupColumns(runtime, metaData, resultSet.getMetaData(), false);
548
- return mapToResult(context, runtime, metaData, resultSet, columns);
663
+ return mapQueryResult(context, connection, resultSet);
549
664
  }
550
665
  catch (final SQLException e) {
551
666
  debugErrorSQL(context, query);
@@ -556,27 +671,57 @@ public class RubyJdbcConnection extends RubyObject {
556
671
  });
557
672
  }
558
673
 
559
- @JRubyMethod(name = {"execute_update", "execute_delete"}, required = 1)
560
- public IRubyObject execute_update(final ThreadContext context, final IRubyObject sql)
561
- throws SQLException {
562
- return withConnection(context, new Callable<RubyInteger>() {
563
- public RubyInteger call(final Connection connection) throws SQLException {
564
- Statement statement = null;
565
- final String updateSQL = sql.convertToString().getUnicodeValue();
674
+ protected IRubyObject executePreparedQuery(final ThreadContext context, final String query,
675
+ final List<?> binds, final int maxRows) {
676
+ return withConnection(context, new Callable<IRubyObject>() {
677
+ public IRubyObject call(final Connection connection) throws SQLException {
678
+ PreparedStatement statement = null; ResultSet resultSet = null;
566
679
  try {
567
- statement = connection.createStatement();
568
- final int rowCount = statement.executeUpdate(updateSQL);
569
- return context.getRuntime().newFixnum(rowCount);
680
+ statement = connection.prepareStatement(query);
681
+ statement.setMaxRows(maxRows); // zero means there is no limit
682
+ setStatementParameters(context, connection, statement, binds);
683
+ resultSet = statement.executeQuery();
684
+ return mapQueryResult(context, connection, resultSet);
570
685
  }
571
686
  catch (final SQLException e) {
572
- debugErrorSQL(context, updateSQL);
687
+ debugErrorSQL(context, query);
573
688
  throw e;
574
689
  }
575
- finally { close(statement); }
690
+ finally { close(resultSet); close(statement); }
576
691
  }
577
692
  });
578
693
  }
694
+
695
+ private IRubyObject mapQueryResult(final ThreadContext context,
696
+ final Connection connection, final ResultSet resultSet) throws SQLException {
697
+ final Ruby runtime = context.getRuntime();
698
+ final DatabaseMetaData metaData = connection.getMetaData();
699
+ final ColumnData[] columns = setupColumns(runtime, metaData, resultSet.getMetaData(), false);
700
+ return mapToResult(context, runtime, metaData, resultSet, columns);
701
+ }
579
702
 
703
+ @JRubyMethod(name = "execute_id_insert", required = 2)
704
+ public IRubyObject execute_id_insert(final ThreadContext context,
705
+ final IRubyObject sql, final IRubyObject id) throws SQLException {
706
+ return withConnection(context, new Callable<IRubyObject>() {
707
+ public IRubyObject call(final Connection connection) throws SQLException {
708
+ PreparedStatement statement = null;
709
+ final String insertSQL = sql.convertToString().getUnicodeValue();
710
+ try {
711
+ statement = connection.prepareStatement(insertSQL);
712
+ statement.setLong(1, RubyNumeric.fix2long(id));
713
+ statement.executeUpdate();
714
+ }
715
+ catch (final SQLException e) {
716
+ debugErrorSQL(context, insertSQL);
717
+ throw e;
718
+ }
719
+ finally { close(statement); }
720
+ return id;
721
+ }
722
+ });
723
+ }
724
+
580
725
  @JRubyMethod(name = "native_database_types", frame = false)
581
726
  public IRubyObject native_database_types() {
582
727
  return getInstanceVariable("@native_database_types");
@@ -631,7 +776,7 @@ public class RubyJdbcConnection extends RubyObject {
631
776
 
632
777
  return runtime.getNil();
633
778
  }
634
-
779
+
635
780
  @JRubyMethod(name = "tables")
636
781
  public IRubyObject tables(ThreadContext context) {
637
782
  return tables(context, null, null, null, TABLE_TYPE);
@@ -670,19 +815,33 @@ public class RubyJdbcConnection extends RubyObject {
670
815
  return TABLE_TYPES;
671
816
  }
672
817
 
673
- @JRubyMethod(name = "table_exists?", required = 1, optional = 1)
674
- public IRubyObject table_exists_p(final ThreadContext context, final IRubyObject[] args) {
675
- IRubyObject name = args[0], schema_name = args.length > 1 ? args[1] : null;
676
- if ( ! ( name instanceof RubyString ) ) {
677
- name = name.callMethod(context, "to_s");
818
+ @JRubyMethod(name = "table_exists?")
819
+ public IRubyObject table_exists_p(final ThreadContext context, IRubyObject table) {
820
+ if ( table.isNil() ) {
821
+ throw context.getRuntime().newArgumentError("nil table name");
678
822
  }
679
- final String tableName = ((RubyString) name).getUnicodeValue();
680
- final String tableSchema = schema_name == null ? null : schema_name.convertToString().getUnicodeValue();
681
- final Ruby runtime = context.getRuntime();
823
+ final String tableName = table.toString();
824
+
825
+ return tableExists(context, null, tableName);
826
+ }
827
+
828
+ @JRubyMethod(name = "table_exists?")
829
+ public IRubyObject table_exists_p(final ThreadContext context, IRubyObject table, IRubyObject schema) {
830
+ if ( table.isNil() ) {
831
+ throw context.getRuntime().newArgumentError("nil table name");
832
+ }
833
+ final String tableName = table.toString();
834
+ final String defaultSchema = schema.isNil() ? null : schema.toString();
682
835
 
836
+ return tableExists(context, defaultSchema, tableName);
837
+ }
838
+
839
+ protected IRubyObject tableExists(final ThreadContext context,
840
+ final String defaultSchema, final String tableName) {
841
+ final Ruby runtime = context.getRuntime();
683
842
  return withConnection(context, new Callable<RubyBoolean>() {
684
843
  public RubyBoolean call(final Connection connection) throws SQLException {
685
- final TableName components = extractTableName(connection, tableSchema, tableName);
844
+ final TableName components = extractTableName(connection, defaultSchema, tableName);
686
845
  return runtime.newBoolean( tableExists(runtime, connection, components) );
687
846
  }
688
847
  });
@@ -695,7 +854,7 @@ public class RubyJdbcConnection extends RubyObject {
695
854
  public IRubyObject call(final Connection connection) throws SQLException {
696
855
  ResultSet columns = null, primaryKeys = null;
697
856
  try {
698
- final String tableName = args[0].convertToString().getUnicodeValue();
857
+ final String tableName = args[0].toString();
699
858
  // optionals (NOTE: catalog argumnet was never used before 1.3.0) :
700
859
  final String catalog = args.length > 1 ? toStringOrNull(args[1]) : null;
701
860
  final String defaultSchema = args.length > 2 ? toStringOrNull(args[2]) : null;
@@ -724,12 +883,17 @@ public class RubyJdbcConnection extends RubyObject {
724
883
  }
725
884
  });
726
885
  }
886
+
887
+ @JRubyMethod(name = "indexes")
888
+ public IRubyObject indexes(final ThreadContext context, IRubyObject tableName, IRubyObject name) {
889
+ return indexes(context, toStringOrNull(tableName), toStringOrNull(name), null);
890
+ }
727
891
 
728
892
  @JRubyMethod(name = "indexes")
729
- public IRubyObject indexes(ThreadContext context, IRubyObject tableName, IRubyObject name, IRubyObject schemaName) {
893
+ public IRubyObject indexes(final ThreadContext context, IRubyObject tableName, IRubyObject name, IRubyObject schemaName) {
730
894
  return indexes(context, toStringOrNull(tableName), toStringOrNull(name), toStringOrNull(schemaName));
731
895
  }
732
-
896
+
733
897
  // NOTE: metaData.getIndexInfo row mappings :
734
898
  private static final int INDEX_INFO_TABLE_NAME = 3;
735
899
  private static final int INDEX_INFO_NON_UNIQUE = 4;
@@ -808,7 +972,7 @@ public class RubyJdbcConnection extends RubyObject {
808
972
 
809
973
  // NOTE: this seems to be not used ... at all ?!
810
974
  /*
811
- * sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
975
+ * sql, values (array), types (column.type array), name = nil, pk = nil, id_value = nil, sequence_name = nil
812
976
  */
813
977
  @Deprecated
814
978
  @JRubyMethod(name = "insert_bind", required = 3, rest = true)
@@ -820,7 +984,7 @@ public class RubyJdbcConnection extends RubyObject {
820
984
  PreparedStatement statement = null;
821
985
  try {
822
986
  statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
823
- setValues(context, args[1], args[2], statement);
987
+ setPreparedStatementValues(context, connection, statement, args[1], args[2]);
824
988
  statement.executeUpdate();
825
989
  return unmarshalIdResult(runtime, statement);
826
990
  }
@@ -831,7 +995,7 @@ public class RubyJdbcConnection extends RubyObject {
831
995
 
832
996
  // NOTE: this seems to be not used ... at all ?!
833
997
  /*
834
- * sql, values, types, name = nil
998
+ * sql, values (array), types (column.type array), name = nil
835
999
  */
836
1000
  @Deprecated
837
1001
  @JRubyMethod(name = "update_bind", required = 3, rest = true)
@@ -844,7 +1008,7 @@ public class RubyJdbcConnection extends RubyObject {
844
1008
  PreparedStatement statement = null;
845
1009
  try {
846
1010
  statement = connection.prepareStatement(sql);
847
- setValues(context, args[1], args[2], statement);
1011
+ setPreparedStatementValues(context, connection, statement, args[1], args[2]);
848
1012
  statement.executeUpdate();
849
1013
  }
850
1014
  finally { close(statement); }
@@ -870,10 +1034,10 @@ public class RubyJdbcConnection extends RubyObject {
870
1034
  throws SQLException {
871
1035
 
872
1036
  final boolean isBinary = args[0].isTrue();
873
- final RubyString columnName = args[1].convertToString();
874
- final RubyString tableName = args[2].convertToString();
875
- final RubyString idKey = args[3].convertToString();
876
- final RubyString idVal = args[4].convertToString();
1037
+ final String columnName = args[1].toString();
1038
+ final String tableName = args[2].toString();
1039
+ final String idKey = args[3].toString();
1040
+ final String idVal = args[4].toString();
877
1041
  final IRubyObject lobValue = args[5];
878
1042
 
879
1043
  final Ruby runtime = context.getRuntime();
@@ -951,16 +1115,16 @@ public class RubyJdbcConnection extends RubyObject {
951
1115
  protected IRubyObject config_value(ThreadContext context, String key) {
952
1116
  return getConfigValue(context, key);
953
1117
  }
954
-
955
- private static String toStringOrNull(IRubyObject arg) {
1118
+
1119
+ private static String toStringOrNull(final IRubyObject arg) {
956
1120
  return arg.isNil() ? null : arg.toString();
957
1121
  }
958
1122
 
959
- protected IRubyObject getAdapter(ThreadContext context) {
1123
+ protected IRubyObject getAdapter(final ThreadContext context) {
960
1124
  return callMethod(context, "adapter");
961
1125
  }
962
1126
 
963
- protected IRubyObject getJdbcColumnClass(ThreadContext context) {
1127
+ protected IRubyObject getJdbcColumnClass(final ThreadContext context) {
964
1128
  return getAdapter(context).callMethod(context, "jdbc_column_class");
965
1129
  }
966
1130
 
@@ -992,42 +1156,6 @@ public class RubyJdbcConnection extends RubyObject {
992
1156
  }
993
1157
  return new String[] { typeArg.toString() }; // expect a RubyString
994
1158
  }
995
-
996
- private static int jdbcTypeFor(final ThreadContext context, IRubyObject type)
997
- throws SQLException {
998
- if ( ! ( type instanceof RubySymbol ) ) {
999
- if ( type instanceof RubyString ) { // to_sym
1000
- if ( context.getRuntime().is1_9() ) {
1001
- type = ( (RubyString) type ).intern19();
1002
- }
1003
- else {
1004
- type = ( (RubyString) type ).intern();
1005
- }
1006
- }
1007
- else {
1008
- throw new IllegalArgumentException(
1009
- "expected a Ruby string/symbol but got: " + type + " (" + type.getMetaClass().getName() + ")"
1010
- );
1011
- }
1012
- }
1013
-
1014
- final String internedValue = type.asJavaString();
1015
-
1016
- if ( internedValue == (Object) "string" ) return Types.VARCHAR;
1017
- else if ( internedValue == (Object) "text" ) return Types.CLOB;
1018
- else if ( internedValue == (Object) "integer" ) return Types.INTEGER;
1019
- else if ( internedValue == (Object) "decimal" ) return Types.DECIMAL;
1020
- else if ( internedValue == (Object) "float" ) return Types.FLOAT;
1021
- else if ( internedValue == (Object) "datetime") return Types.TIMESTAMP;
1022
- else if ( internedValue == (Object) "timestamp" ) return Types.TIMESTAMP;
1023
- else if ( internedValue == (Object) "time" ) return Types.TIME;
1024
- else if ( internedValue == (Object) "date" ) return Types.DATE;
1025
- else if ( internedValue == (Object) "binary" ) return Types.BLOB;
1026
- else if ( internedValue == (Object) "boolean" ) return Types.BOOLEAN;
1027
- else if ( internedValue == (Object) "xml" ) return Types.SQLXML;
1028
- else if ( internedValue == (Object) "array" ) return Types.ARRAY;
1029
- else return -1;
1030
- }
1031
1159
 
1032
1160
  /**
1033
1161
  * @deprecated this method is no longer used, instead consider overriding
@@ -1112,14 +1240,14 @@ public class RubyJdbcConnection extends RubyObject {
1112
1240
  return booleanToRuby(runtime, resultSet, column);
1113
1241
  case Types.SQLXML: // JDBC 4.0
1114
1242
  return xmlToRuby(runtime, resultSet, column);
1243
+ case Types.ARRAY: // we handle JDBC Array into (Ruby) []
1244
+ return arrayToRuby(runtime, resultSet, column);
1115
1245
  case Types.NULL:
1116
1246
  return runtime.getNil();
1117
1247
  // NOTE: (JDBC) exotic stuff just cause it's so easy with JRuby :)
1118
1248
  case Types.JAVA_OBJECT:
1119
1249
  case Types.OTHER:
1120
1250
  return objectToRuby(runtime, resultSet, column);
1121
- case Types.ARRAY: // we handle JDBC Array into (Ruby) []
1122
- return arrayToRuby(runtime, resultSet, column);
1123
1251
  // (default) String
1124
1252
  case Types.CHAR:
1125
1253
  case Types.VARCHAR:
@@ -1370,119 +1498,677 @@ public class RubyJdbcConnection extends RubyObject {
1370
1498
  finally { xml.free(); }
1371
1499
  }
1372
1500
 
1373
- protected final Connection getConnection() {
1374
- return getConnection(false);
1375
- }
1376
-
1377
- protected Connection getConnection(boolean error) {
1378
- final Connection connection = (Connection) dataGetStruct();
1379
- if ( connection == null && error ) {
1380
- final RubyClass errorClass = getConnectionNotEstablished( getRuntime() );
1381
- throw new RaiseException(getRuntime(), errorClass, "no connection available", false);
1382
- }
1383
- return connection;
1384
- }
1385
-
1386
- private synchronized RubyJdbcConnection setConnection(final Connection connection) {
1387
- close( getConnection(false) ); // close previously open connection if there is one
1501
+ /* protected */ void setStatementParameters(final ThreadContext context,
1502
+ final Connection connection, final PreparedStatement statement,
1503
+ final List<?> binds) throws SQLException {
1388
1504
 
1389
- final IRubyObject rubyConnectionObject =
1390
- connection != null ? convertJavaToRuby(connection) : getRuntime().getNil();
1391
- setInstanceVariable( "@connection", rubyConnectionObject );
1392
- dataWrapStruct(connection);
1393
- return this;
1394
- }
1395
-
1396
- private boolean isConnectionBroken(final ThreadContext context, final Connection connection) {
1397
- Statement statement = null;
1398
- try {
1399
- final RubyString aliveSQL = getConfigValue(context, "connection_alive_sql").convertToString();
1400
- if ( isSelect(aliveSQL) ) { // expect a SELECT/CALL SQL statement
1401
- statement = connection.createStatement();
1402
- statement.execute( aliveSQL.toString() );
1403
- return false; // connection ain't broken
1505
+ final Ruby runtime = context.getRuntime();
1506
+
1507
+ for ( int i = 0; i < binds.size(); i++ ) {
1508
+ // [ [ column1, param1 ], [ column2, param2 ], ... ]
1509
+ Object param = binds.get(i); IRubyObject column = null;
1510
+ if ( param.getClass() == RubyArray.class ) {
1511
+ final RubyArray _param = (RubyArray) param;
1512
+ column = _param.eltInternal(0); param = _param.eltInternal(1);
1404
1513
  }
1405
- else { // alive_sql nil (or not a statement we can execute)
1406
- return ! connection.isClosed(); // if closed than broken
1514
+ else if ( param instanceof List ) {
1515
+ final List<?> _param = (List<?>) param;
1516
+ column = (IRubyObject) _param.get(0); param = _param.get(1);
1407
1517
  }
1518
+ else if ( param instanceof Object[] ) {
1519
+ final Object[] _param = (Object[]) param;
1520
+ column = (IRubyObject) _param[0]; param = _param[1];
1521
+ }
1522
+
1523
+ final IRubyObject type;
1524
+ if ( column != null && ! column.isNil() ) {
1525
+ type = column.callMethod(context, "type");
1526
+ }
1527
+ else {
1528
+ type = null;
1529
+ }
1530
+
1531
+ setStatementParameter(context, runtime, connection, statement, i + 1, param, type);
1408
1532
  }
1409
- catch (Exception e) {
1410
- debugMessage(context, "connection considered broken due: " + e.toString());
1411
- return true;
1412
- }
1413
- finally { close(statement); }
1414
1533
  }
1415
-
1416
- private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
1417
1534
 
1418
- private static void setValue(final ThreadContext context,
1419
- final IRubyObject value, final IRubyObject type,
1420
- final PreparedStatement statement, final int index) throws SQLException {
1535
+ /* protected */ void setStatementParameter(final ThreadContext context,
1536
+ final Ruby runtime, final Connection connection,
1537
+ final PreparedStatement statement, final int index,
1538
+ final Object value, final IRubyObject column) throws SQLException {
1421
1539
 
1422
- final int jdbcType = jdbcTypeFor(context, type);
1540
+ final RubySymbol columnType = resolveColumnType(context, runtime, column);
1541
+ final int type = jdbcTypeFor(runtime, column, columnType, value);
1423
1542
 
1424
- if ( value.isNil() ) {
1425
- statement.setNull(index, jdbcType);
1426
- return;
1427
- }
1428
-
1429
- switch (jdbcType) {
1430
- case Types.VARCHAR:
1431
- case Types.CLOB:
1432
- statement.setString(index, RubyString.objAsString(context, value).toString());
1433
- break;
1434
- case Types.INTEGER:
1435
- statement.setLong(index, RubyNumeric.fix2long(value));
1436
- break;
1437
- case Types.FLOAT:
1438
- statement.setDouble(index, ((RubyNumeric) value).getDoubleValue());
1439
- break;
1440
- case Types.TIMESTAMP:
1441
- case Types.TIME:
1442
- case Types.DATE:
1443
- if ( ! ( value instanceof RubyTime ) ) {
1444
- final String stringValue = RubyString.objAsString(context, value).toString();
1445
- try {
1446
- Timestamp timestamp = new Timestamp( FORMAT.parse( stringValue ).getTime() );
1447
- statement.setTimestamp( index, timestamp, Calendar.getInstance() );
1448
- }
1449
- catch (Exception e) {
1450
- statement.setString( index, stringValue );
1451
- }
1452
- } else {
1453
- final RubyTime timeValue = (RubyTime) value;
1454
- final java.util.Date dateValue = timeValue.getJavaDate();
1455
-
1456
- long millis = dateValue.getTime();
1457
- Timestamp timestamp = new Timestamp(millis);
1458
- Calendar calendar = Calendar.getInstance();
1459
- calendar.setTime(dateValue);
1460
- if ( jdbcType != Types.DATE ) {
1461
- int micros = (int) timeValue.microseconds();
1462
- timestamp.setNanos( micros * 1000 ); // time.nsec ~ time.usec * 1000
1543
+ // TODO pass column with (JDBC) type to methods :
1544
+
1545
+ switch (type) {
1546
+ case Types.TINYINT:
1547
+ case Types.SMALLINT:
1548
+ case Types.INTEGER:
1549
+ if ( value instanceof RubyBignum ) {
1550
+ setBigIntegerParameter(runtime, connection, statement, index, type, (RubyBignum) value);
1463
1551
  }
1464
- statement.setTimestamp( index, timestamp, calendar );
1465
- }
1466
- break;
1467
- case Types.BOOLEAN:
1468
- statement.setBoolean(index, value.isTrue());
1469
- break;
1470
- default: throw new RuntimeException("type " + jdbcType + " not supported in _bind (yet)");
1471
- }
1472
- }
1473
-
1474
- private static void setValues(final ThreadContext context,
1475
- final IRubyObject valuesArg, final IRubyObject typesArg,
1476
- final PreparedStatement statement) throws SQLException {
1477
- final RubyArray values = (RubyArray) valuesArg;
1478
- final RubyArray types = (RubyArray) typesArg;
1479
- for( int i = 0, j = values.getLength(); i < j; i++ ) {
1480
- setValue(context, values.eltInternal(i), types.eltInternal(i), statement, i + 1);
1481
- }
1482
- }
1483
-
1484
- private boolean tableExists(final Ruby runtime,
1485
- final Connection connection, final TableName tableName) throws SQLException {
1552
+ setIntegerParameter(runtime, connection, statement, index, type, value);
1553
+ break;
1554
+ case Types.BIGINT:
1555
+ setBigIntegerParameter(runtime, connection, statement, index, type, value);
1556
+ break;
1557
+ case Types.REAL:
1558
+ case Types.FLOAT:
1559
+ case Types.DOUBLE:
1560
+ setDoubleParameter(runtime, connection, statement, index, type, value);
1561
+ break;
1562
+ case Types.NUMERIC:
1563
+ case Types.DECIMAL:
1564
+ setDecimalParameter(runtime, connection, statement, index, type, value);
1565
+ break;
1566
+ case Types.DATE:
1567
+ setDateParameter(runtime, connection, statement, index, type, value);
1568
+ break;
1569
+ case Types.TIME:
1570
+ setTimeParameter(runtime, connection, statement, index, type, value);
1571
+ break;
1572
+ case Types.TIMESTAMP:
1573
+ setTimestampParameter(runtime, connection, statement, index, type, value);
1574
+ break;
1575
+ case Types.BIT:
1576
+ case Types.BOOLEAN:
1577
+ setBooleanParameter(runtime, connection, statement, index, type, value);
1578
+ break;
1579
+ case Types.SQLXML:
1580
+ setXmlParameter(runtime, connection, statement, index, type, value);
1581
+ break;
1582
+ case Types.ARRAY:
1583
+ setArrayParameter(runtime, connection, statement, index, type, value);
1584
+ break;
1585
+ case Types.JAVA_OBJECT:
1586
+ case Types.OTHER:
1587
+ setObjectParameter(runtime, connection, statement, index, type, value);
1588
+ break;
1589
+ case Types.BINARY:
1590
+ case Types.VARBINARY:
1591
+ case Types.LONGVARBINARY:
1592
+ case Types.BLOB:
1593
+ setBlobParameter(runtime, connection, statement, index, type, value);
1594
+ break;
1595
+ case Types.CLOB:
1596
+ case Types.NCLOB: // JDBC 4.0
1597
+ setClobParameter(runtime, connection, statement, index, type, value);
1598
+ break;
1599
+ case Types.CHAR:
1600
+ case Types.VARCHAR:
1601
+ case Types.NCHAR: // JDBC 4.0
1602
+ case Types.NVARCHAR: // JDBC 4.0
1603
+ default:
1604
+ setStringParameter(runtime, connection, statement, index, type, value);
1605
+ }
1606
+ }
1607
+
1608
+ @Deprecated
1609
+ private void setPreparedStatementValues(final ThreadContext context,
1610
+ final Connection connection, final PreparedStatement statement,
1611
+ final IRubyObject valuesArg, final IRubyObject typesArg) throws SQLException {
1612
+ final Ruby runtime = context.getRuntime();
1613
+ final RubyArray values = (RubyArray) valuesArg;
1614
+ final RubyArray types = (RubyArray) typesArg; // column types
1615
+ for( int i = 0, j = values.getLength(); i < j; i++ ) {
1616
+ setStatementParameter(
1617
+ context, runtime, connection, statement, i + 1,
1618
+ values.eltInternal(i), types.eltInternal(i)
1619
+ );
1620
+ }
1621
+ }
1622
+
1623
+ private RubySymbol resolveColumnType(final ThreadContext context, final Ruby runtime,
1624
+ final IRubyObject column) {
1625
+ if ( column instanceof RubySymbol ) { // deprecated behavior
1626
+ return (RubySymbol) column;
1627
+ }
1628
+ if ( column instanceof RubyString) { // deprecated behavior
1629
+ if ( runtime.is1_9() ) {
1630
+ return ( (RubyString) column ).intern19();
1631
+ }
1632
+ else {
1633
+ return ( (RubyString) column ).intern();
1634
+ }
1635
+ }
1636
+
1637
+ if ( column == null || column.isNil() ) {
1638
+ throw runtime.newArgumentError("nil column passed");
1639
+ }
1640
+ return (RubySymbol) column.callMethod(context, "type");
1641
+ }
1642
+
1643
+ /* protected */ int jdbcTypeFor(final Ruby runtime, final IRubyObject column,
1644
+ final RubySymbol columnType, final Object value) throws SQLException {
1645
+
1646
+ final String internedType = columnType.asJavaString();
1647
+
1648
+ if ( internedType == (Object) "string" ) return Types.VARCHAR;
1649
+ else if ( internedType == (Object) "text" ) return Types.CLOB;
1650
+ else if ( internedType == (Object) "integer" ) return Types.INTEGER;
1651
+ else if ( internedType == (Object) "decimal" ) return Types.DECIMAL;
1652
+ else if ( internedType == (Object) "float" ) return Types.FLOAT;
1653
+ else if ( internedType == (Object) "date" ) return Types.DATE;
1654
+ else if ( internedType == (Object) "time" ) return Types.TIME;
1655
+ else if ( internedType == (Object) "datetime") return Types.TIMESTAMP;
1656
+ else if ( internedType == (Object) "timestamp" ) return Types.TIMESTAMP;
1657
+ else if ( internedType == (Object) "binary" ) return Types.BLOB;
1658
+ else if ( internedType == (Object) "boolean" ) return Types.BOOLEAN;
1659
+ else if ( internedType == (Object) "xml" ) return Types.SQLXML;
1660
+ else if ( internedType == (Object) "array" ) return Types.ARRAY;
1661
+ else return Types.OTHER; // -1 as well as 0 are used in Types
1662
+ }
1663
+
1664
+ /* protected */ void setIntegerParameter(final Ruby runtime, final Connection connection,
1665
+ final PreparedStatement statement, final int index, final int type,
1666
+ final Object value) throws SQLException {
1667
+ if ( value instanceof IRubyObject ) {
1668
+ setIntegerParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1669
+ }
1670
+ else {
1671
+ if ( value == null ) statement.setNull(index, Types.INTEGER);
1672
+ else {
1673
+ statement.setLong(index, ((Number) value).longValue());
1674
+ }
1675
+ }
1676
+ }
1677
+
1678
+ /* protected */ void setIntegerParameter(final Ruby runtime, final Connection connection,
1679
+ final PreparedStatement statement, final int index, final int type,
1680
+ final IRubyObject value) throws SQLException {
1681
+ if ( value.isNil() ) statement.setNull(index, Types.INTEGER);
1682
+ else {
1683
+ if ( value instanceof RubyFixnum ) {
1684
+ statement.setLong(index, ((RubyFixnum) value).getLongValue());
1685
+ }
1686
+ else {
1687
+ statement.setInt(index, RubyNumeric.fix2int(value));
1688
+ }
1689
+ }
1690
+ }
1691
+
1692
+ /* protected */ void setBigIntegerParameter(final Ruby runtime, final Connection connection,
1693
+ final PreparedStatement statement, final int index, final int type,
1694
+ final Object value) throws SQLException {
1695
+ if ( value instanceof IRubyObject ) {
1696
+ setBigIntegerParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1697
+ }
1698
+ else {
1699
+ if ( value == null ) statement.setNull(index, Types.BIGINT);
1700
+ else {
1701
+ if ( value instanceof BigDecimal ) {
1702
+ statement.setBigDecimal(index, (BigDecimal) value);
1703
+ }
1704
+ else if ( value instanceof BigInteger ) {
1705
+ setLongOrDecimalParameter(statement, index, (BigInteger) value);
1706
+ }
1707
+ else {
1708
+ statement.setLong(index, ((Number) value).longValue());
1709
+ }
1710
+ }
1711
+ }
1712
+ }
1713
+
1714
+ /* protected */ void setBigIntegerParameter(final Ruby runtime, final Connection connection,
1715
+ final PreparedStatement statement, final int index, final int type,
1716
+ final IRubyObject value) throws SQLException {
1717
+ if ( value.isNil() ) statement.setNull(index, Types.INTEGER);
1718
+ else {
1719
+ if ( value instanceof RubyBignum ) {
1720
+ setLongOrDecimalParameter(statement, index, ((RubyBignum) value).getValue());
1721
+ }
1722
+ else {
1723
+ statement.setLong(index, ((RubyInteger) value).getLongValue());
1724
+ }
1725
+ }
1726
+ }
1727
+
1728
+ private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
1729
+ private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
1730
+
1731
+ /* protected */ static void setLongOrDecimalParameter(final PreparedStatement statement,
1732
+ final int index, final BigInteger value) throws SQLException {
1733
+ if ( value.compareTo(MAX_LONG) <= 0 // -1 intValue < MAX_VALUE
1734
+ && value.compareTo(MIN_LONG) >= 0 ) {
1735
+ statement.setLong(index, value.longValue());
1736
+ }
1737
+ else {
1738
+ statement.setBigDecimal(index, new BigDecimal(value));
1739
+ }
1740
+ }
1741
+
1742
+ /* protected */ void setDoubleParameter(final Ruby runtime, final Connection connection,
1743
+ final PreparedStatement statement, final int index, final int type,
1744
+ final Object value) throws SQLException {
1745
+ if ( value instanceof IRubyObject ) {
1746
+ setDoubleParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1747
+ }
1748
+ else {
1749
+ if ( value == null ) statement.setNull(index, Types.DOUBLE);
1750
+ else {
1751
+ statement.setDouble(index, ((Number) value).doubleValue());
1752
+ }
1753
+ }
1754
+ }
1755
+
1756
+ /* protected */ void setDoubleParameter(final Ruby runtime, final Connection connection,
1757
+ final PreparedStatement statement, final int index, final int type,
1758
+ final IRubyObject value) throws SQLException {
1759
+ if ( value.isNil() ) statement.setNull(index, Types.DOUBLE);
1760
+ else {
1761
+ statement.setDouble(index, ((RubyNumeric) value).getDoubleValue());
1762
+ }
1763
+ }
1764
+
1765
+ /* protected */ void setDecimalParameter(final Ruby runtime, final Connection connection,
1766
+ final PreparedStatement statement, final int index, final int type,
1767
+ final Object value) throws SQLException {
1768
+ if ( value instanceof IRubyObject ) {
1769
+ setDecimalParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1770
+ }
1771
+ else {
1772
+ if ( value == null ) statement.setNull(index, Types.DECIMAL);
1773
+ else {
1774
+ if ( value instanceof BigDecimal ) {
1775
+ statement.setBigDecimal(index, (BigDecimal) value);
1776
+ }
1777
+ else if ( value instanceof BigInteger ) {
1778
+ setLongOrDecimalParameter(statement, index, (BigInteger) value);
1779
+ }
1780
+ else {
1781
+ statement.setDouble(index, ((Number) value).doubleValue());
1782
+ }
1783
+ }
1784
+ }
1785
+ }
1786
+
1787
+ /* protected */ void setDecimalParameter(final Ruby runtime, final Connection connection,
1788
+ final PreparedStatement statement, final int index, final int type,
1789
+ final IRubyObject value) throws SQLException {
1790
+ if ( value.isNil() ) statement.setNull(index, Types.DECIMAL);
1791
+ else {
1792
+ // NOTE: RubyBigDecimal moved into org.jruby.ext.bigdecimal (1.6 -> 1.7)
1793
+ if ( value.getMetaClass().getName().indexOf("BigDecimal") != -1 ) {
1794
+ try { // reflect ((RubyBigDecimal) value).getValue() :
1795
+ BigDecimal decValue = (BigDecimal) value.getClass().
1796
+ getMethod("getValue", (Class<?>[]) null).
1797
+ invoke(value, (Object[]) null);
1798
+ statement.setBigDecimal(index, decValue);
1799
+ }
1800
+ catch (NoSuchMethodException e) {
1801
+ throw new RuntimeException(e);
1802
+ }
1803
+ catch (IllegalAccessException e) {
1804
+ throw new RuntimeException(e);
1805
+ }
1806
+ catch (InvocationTargetException e) {
1807
+ throw new RuntimeException(e.getCause() != null ? e.getCause() : e);
1808
+ }
1809
+ }
1810
+ else {
1811
+ statement.setDouble(index, ((RubyNumeric) value).getDoubleValue());
1812
+ }
1813
+ }
1814
+ }
1815
+
1816
+ /* protected */ void setTimestampParameter(final Ruby runtime, final Connection connection,
1817
+ final PreparedStatement statement, final int index, final int type,
1818
+ final Object value) throws SQLException {
1819
+ if ( value instanceof IRubyObject ) {
1820
+ setTimestampParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1821
+ }
1822
+ else {
1823
+ if ( value == null ) statement.setNull(index, Types.TIMESTAMP);
1824
+ else {
1825
+ if ( value instanceof Timestamp ) {
1826
+ statement.setTimestamp(index, (Timestamp) value);
1827
+ }
1828
+ else if ( value instanceof java.util.Date ) {
1829
+ statement.setTimestamp(index, new Timestamp(((java.util.Date) value).getTime()));
1830
+ }
1831
+ else {
1832
+ statement.setTimestamp(index, Timestamp.valueOf(value.toString()));
1833
+ }
1834
+ }
1835
+ }
1836
+ }
1837
+
1838
+ /* protected */ void setTimestampParameter(final Ruby runtime, final Connection connection,
1839
+ final PreparedStatement statement, final int index, final int type,
1840
+ final IRubyObject value) throws SQLException {
1841
+ if ( value.isNil() ) statement.setNull(index, Types.TIMESTAMP);
1842
+ else {
1843
+ if ( value instanceof RubyTime ) {
1844
+ final RubyTime timeValue = (RubyTime) value;
1845
+ final java.util.Date dateValue = timeValue.getJavaDate();
1846
+
1847
+ long millis = dateValue.getTime();
1848
+ Timestamp timestamp = new Timestamp(millis);
1849
+ Calendar calendar = Calendar.getInstance();
1850
+ calendar.setTime(dateValue);
1851
+ if ( type != Types.DATE ) {
1852
+ int micros = (int) timeValue.microseconds();
1853
+ timestamp.setNanos( micros * 1000 ); // time.nsec ~ time.usec * 1000
1854
+ }
1855
+ statement.setTimestamp( index, timestamp, calendar );
1856
+ }
1857
+ else {
1858
+ final String stringValue = value.convertToString().toString();
1859
+ // yyyy-[m]m-[d]d hh:mm:ss[.f...]
1860
+ final Timestamp timestamp = Timestamp.valueOf( stringValue );
1861
+ statement.setTimestamp( index, timestamp, Calendar.getInstance() );
1862
+ }
1863
+ }
1864
+ }
1865
+
1866
+ /* protected */ void setTimeParameter(final Ruby runtime, final Connection connection,
1867
+ final PreparedStatement statement, final int index, final int type,
1868
+ final Object value) throws SQLException {
1869
+ if ( value instanceof IRubyObject ) {
1870
+ setTimeParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1871
+ }
1872
+ else {
1873
+ if ( value == null ) statement.setNull(index, Types.TIME);
1874
+ else {
1875
+ if ( value instanceof Time ) {
1876
+ statement.setTime(index, (Time) value);
1877
+ }
1878
+ else if ( value instanceof java.util.Date ) {
1879
+ statement.setTime(index, new Time(((java.util.Date) value).getTime()));
1880
+ }
1881
+ else { // hh:mm:ss
1882
+ statement.setTime(index, Time.valueOf(value.toString()));
1883
+ // statement.setString(index, value.toString());
1884
+ }
1885
+ }
1886
+ }
1887
+ }
1888
+
1889
+ /* protected */ void setTimeParameter(final Ruby runtime, final Connection connection,
1890
+ final PreparedStatement statement, final int index, final int type,
1891
+ final IRubyObject value) throws SQLException {
1892
+ if ( value.isNil() ) statement.setNull(index, Types.TIME);
1893
+ else {
1894
+ // setTimestampParameter(runtime, connection, statement, index, type, value);
1895
+ if ( value instanceof RubyTime ) {
1896
+ final RubyTime timeValue = (RubyTime) value;
1897
+ final java.util.Date dateValue = timeValue.getJavaDate();
1898
+
1899
+ Time time = new Time(dateValue.getTime());
1900
+ Calendar calendar = Calendar.getInstance();
1901
+ calendar.setTime(dateValue);
1902
+ statement.setTime( index, time, calendar );
1903
+ }
1904
+ else {
1905
+ final String stringValue = value.convertToString().toString();
1906
+ final Time time = Time.valueOf( stringValue );
1907
+ statement.setTime( index, time, Calendar.getInstance() );
1908
+ }
1909
+ }
1910
+ }
1911
+
1912
+ /* protected */ void setDateParameter(final Ruby runtime, final Connection connection,
1913
+ final PreparedStatement statement, final int index, final int type,
1914
+ final Object value) throws SQLException {
1915
+ if ( value instanceof IRubyObject ) {
1916
+ setDateParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1917
+ }
1918
+ else {
1919
+ if ( value == null ) statement.setNull(index, Types.DATE);
1920
+ else {
1921
+ if ( value instanceof Date ) {
1922
+ statement.setDate(index, (Date) value);
1923
+ }
1924
+ else if ( value instanceof java.util.Date ) {
1925
+ statement.setDate(index, new Date(((java.util.Date) value).getTime()));
1926
+ }
1927
+ else { // yyyy-[m]m-[d]d
1928
+ statement.setDate(index, Date.valueOf(value.toString()));
1929
+ // statement.setString(index, value.toString());
1930
+ }
1931
+ }
1932
+ }
1933
+ }
1934
+
1935
+ /* protected */ void setDateParameter(final Ruby runtime, final Connection connection,
1936
+ final PreparedStatement statement, final int index, final int type,
1937
+ final IRubyObject value) throws SQLException {
1938
+ if ( value.isNil() ) statement.setNull(index, Types.DATE);
1939
+ else {
1940
+ // setTimestampParameter(runtime, connection, statement, index, type, value);
1941
+ if ( value instanceof RubyTime ) {
1942
+ final RubyTime timeValue = (RubyTime) value;
1943
+ final java.util.Date dateValue = timeValue.getJavaDate();
1944
+
1945
+ Date date = new Date(dateValue.getTime());
1946
+ Calendar calendar = Calendar.getInstance();
1947
+ calendar.setTime(dateValue);
1948
+ statement.setDate( index, date, calendar );
1949
+ }
1950
+ else {
1951
+ final String stringValue = value.convertToString().toString();
1952
+ final Date date = Date.valueOf( stringValue );
1953
+ statement.setDate( index, date, Calendar.getInstance() );
1954
+ }
1955
+ }
1956
+ }
1957
+
1958
+ /* protected */ void setBooleanParameter(final Ruby runtime, final Connection connection,
1959
+ final PreparedStatement statement, final int index, final int type,
1960
+ final Object value) throws SQLException {
1961
+ if ( value instanceof IRubyObject ) {
1962
+ setBooleanParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1963
+ }
1964
+ else {
1965
+ if ( value == null ) statement.setNull(index, Types.BOOLEAN);
1966
+ else {
1967
+ statement.setBoolean(index, ((Boolean) value).booleanValue());
1968
+ }
1969
+ }
1970
+ }
1971
+
1972
+ /* protected */ void setBooleanParameter(final Ruby runtime, final Connection connection,
1973
+ final PreparedStatement statement, final int index, final int type,
1974
+ final IRubyObject value) throws SQLException {
1975
+ if ( value.isNil() ) statement.setNull(index, Types.BOOLEAN);
1976
+ else {
1977
+ statement.setBoolean(index, value.isTrue());
1978
+ }
1979
+ }
1980
+
1981
+ /* protected */ void setStringParameter(final Ruby runtime, final Connection connection,
1982
+ final PreparedStatement statement, final int index, final int type,
1983
+ final Object value) throws SQLException {
1984
+ if ( value instanceof IRubyObject ) {
1985
+ setStringParameter(runtime, connection, statement, index, type, (IRubyObject) value);
1986
+ }
1987
+ else {
1988
+ if ( value == null ) statement.setNull(index, Types.VARCHAR);
1989
+ else {
1990
+ statement.setString(index, value.toString());
1991
+ }
1992
+ }
1993
+ }
1994
+
1995
+ /* protected */ void setStringParameter(final Ruby runtime, final Connection connection,
1996
+ final PreparedStatement statement, final int index, final int type,
1997
+ final IRubyObject value) throws SQLException {
1998
+ if ( value.isNil() ) statement.setNull(index, Types.VARCHAR);
1999
+ else {
2000
+ statement.setString(index, value.convertToString().toString());
2001
+ }
2002
+ }
2003
+
2004
+ /* protected */ void setArrayParameter(final Ruby runtime, final Connection connection,
2005
+ final PreparedStatement statement, final int index, final int type,
2006
+ final Object value) throws SQLException {
2007
+ if ( value instanceof IRubyObject ) {
2008
+ setArrayParameter(runtime, connection, statement, index, type, (IRubyObject) value);
2009
+ }
2010
+ else {
2011
+ if ( value == null ) statement.setNull(index, Types.ARRAY);
2012
+ else {
2013
+ // TODO get array element type name ?!
2014
+ Array array = connection.createArrayOf(null, (Object[]) value);
2015
+ statement.setArray(index, array);
2016
+ }
2017
+ }
2018
+ }
2019
+
2020
+ /* protected */ void setArrayParameter(final Ruby runtime, final Connection connection,
2021
+ final PreparedStatement statement, final int index, final int type,
2022
+ final IRubyObject value) throws SQLException {
2023
+ if ( value.isNil() ) statement.setNull(index, Types.ARRAY);
2024
+ else {
2025
+ // TODO get array element type name ?!
2026
+ Array array = connection.createArrayOf(null, ((RubyArray) value).toArray());
2027
+ statement.setArray(index, array);
2028
+ }
2029
+ }
2030
+
2031
+ /* protected */ void setXmlParameter(final Ruby runtime, final Connection connection,
2032
+ final PreparedStatement statement, final int index, final int type,
2033
+ final Object value) throws SQLException {
2034
+ if ( value instanceof IRubyObject ) {
2035
+ setXmlParameter(runtime, connection, statement, index, type, (IRubyObject) value);
2036
+ }
2037
+ else {
2038
+ if ( value == null ) statement.setNull(index, Types.SQLXML);
2039
+ else {
2040
+ SQLXML xml = connection.createSQLXML();
2041
+ xml.setString(value.toString());
2042
+ statement.setSQLXML(index, xml);
2043
+ }
2044
+ }
2045
+ }
2046
+
2047
+ /* protected */ void setXmlParameter(final Ruby runtime, final Connection connection,
2048
+ final PreparedStatement statement, final int index, final int type,
2049
+ final IRubyObject value) throws SQLException {
2050
+ if ( value.isNil() ) statement.setNull(index, Types.SQLXML);
2051
+ else {
2052
+ SQLXML xml = connection.createSQLXML();
2053
+ xml.setString(value.convertToString().toString());
2054
+ statement.setSQLXML(index, xml);
2055
+ }
2056
+ }
2057
+
2058
+ /* protected */ void setBlobParameter(final Ruby runtime, final Connection connection,
2059
+ final PreparedStatement statement, final int index, final int type,
2060
+ final Object value) throws SQLException {
2061
+ if ( value instanceof IRubyObject ) {
2062
+ setBlobParameter(runtime, connection, statement, index, type, (IRubyObject) value);
2063
+ }
2064
+ else {
2065
+ if ( value == null ) statement.setNull(index, Types.BLOB);
2066
+ else {
2067
+ statement.setBlob(index, (InputStream) value);
2068
+ }
2069
+ }
2070
+ }
2071
+
2072
+ /* protected */ void setBlobParameter(final Ruby runtime, final Connection connection,
2073
+ final PreparedStatement statement, final int index, final int type,
2074
+ final IRubyObject value) throws SQLException {
2075
+ if ( value.isNil() ) statement.setNull(index, Types.BLOB);
2076
+ else {
2077
+ if ( value instanceof RubyString ) {
2078
+ statement.setBlob(index, new ByteArrayInputStream(((RubyString) value).getBytes()));
2079
+ }
2080
+ else { // assume IO/File
2081
+ statement.setBlob(index, ((RubyIO) value).getInStream());
2082
+ }
2083
+ }
2084
+ }
2085
+
2086
+ /* protected */ void setClobParameter(final Ruby runtime, final Connection connection,
2087
+ final PreparedStatement statement, final int index, final int type,
2088
+ final Object value) throws SQLException {
2089
+ if ( value instanceof IRubyObject ) {
2090
+ setClobParameter(runtime, connection, statement, index, type, (IRubyObject) value);
2091
+ }
2092
+ else {
2093
+ if ( value == null ) statement.setNull(index, Types.CLOB);
2094
+ else {
2095
+ statement.setClob(index, (Reader) value);
2096
+ }
2097
+ }
2098
+ }
2099
+
2100
+ /* protected */ void setClobParameter(final Ruby runtime, final Connection connection,
2101
+ final PreparedStatement statement, final int index, final int type,
2102
+ final IRubyObject value) throws SQLException {
2103
+ if ( value.isNil() ) statement.setNull(index, Types.CLOB);
2104
+ else {
2105
+ if ( value instanceof RubyString ) {
2106
+ statement.setClob(index, new StringReader(((RubyString) value).decodeString()));
2107
+ }
2108
+ else { // assume IO/File
2109
+ statement.setClob(index, new InputStreamReader(((RubyIO) value).getInStream()));
2110
+ }
2111
+ }
2112
+ }
2113
+
2114
+ /* protected */ void setObjectParameter(final Ruby runtime, final Connection connection,
2115
+ final PreparedStatement statement, final int index, final int type,
2116
+ Object value) throws SQLException {
2117
+ if (value instanceof IRubyObject) {
2118
+ value = ((IRubyObject) value).toJava(Object.class);
2119
+ }
2120
+ if ( value == null ) statement.setNull(index, Types.JAVA_OBJECT);
2121
+ statement.setObject(index, value);
2122
+ }
2123
+
2124
+ protected final Connection getConnection() {
2125
+ return getConnection(false);
2126
+ }
2127
+
2128
+ protected Connection getConnection(boolean error) {
2129
+ final Connection connection = (Connection) dataGetStruct();
2130
+ if ( connection == null && error ) {
2131
+ final RubyClass errorClass = getConnectionNotEstablished( getRuntime() );
2132
+ throw new RaiseException(getRuntime(), errorClass, "no connection available", false);
2133
+ }
2134
+ return connection;
2135
+ }
2136
+
2137
+ private synchronized IRubyObject setConnection(final Connection connection) {
2138
+ close( getConnection(false) ); // close previously open connection if there is one
2139
+
2140
+ final IRubyObject rubyConnectionObject =
2141
+ connection != null ? convertJavaToRuby(connection) : getRuntime().getNil();
2142
+ setInstanceVariable( "@connection", rubyConnectionObject );
2143
+ dataWrapStruct(connection);
2144
+ return rubyConnectionObject;
2145
+ }
2146
+
2147
+ protected boolean isConnectionValid(final ThreadContext context, final Connection connection) {
2148
+ if ( connection == null ) return false;
2149
+ final IRubyObject alive_sql = getConfigValue(context, "connection_alive_sql");
2150
+ Statement statement = null;
2151
+ try {
2152
+ RubyString aliveSQL = alive_sql.isNil() ? null : alive_sql.convertToString();
2153
+ if ( aliveSQL != null && isSelect(aliveSQL) ) {
2154
+ // expect a SELECT/CALL SQL statement
2155
+ statement = createStatement(context, connection);
2156
+ statement.execute( aliveSQL.toString() );
2157
+ return true; // connection alive
2158
+ }
2159
+ else { // alive_sql nil (or not a statement we can execute)
2160
+ return connection.isValid(0); // since JDBC 4.0
2161
+ }
2162
+ }
2163
+ catch (Exception e) {
2164
+ debugMessage(context, "connection considered broken due: " + e.toString());
2165
+ return false;
2166
+ }
2167
+ finally { close(statement); }
2168
+ }
2169
+
2170
+ private boolean tableExists(final Ruby runtime,
2171
+ final Connection connection, final TableName tableName) throws SQLException {
1486
2172
  final IRubyObject matchedTables =
1487
2173
  matchTables(runtime, connection, tableName.catalog, tableName.schema, tableName.name, getTableTypes(), true);
1488
2174
  // NOTE: allow implementers to ignore checkExistsOnly paramater - empty array means does not exists
@@ -1570,6 +2256,7 @@ public class RubyJdbcConnection extends RubyObject {
1570
2256
  final String catalog, final String schemaPattern,
1571
2257
  final String tablePattern, final String[] types) {
1572
2258
  return new SQLBlock() {
2259
+ @Override
1573
2260
  public IRubyObject call(final Connection connection) throws SQLException {
1574
2261
  return matchTables(runtime, connection, catalog, schemaPattern, tablePattern, types, false);
1575
2262
  }
@@ -1733,13 +2420,31 @@ public class RubyJdbcConnection extends RubyObject {
1733
2420
  final DatabaseMetaData metaData, final ResultSet resultSet,
1734
2421
  final boolean downCase) throws SQLException {
1735
2422
 
1736
- ColumnData[] columns = extractColumns(runtime, metaData, resultSet, downCase);
2423
+ final ColumnData[] columns = extractColumns(runtime, metaData, resultSet, downCase);
1737
2424
 
1738
2425
  final RubyArray results = runtime.newArray();
1739
2426
  // [ { 'col1': 1, 'col2': 2 }, { 'col1': 3, 'col2': 4 } ]
1740
2427
  populateFromResultSet(context, runtime, (List<IRubyObject>) results, resultSet, columns);
1741
2428
  return results;
1742
2429
  }
2430
+
2431
+ private IRubyObject yieldResultRows(final ThreadContext context, final Ruby runtime,
2432
+ final DatabaseMetaData metaData, final ResultSet resultSet,
2433
+ final Block block) throws SQLException {
2434
+
2435
+ final ColumnData[] columns = extractColumns(runtime, metaData, resultSet, false);
2436
+
2437
+ final IRubyObject[] blockArgs = new IRubyObject[columns.length];
2438
+ while ( resultSet.next() ) {
2439
+ for ( int i = 0; i < columns.length; i++ ) {
2440
+ final ColumnData column = columns[i];
2441
+ blockArgs[i] = jdbcToRuby(runtime, column.index, column.type, resultSet);
2442
+ }
2443
+ block.call( context, blockArgs );
2444
+ }
2445
+
2446
+ return runtime.getNil(); // yielded result rows
2447
+ }
1743
2448
 
1744
2449
  /**
1745
2450
  * Extract columns from result set.
@@ -1797,7 +2502,7 @@ public class RubyJdbcConnection extends RubyObject {
1797
2502
  tries = (int) retryCount.convertToInteger().getLongValue();
1798
2503
  if ( tries <= 0 ) tries = 1;
1799
2504
  }
1800
- if ( isConnectionBroken(context, connection) ) {
2505
+ if ( ! isConnectionValid(context, connection) ) {
1801
2506
  reconnect(context); continue; // retry connection (block) again
1802
2507
  }
1803
2508
  break; // connection not broken yet failed
@@ -1858,10 +2563,9 @@ public class RubyJdbcConnection extends RubyObject {
1858
2563
  exception.getMessage() : exception.toString(); // useful to easily see type on Ruby side
1859
2564
  final RaiseException error = wrapException(context, getJDBCError(runtime), exception, message);
1860
2565
  final int errorCode = ((SQLException) exception).getErrorCode();
1861
- RuntimeHelpers.invoke( context, error.getException(),
1862
- "errno=", runtime.newFixnum(errorCode) );
1863
- RuntimeHelpers.invoke( context, error.getException(),
1864
- "sql_exception=", JavaEmbedUtils.javaToRuby(runtime, exception) );
2566
+ final RubyException self = error.getException();
2567
+ self.getMetaClass().finvoke(context, self, "errno=", runtime.newFixnum(errorCode));
2568
+ self.getMetaClass().finvoke(context, self, "sql_exception=", JavaEmbedUtils.javaToRuby(runtime, exception));
1865
2569
  return error;
1866
2570
  }
1867
2571
  return wrapException(context, getJDBCError(runtime), exception);
@@ -2055,6 +2759,12 @@ public class RubyJdbcConnection extends RubyObject {
2055
2759
  this.schema = schema;
2056
2760
  this.name = table;
2057
2761
  }
2762
+
2763
+ @Override
2764
+ public String toString() {
2765
+ return getClass().getName() +
2766
+ "{catalog=" + catalog + ",schema=" + schema + ",name=" + name + "}";
2767
+ }
2058
2768
 
2059
2769
  }
2060
2770
 
@@ -2206,4 +2916,36 @@ public class RubyJdbcConnection extends RubyObject {
2206
2916
  }
2207
2917
  }
2208
2918
 
2919
+ private static RubyArray createCallerBacktrace(final ThreadContext context) {
2920
+ final Ruby runtime = context.getRuntime();
2921
+ runtime.incrementCallerCount();
2922
+
2923
+ Method gatherCallerBacktrace; RubyStackTraceElement[] trace;
2924
+ try {
2925
+ gatherCallerBacktrace = context.getClass().getMethod("gatherCallerBacktrace");
2926
+ trace = (RubyStackTraceElement[]) gatherCallerBacktrace.invoke(context); // 1.6.8
2927
+ }
2928
+ catch (NoSuchMethodException ignore) {
2929
+ try {
2930
+ gatherCallerBacktrace = context.getClass().getMethod("gatherCallerBacktrace", Integer.TYPE);
2931
+ trace = (RubyStackTraceElement[]) gatherCallerBacktrace.invoke(context, 0); // 1.7.4
2932
+ }
2933
+ catch (NoSuchMethodException e) { throw new RuntimeException(e); }
2934
+ catch (IllegalAccessException e) { throw new RuntimeException(e); }
2935
+ catch (InvocationTargetException e) { throw new RuntimeException(e.getTargetException()); }
2936
+ }
2937
+ catch (IllegalAccessException e) { throw new RuntimeException(e); }
2938
+ catch (InvocationTargetException e) { throw new RuntimeException(e.getTargetException()); }
2939
+ // RubyStackTraceElement[] trace = context.gatherCallerBacktrace(level);
2940
+
2941
+ final RubyArray backtrace = runtime.newArray(trace.length);
2942
+ for (int i = 0; i < trace.length; i++) {
2943
+ RubyStackTraceElement element = trace[i];
2944
+ backtrace.append( RubyString.newString(runtime,
2945
+ element.getFileName() + ":" + element.getLineNumber() + ":in `" + element.getMethodName() + "'"
2946
+ ) );
2947
+ }
2948
+ return backtrace;
2949
+ }
2950
+
2209
2951
  }