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

Sign up to get free protection for your applications and to get access to all the features.
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
  }