activerecord-jdbc-adapter 1.2.9.1 → 1.3.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (231) hide show
  1. data/.travis.yml +3 -0
  2. data/Appraisals +12 -4
  3. data/Gemfile +3 -3
  4. data/Gemfile.lock +19 -19
  5. data/History.txt +90 -16
  6. data/LICENSE.txt +2 -1
  7. data/README.md +14 -1
  8. data/activerecord-jdbc-adapter.gemspec +2 -2
  9. data/gemfiles/rails23.gemfile +5 -5
  10. data/gemfiles/rails23.gemfile.lock +27 -27
  11. data/gemfiles/rails30.gemfile +3 -3
  12. data/gemfiles/rails30.gemfile.lock +8 -8
  13. data/gemfiles/rails31.gemfile +4 -4
  14. data/gemfiles/rails31.gemfile.lock +18 -18
  15. data/gemfiles/rails32.gemfile +4 -4
  16. data/gemfiles/rails32.gemfile.lock +17 -17
  17. data/gemfiles/rails40.gemfile +17 -0
  18. data/gemfiles/rails40.gemfile.lock +126 -0
  19. data/lib/activerecord-jdbc-adapter.rb +0 -7
  20. data/lib/arjdbc.rb +6 -5
  21. data/lib/arjdbc/db2.rb +1 -1
  22. data/lib/arjdbc/db2/adapter.rb +52 -29
  23. data/lib/arjdbc/db2/connection_methods.rb +13 -14
  24. data/lib/arjdbc/derby.rb +1 -1
  25. data/lib/arjdbc/derby/adapter.rb +29 -9
  26. data/lib/arjdbc/derby/connection_methods.rb +17 -20
  27. data/lib/arjdbc/firebird.rb +1 -1
  28. data/lib/arjdbc/h2.rb +2 -2
  29. data/lib/arjdbc/h2/adapter.rb +1 -1
  30. data/lib/arjdbc/h2/connection_methods.rb +12 -16
  31. data/lib/arjdbc/hsqldb.rb +1 -1
  32. data/lib/arjdbc/hsqldb/connection_methods.rb +13 -16
  33. data/lib/arjdbc/informix.rb +1 -1
  34. data/lib/arjdbc/informix/connection_methods.rb +8 -10
  35. data/lib/arjdbc/jdbc.rb +1 -1
  36. data/lib/arjdbc/jdbc/adapter.rb +125 -53
  37. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  38. data/lib/arjdbc/jdbc/base_ext.rb +34 -9
  39. data/lib/arjdbc/jdbc/column.rb +15 -2
  40. data/lib/arjdbc/jdbc/connection.rb +0 -2
  41. data/lib/arjdbc/jdbc/connection_methods.rb +10 -3
  42. data/lib/arjdbc/jdbc/driver.rb +2 -2
  43. data/lib/arjdbc/jdbc/extension.rb +35 -21
  44. data/lib/arjdbc/jdbc/java.rb +0 -2
  45. data/lib/arjdbc/jdbc/missing_functionality_helper.rb +35 -25
  46. data/lib/arjdbc/jdbc/railtie.rb +2 -9
  47. data/lib/arjdbc/mimer.rb +1 -1
  48. data/lib/arjdbc/mssql.rb +2 -2
  49. data/lib/arjdbc/mssql/adapter.rb +271 -92
  50. data/lib/arjdbc/mssql/connection_methods.rb +30 -32
  51. data/lib/arjdbc/mssql/explain_support.rb +107 -0
  52. data/lib/arjdbc/mssql/limit_helpers.rb +48 -18
  53. data/lib/arjdbc/mysql.rb +1 -1
  54. data/lib/arjdbc/mysql/adapter.rb +63 -14
  55. data/lib/arjdbc/mysql/connection_methods.rb +22 -24
  56. data/lib/arjdbc/mysql/explain_support.rb +2 -5
  57. data/lib/arjdbc/oracle.rb +1 -1
  58. data/lib/arjdbc/oracle/adapter.rb +78 -38
  59. data/lib/arjdbc/oracle/connection_methods.rb +9 -10
  60. data/lib/arjdbc/postgresql.rb +1 -1
  61. data/lib/arjdbc/postgresql/adapter.rb +964 -380
  62. data/lib/arjdbc/postgresql/column_cast.rb +136 -0
  63. data/lib/arjdbc/postgresql/connection_methods.rb +19 -21
  64. data/lib/arjdbc/postgresql/explain_support.rb +3 -6
  65. data/lib/arjdbc/railtie.rb +9 -0
  66. data/lib/arjdbc/sqlite3.rb +1 -1
  67. data/lib/arjdbc/sqlite3/adapter.rb +73 -26
  68. data/lib/arjdbc/sqlite3/connection_methods.rb +27 -28
  69. data/lib/arjdbc/sqlite3/explain_support.rb +2 -5
  70. data/lib/arjdbc/sybase.rb +1 -1
  71. data/lib/arjdbc/version.rb +5 -4
  72. data/pom.xml +8 -0
  73. data/rakelib/02-test.rake +57 -51
  74. data/rakelib/compile.rake +17 -5
  75. data/rakelib/rails.rake +42 -31
  76. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +4 -3
  77. data/src/java/arjdbc/derby/DerbyModule.java +98 -85
  78. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +70 -0
  79. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +0 -4
  80. data/src/java/arjdbc/jdbc/AdapterJavaService.java +26 -15
  81. data/src/java/arjdbc/jdbc/Callable.java +44 -0
  82. data/src/java/arjdbc/jdbc/JdbcConnectionFactory.java +10 -2
  83. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1675 -834
  84. data/src/java/arjdbc/jdbc/SQLBlock.java +9 -3
  85. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +73 -36
  86. data/src/java/arjdbc/mysql/MySQLModule.java +11 -10
  87. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +86 -80
  88. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +27 -7
  89. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +214 -0
  90. data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +25 -67
  91. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +52 -49
  92. data/src/java/arjdbc/util/QuotingUtils.java +6 -6
  93. data/test/abstract_db_create.rb +11 -11
  94. data/test/activerecord/connection_adapters/type_conversion_test.rb +18 -12
  95. data/test/db/db2.rb +1 -1
  96. data/test/{db2_binary_test.rb → db/db2/binary_test.rb} +0 -0
  97. data/test/db/db2/has_many_through_test.rb +6 -0
  98. data/test/{db2_reset_column_information_test.rb → db/db2/reset_column_information_test.rb} +1 -2
  99. data/test/{db2_serialize_test.rb → db/db2/serialize_test.rb} +0 -0
  100. data/test/{db2_simple_test.rb → db/db2/simple_test.rb} +1 -8
  101. data/test/db/db2/test_helper.rb +6 -0
  102. data/test/{db2_test.rb → db/db2/unit_test.rb} +1 -1
  103. data/test/db/derby.rb +1 -1
  104. data/test/{derby_binary_test.rb → db/derby/binary_test.rb} +0 -0
  105. data/test/{derby_migration_test.rb → db/derby/migration_test.rb} +0 -0
  106. data/test/{derby_reset_column_information_test.rb → db/derby/reset_column_information_test.rb} +0 -0
  107. data/test/{derby_row_locking_test.rb → db/derby/row_locking_test.rb} +1 -4
  108. data/test/db/derby/schema_dump_test.rb +5 -0
  109. data/test/{derby_serialize_test.rb → db/derby/serialize_test.rb} +0 -0
  110. data/test/{derby_simple_test.rb → db/derby/simple_test.rb} +23 -38
  111. data/test/db/derby/test_helper.rb +6 -0
  112. data/test/db/derby/unit_test.rb +32 -0
  113. data/test/db/derby/xml_column_test.rb +17 -0
  114. data/test/db/h2.rb +1 -1
  115. data/test/{h2_binary_test.rb → db/h2/binary_test.rb} +0 -0
  116. data/test/{h2_change_column_test.rb → db/h2/change_column_test.rb} +1 -0
  117. data/test/{h2_schema_dump_test.rb → db/h2/schema_dump_test.rb} +0 -0
  118. data/test/{h2_serialize_test.rb → db/h2/serialize_test.rb} +0 -0
  119. data/test/{h2_simple_test.rb → db/h2/simple_test.rb} +3 -1
  120. data/test/db/hsqldb.rb +1 -1
  121. data/test/{hsqldb_binary_test.rb → db/hsqldb/binary_test.rb} +0 -0
  122. data/test/{hsqldb_schema_dump_test.rb → db/hsqldb/schema_dump_test.rb} +0 -0
  123. data/test/{hsqldb_serialize_test.rb → db/hsqldb/serialize_test.rb} +0 -0
  124. data/test/{hsqldb_simple_test.rb → db/hsqldb/simple_test.rb} +3 -1
  125. data/test/db/informix.rb +1 -1
  126. data/test/db/jdbc.rb +3 -2
  127. data/test/db/jdbc_derby.rb +1 -1
  128. data/test/db/jdbc_h2.rb +1 -1
  129. data/test/db/jdbc_mysql.rb +1 -1
  130. data/test/db/jdbc_postgres.rb +1 -1
  131. data/test/db/jndi_config.rb +1 -2
  132. data/test/db/jndi_pooled_config.rb +2 -3
  133. data/test/db/mssql.rb +2 -2
  134. data/test/{mssql_binary_test.rb → db/mssql/binary_test.rb} +0 -0
  135. data/test/{mssql_db_create_test.rb → db/mssql/db_create_test.rb} +1 -1
  136. data/test/db/mssql/exec_proc_test.rb +46 -0
  137. data/test/{mssql_identity_insert_test.rb → db/mssql/identity_insert_test.rb} +0 -0
  138. data/test/db/mssql/ignore_system_views_test.rb +40 -0
  139. data/test/{mssql_limit_offset_test.rb → db/mssql/limit_offset_test.rb} +10 -1
  140. data/test/{mssql_multibyte_test.rb → db/mssql/multibyte_test.rb} +0 -0
  141. data/test/db/mssql/multiple_connections_test.rb +71 -0
  142. data/test/{mssql_reset_column_information_test.rb → db/mssql/reset_column_information_test.rb} +0 -0
  143. data/test/{mssql_row_locking_test.rb → db/mssql/row_locking_test.rb} +0 -0
  144. data/test/{mssql_serialize_test.rb → db/mssql/serialize_test.rb} +1 -1
  145. data/test/db/mssql/simple_test.rb +140 -0
  146. data/test/db/mssql/transaction_test.rb +6 -0
  147. data/test/db/mssql/types_test.rb +205 -0
  148. data/test/{mssql_test.rb → db/mssql/unit_test.rb} +2 -2
  149. data/test/db/mysql.rb +1 -2
  150. data/test/db/mysql/_rails_test_mysql.32.out +6768 -0
  151. data/test/{mysql_binary_test.rb → db/mysql/binary_test.rb} +0 -0
  152. data/test/db/mysql/connection_test.rb +51 -0
  153. data/test/{mysql_db_create_test.rb → db/mysql/db_create_test.rb} +0 -0
  154. data/test/{mysql_index_length_test.rb → db/mysql/index_length_test.rb} +0 -0
  155. data/test/{mysql_multibyte_test.rb → db/mysql/multibyte_test.rb} +0 -0
  156. data/test/{mysql_nonstandard_primary_key_test.rb → db/mysql/nonstandard_primary_key_test.rb} +0 -0
  157. data/test/{mysql_reset_column_information_test.rb → db/mysql/reset_column_information_test.rb} +0 -0
  158. data/test/{mysql_schema_dump_test.rb → db/mysql/schema_dump_test.rb} +9 -1
  159. data/test/{mysql_serialize_test.rb → db/mysql/serialize_test.rb} +0 -0
  160. data/test/{mysql_simple_test.rb → db/mysql/simple_test.rb} +16 -8
  161. data/test/db/mysql/transaction_test.rb +6 -0
  162. data/test/db/mysql/types_test.rb +30 -0
  163. data/test/{mysql_test.rb → db/mysql/unit_test.rb} +1 -1
  164. data/test/db/mysql_config.rb +1 -1
  165. data/test/db/oracle.rb +1 -1
  166. data/test/{oracle_binary_test.rb → db/oracle/binary_test.rb} +0 -0
  167. data/test/{oracle_limit_test.rb → db/oracle/limit_test.rb} +0 -0
  168. data/test/db/oracle/multibyte_test.rb +22 -0
  169. data/test/{oracle_reset_column_information_test.rb → db/oracle/reset_column_information_test.rb} +0 -0
  170. data/test/{oracle_serialize_test.rb → db/oracle/serialize_test.rb} +0 -0
  171. data/test/{oracle_simple_test.rb → db/oracle/simple_test.rb} +14 -19
  172. data/test/{oracle_specific_test.rb → db/oracle/specific_test.rb} +62 -16
  173. data/test/db/oracle/transaction_test.rb +31 -0
  174. data/test/db/oracle/unit_test.rb +31 -0
  175. data/test/db/postgres.rb +1 -1
  176. data/test/db/postgres/_rails_test_postgres.32.out +6777 -0
  177. data/test/db/postgres/a_custom_primary_key_test.rb +50 -0
  178. data/test/db/postgres/array_type_test.rb +101 -0
  179. data/test/{postgres_binary_test.rb → db/postgres/binary_test.rb} +0 -0
  180. data/test/db/postgres/connection_test.rb +55 -0
  181. data/test/db/postgres/data_types_test.rb +703 -0
  182. data/test/{postgres_db_create_test.rb → db/postgres/db_create_test.rb} +1 -1
  183. data/test/{postgres_drop_db_test.rb → db/postgres/db_drop_test.rb} +2 -0
  184. data/test/db/postgres/hstore_test.rb +200 -0
  185. data/test/db/postgres/information_schema_leak_test.rb +30 -0
  186. data/test/db/postgres/json_test.rb +86 -0
  187. data/test/db/postgres/ltree_test.rb +50 -0
  188. data/test/{postgres_mixed_case_test.rb → db/postgres/mixed_case_test.rb} +0 -0
  189. data/test/db/postgres/native_types_test.rb +128 -0
  190. data/test/{postgres_reserved_test.rb → db/postgres/reserved_test.rb} +0 -0
  191. data/test/{postgres_reset_column_information_test.rb → db/postgres/reset_column_information_test.rb} +0 -0
  192. data/test/{postgres_row_locking_test.rb → db/postgres/row_locking_test.rb} +0 -0
  193. data/test/{postgres_schema_dump_test.rb → db/postgres/schema_dump_test.rb} +4 -4
  194. data/test/db/postgres/schema_test.rb +113 -0
  195. data/test/{postgres_simple_test.rb → db/postgres/simple_test.rb} +48 -8
  196. data/test/{postgres_table_alias_length_test.rb → db/postgres/table_alias_length_test.rb} +2 -1
  197. data/test/db/postgres/transaction_test.rb +6 -0
  198. data/test/{postgres_test.rb → db/postgres/unit_test.rb} +3 -3
  199. data/test/db/sqlite3.rb +1 -1
  200. data/test/db/sqlite3/_rails_test_sqlite3.32.out +6502 -0
  201. data/test/db/sqlite3/has_many_though_test.rb +6 -0
  202. data/test/{sqlite3_reset_column_information_test.rb → db/sqlite3/reset_column_information_test.rb} +0 -0
  203. data/test/{sqlite3_schema_dump_test.rb → db/sqlite3/schema_dump_test.rb} +0 -0
  204. data/test/{sqlite3_serialize_test.rb → db/sqlite3/serialize_test.rb} +0 -0
  205. data/test/{sqlite3_simple_test.rb → db/sqlite3/simple_test.rb} +63 -63
  206. data/test/db/sqlite3/transaction_test.rb +32 -0
  207. data/test/{sqlite3_type_conversion_test.rb → db/sqlite3/type_conversion_test.rb} +0 -0
  208. data/test/has_many_through.rb +29 -64
  209. data/test/jdbc/oracle.rb +11 -0
  210. data/test/jndi_test.rb +16 -4
  211. data/test/models/auto_id.rb +1 -1
  212. data/test/models/rights_and_roles.rb +57 -0
  213. data/test/row_locking.rb +3 -0
  214. data/test/schema_dump.rb +24 -10
  215. data/test/simple.rb +359 -104
  216. data/test/test_helper.rb +4 -2
  217. data/test/transaction.rb +109 -0
  218. metadata +119 -86
  219. data/lib/arjdbc/jdbc/compatibility.rb +0 -51
  220. data/lib/arjdbc/jdbc/core_ext.rb +0 -24
  221. data/lib/arjdbc/jdbc/discover.rb +0 -18
  222. data/test/derby_schema_dump_test.rb +0 -9
  223. data/test/mssql_ignore_system_views_test.rb +0 -30
  224. data/test/mssql_legacy_types_test.rb +0 -58
  225. data/test/mssql_null_test.rb +0 -14
  226. data/test/mssql_simple_test.rb +0 -51
  227. data/test/postgres_information_schema_leak_test.rb +0 -28
  228. data/test/postgres_native_type_mapping_test.rb +0 -93
  229. data/test/postgres_nonseq_pkey_test.rb +0 -38
  230. data/test/postgres_schema_search_path_test.rb +0 -48
  231. data/test/postgres_type_conversion_test.rb +0 -33
@@ -0,0 +1,70 @@
1
+ /*
2
+ * The MIT License
3
+ *
4
+ * Copyright 2013 Karol Bucek.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ */
24
+ package arjdbc.derby;
25
+
26
+ import java.sql.Connection;
27
+ import java.sql.SQLException;
28
+
29
+ import org.jruby.Ruby;
30
+ import org.jruby.RubyClass;
31
+ import org.jruby.runtime.ObjectAllocator;
32
+ import org.jruby.runtime.builtin.IRubyObject;
33
+
34
+ /**
35
+ * ArJdbc::DerbyJdbcConnection
36
+ *
37
+ * @author kares
38
+ */
39
+ public class DerbyRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection {
40
+
41
+ protected DerbyRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
42
+ super(runtime, metaClass);
43
+ }
44
+
45
+ public static RubyClass createDerbyJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
46
+ final RubyClass clazz = getConnectionAdapters(runtime). // ActiveRecord::ConnectionAdapters
47
+ defineClassUnder("DerbyJdbcConnection", jdbcConnection, DERBY_JDBCCONNECTION_ALLOCATOR);
48
+ clazz.defineAnnotatedMethods(DerbyRubyJdbcConnection.class);
49
+ return clazz;
50
+ }
51
+
52
+ private static ObjectAllocator DERBY_JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
53
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
54
+ return new DerbyRubyJdbcConnection(runtime, klass);
55
+ }
56
+ };
57
+
58
+ @Override
59
+ protected IRubyObject matchTables(final Ruby runtime,
60
+ final Connection connection,
61
+ final String catalog, String schemaPattern,
62
+ final String tablePattern, final String[] types,
63
+ final boolean checkExistsOnly) throws SQLException {
64
+ if (schemaPattern != null && schemaPattern.equals("")) {
65
+ schemaPattern = null; // Derby doesn't like empty-string schema name
66
+ }
67
+ return super.matchTables(runtime, connection, catalog, schemaPattern, tablePattern, types, checkExistsOnly);
68
+ }
69
+
70
+ }
@@ -26,10 +26,6 @@
26
26
 
27
27
  package arjdbc.h2;
28
28
 
29
- import java.sql.ResultSet;
30
- import java.sql.SQLException;
31
- import java.sql.Types;
32
-
33
29
  import arjdbc.jdbc.RubyJdbcConnection;
34
30
 
35
31
  import org.jruby.Ruby;
@@ -31,6 +31,7 @@ import java.io.IOException;
31
31
  import arjdbc.db2.DB2Module;
32
32
  import arjdbc.db2.DB2RubyJdbcConnection;
33
33
  import arjdbc.derby.DerbyModule;
34
+ import arjdbc.derby.DerbyRubyJdbcConnection;
34
35
  import arjdbc.h2.H2RubyJdbcConnection;
35
36
  import arjdbc.hsqldb.HSQLDBModule;
36
37
  import arjdbc.informix.InformixRubyJdbcConnection;
@@ -40,7 +41,7 @@ import arjdbc.mysql.MySQLModule;
40
41
  import arjdbc.mysql.MySQLRubyJdbcConnection;
41
42
  import arjdbc.oracle.OracleModule;
42
43
  import arjdbc.oracle.OracleRubyJdbcConnection;
43
- import arjdbc.postgresql.PostgresqlRubyJdbcConnection;
44
+ import arjdbc.postgresql.PostgreSQLRubyJdbcConnection;
44
45
  import arjdbc.sqlite3.SQLite3Module;
45
46
  import arjdbc.sqlite3.SQLite3RubyJdbcConnection;
46
47
 
@@ -53,24 +54,34 @@ public class AdapterJavaService implements BasicLibraryService {
53
54
 
54
55
  public boolean basicLoad(final Ruby runtime) throws IOException {
55
56
  // ActiveRecord::ConnectionAdapter-s :
56
- RubyClass jdbcConnection = RubyJdbcConnection.createJdbcConnectionClass(runtime);
57
- PostgresqlRubyJdbcConnection.createPostgresqlJdbcConnectionClass(runtime, jdbcConnection);
58
- MSSQLRubyJdbcConnection.createMSSQLJdbcConnectionClass(runtime, jdbcConnection);
59
- InformixRubyJdbcConnection.createInformixJdbcConnectionClass(runtime, jdbcConnection);
60
- OracleRubyJdbcConnection.createOracleJdbcConnectionClass(runtime, jdbcConnection);
61
- SQLite3RubyJdbcConnection.createSQLite3JdbcConnectionClass(runtime, jdbcConnection);
62
- H2RubyJdbcConnection.createH2JdbcConnectionClass(runtime, jdbcConnection);
63
- MySQLRubyJdbcConnection.createMySQLJdbcConnectionClass(runtime, jdbcConnection);
64
- DB2RubyJdbcConnection.createDB2JdbcConnectionClass(runtime, jdbcConnection);
65
- // ArJdbc :
66
- RubyModule arJdbc = runtime.getOrCreateModule("ArJdbc");
57
+ final RubyClass jdbcConnection = RubyJdbcConnection.createJdbcConnectionClass(runtime);
58
+ final RubyModule arJdbc = runtime.getOrCreateModule("ArJdbc");
59
+ // MySQL
67
60
  MySQLModule.load(arJdbc);
68
- MSSQLModule.load(arJdbc);
69
- DerbyModule.load(arJdbc);
70
- HSQLDBModule.load(arJdbc);
61
+ MySQLRubyJdbcConnection.createMySQLJdbcConnectionClass(runtime, jdbcConnection);
62
+ // PostgreSQL
63
+ PostgreSQLRubyJdbcConnection.createPostgreSQLJdbcConnectionClass(runtime, jdbcConnection);
64
+ // SQLite3
71
65
  SQLite3Module.load(arJdbc);
66
+ SQLite3RubyJdbcConnection.createSQLite3JdbcConnectionClass(runtime, jdbcConnection);
67
+ // Oracle
72
68
  OracleModule.load(arJdbc);
69
+ OracleRubyJdbcConnection.createOracleJdbcConnectionClass(runtime, jdbcConnection);
70
+ // MS-SQL
71
+ MSSQLModule.load(arJdbc);
72
+ MSSQLRubyJdbcConnection.createMSSQLJdbcConnectionClass(runtime, jdbcConnection);
73
+ // DB2
73
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);
74
85
  return true;
75
86
  }
76
87
 
@@ -0,0 +1,44 @@
1
+ /*
2
+ * The MIT License
3
+ *
4
+ * Copyright 2013 Karol Bucek.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ */
24
+ package arjdbc.jdbc;
25
+
26
+ import java.sql.Connection;
27
+ import java.sql.SQLException;
28
+
29
+ /**
30
+ * A JDBC connection callable ("block") of code.
31
+ *
32
+ * @author kares
33
+ */
34
+ public interface Callable<T> {
35
+
36
+ /**
37
+ * Perform an operation in the context of the given connection.
38
+ * @param connection
39
+ * @return
40
+ * @throws SQLException
41
+ */
42
+ T call(final Connection connection) throws SQLException;
43
+
44
+ }
@@ -25,12 +25,20 @@
25
25
  package arjdbc.jdbc;
26
26
 
27
27
  import java.sql.Connection;
28
+ import java.sql.SQLException;
28
29
 
29
30
  /**
30
- * Interface to be implemented in Ruby for retrieving a new connection
31
+ * Interface to be implemented in Ruby for retrieving a new connection.
31
32
  *
32
33
  * @author nicksieger
33
34
  */
34
35
  public interface JdbcConnectionFactory {
35
- Connection newConnection();
36
+
37
+ /**
38
+ * Retrieve a (new) connection from the factory.
39
+ * @return a connection
40
+ * @throws SQLException
41
+ */
42
+ Connection newConnection() throws SQLException;
43
+
36
44
  }
@@ -31,31 +31,35 @@ import java.io.InputStream;
31
31
  import java.io.Reader;
32
32
  import java.io.StringReader;
33
33
  import java.math.BigInteger;
34
+ import java.sql.Array;
34
35
  import java.sql.Connection;
35
36
  import java.sql.DatabaseMetaData;
36
37
  import java.sql.PreparedStatement;
37
38
  import java.sql.ResultSet;
38
39
  import java.sql.ResultSetMetaData;
39
40
  import java.sql.SQLException;
41
+ import java.sql.SQLXML;
40
42
  import java.sql.Statement;
43
+ import java.sql.Date;
44
+ import java.sql.Time;
41
45
  import java.sql.Timestamp;
42
46
  import java.sql.Types;
43
47
  import java.text.DateFormat;
44
48
  import java.text.SimpleDateFormat;
45
49
  import java.util.ArrayList;
46
50
  import java.util.Calendar;
47
- import java.util.Date;
48
51
  import java.util.List;
49
52
 
50
53
  import org.jruby.Ruby;
51
54
  import org.jruby.RubyArray;
52
55
  import org.jruby.RubyBignum;
56
+ import org.jruby.RubyBoolean;
53
57
  import org.jruby.RubyClass;
54
58
  import org.jruby.RubyHash;
59
+ import org.jruby.RubyInteger;
55
60
  import org.jruby.RubyModule;
56
61
  import org.jruby.RubyNumeric;
57
62
  import org.jruby.RubyObject;
58
- import org.jruby.RubyObjectAdapter;
59
63
  import org.jruby.RubyString;
60
64
  import org.jruby.RubySymbol;
61
65
  import org.jruby.RubyTime;
@@ -68,7 +72,6 @@ import org.jruby.runtime.Arity;
68
72
  import org.jruby.runtime.Block;
69
73
  import org.jruby.runtime.ObjectAllocator;
70
74
  import org.jruby.runtime.ThreadContext;
71
- import org.jruby.runtime.backtrace.RubyStackTraceElement;
72
75
  import org.jruby.runtime.builtin.IRubyObject;
73
76
  import org.jruby.util.ByteList;
74
77
 
@@ -76,10 +79,9 @@ import org.jruby.util.ByteList;
76
79
  * Part of our ActiveRecord::ConnectionAdapters::Connection impl.
77
80
  */
78
81
  public class RubyJdbcConnection extends RubyObject {
79
- private static final String[] TABLE_TYPE = new String[]{"TABLE"};
80
- private static final String[] TABLE_TYPES = new String[]{"TABLE", "VIEW", "SYNONYM"};
81
-
82
- private static RubyObjectAdapter rubyApi;
82
+
83
+ private static final String[] TABLE_TYPE = new String[] { "TABLE" };
84
+ private static final String[] TABLE_TYPES = new String[] { "TABLE", "VIEW", "SYNONYM" };
83
85
 
84
86
  protected RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
85
87
  super(runtime, metaClass);
@@ -95,81 +97,187 @@ public class RubyJdbcConnection extends RubyObject {
95
97
  RubyClass jdbcConnection = getConnectionAdapters(runtime).
96
98
  defineClassUnder("JdbcConnection", runtime.getObject(), JDBCCONNECTION_ALLOCATOR);
97
99
  jdbcConnection.defineAnnotatedMethods(RubyJdbcConnection.class);
98
-
99
- rubyApi = JavaEmbedUtils.newObjectAdapter();
100
-
101
100
  return jdbcConnection;
102
101
  }
103
102
 
104
- protected static RubyModule getConnectionAdapters(Ruby runtime) {
103
+ /**
104
+ * @param runtime
105
+ * @return <code>ActiveRecord::ConnectionAdapters</code>
106
+ */
107
+ protected static RubyModule getConnectionAdapters(final Ruby runtime) {
105
108
  return (RubyModule) runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters");
106
109
  }
110
+
111
+ /**
112
+ * @param runtime
113
+ * @return <code>ActiveRecord::Result</code>
114
+ */
115
+ static RubyClass getResult(final Ruby runtime) {
116
+ return runtime.getModule("ActiveRecord").getClass("Result");
117
+ }
107
118
 
108
- protected String[] getTableTypes() {
109
- return TABLE_TYPES;
119
+ /**
120
+ * @param runtime
121
+ * @return <code>ActiveRecord::ConnectionAdapters::IndexDefinition</code>
122
+ */
123
+ protected static RubyClass getIndexDefinition(final Ruby runtime) {
124
+ return getConnectionAdapters(runtime).getClass("IndexDefinition");
125
+ }
126
+
127
+ /**
128
+ * @param runtime
129
+ * @return <code>ActiveRecord::JDBCError</code>
130
+ */
131
+ protected static RubyClass getJDBCError(final Ruby runtime) {
132
+ return runtime.getModule("ActiveRecord").getClass("JDBCError");
110
133
  }
111
134
 
112
- @JRubyMethod(name = {"columns", "columns_internal"}, required = 1, optional = 2)
113
- public IRubyObject columns_internal(final ThreadContext context, final IRubyObject[] args)
114
- throws SQLException, IOException {
115
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
116
- public Object call(Connection c) throws SQLException {
117
- ResultSet results = null, pkeys = null;
118
- try {
119
- String defaultSchema = args.length > 2 ? toStringOrNull(args[2]) : null;
120
- String tableName = rubyApi.convertToRubyString(args[0]).getUnicodeValue();
121
- TableNameComponents components = extractTableNameComponents(c, defaultSchema, tableName);
122
-
123
- RubyArray matchingTables = (RubyArray) tableLookupBlock(context.getRuntime(),
124
- components.catalog, components.schema, components.table, getTableTypes(), false).call(c);
125
- if (matchingTables.isEmpty()) {
126
- throw new SQLException("Table " + tableName + " does not exist");
127
- }
135
+ /**
136
+ * @param runtime
137
+ * @return <code>ActiveRecord::ConnectionNotEstablished</code>
138
+ */
139
+ protected static RubyClass getConnectionNotEstablished(final Ruby runtime) {
140
+ return runtime.getModule("ActiveRecord").getClass("ConnectionNotEstablished");
141
+ }
142
+
143
+ /**
144
+ * NOTE: Only available since AR-4.0
145
+ * @param runtime
146
+ * @return <code>ActiveRecord::TransactionIsolationError</code>
147
+ */
148
+ protected static RubyClass getTransactionIsolationError(final Ruby runtime) {
149
+ return (RubyClass) runtime.getModule("ActiveRecord").getConstant("TransactionIsolationError");
150
+ }
151
+
152
+ /**
153
+ * @param runtime
154
+ * @return <code>ActiveRecord::ConnectionAdapters::JdbcTypeConverter</code>
155
+ */
156
+ private static RubyClass getJdbcTypeConverter(final Ruby runtime) {
157
+ return getConnectionAdapters(runtime).getClass("JdbcTypeConverter");
158
+ }
159
+
160
+ /*
161
+ def transaction_isolation_levels
162
+ {
163
+ read_uncommitted: "READ UNCOMMITTED",
164
+ read_committed: "READ COMMITTED",
165
+ repeatable_read: "REPEATABLE READ",
166
+ serializable: "SERIALIZABLE"
167
+ }
168
+ end
169
+ */
128
170
 
129
- DatabaseMetaData metadata = c.getMetaData();
130
- results = metadata.getColumns(components.catalog, components.schema, components.table, null);
131
- pkeys = metadata.getPrimaryKeys(components.catalog, components.schema, components.table);
132
- return unmarshal_columns(context, metadata, results, pkeys);
133
- } finally {
134
- close(results);
135
- close(pkeys);
171
+ public static int mapTransactionIsolationLevel(IRubyObject isolation) {
172
+ if ( ! ( isolation instanceof RubySymbol ) ) {
173
+ isolation = isolation.convertToString().callMethod("intern");
174
+ }
175
+
176
+ final Object isolationString = isolation.toString(); // RubySymbol.toString
177
+ if ( isolationString == "read_uncommitted" ) return Connection.TRANSACTION_READ_UNCOMMITTED; // 1
178
+ if ( isolationString == "read_committed" ) return Connection.TRANSACTION_READ_COMMITTED; // 2
179
+ if ( isolationString == "repeatable_read" ) return Connection.TRANSACTION_REPEATABLE_READ; // 4
180
+ if ( isolationString == "serializable" ) return Connection.TRANSACTION_SERIALIZABLE; // 8
181
+
182
+ throw new IllegalArgumentException(
183
+ "unexpected isolation level: " + isolation + " (" + isolationString + ")"
184
+ );
185
+ }
186
+
187
+ @JRubyMethod(name = "supports_transaction_isolation?", optional = 1)
188
+ public IRubyObject supports_transaction_isolation_p(final ThreadContext context,
189
+ final IRubyObject[] args) throws SQLException {
190
+ final IRubyObject isolation = args.length > 0 ? args[0] : null;
191
+
192
+ return withConnection(context, new Callable<IRubyObject>() {
193
+ public IRubyObject call(final Connection connection) throws SQLException {
194
+ final DatabaseMetaData metaData = connection.getMetaData();
195
+ final boolean supported;
196
+ if ( isolation != null && ! isolation.isNil() ) {
197
+ final int level = mapTransactionIsolationLevel(isolation);
198
+ supported = metaData.supportsTransactionIsolationLevel(level);
199
+ }
200
+ else {
201
+ final int level = metaData.getDefaultTransactionIsolation();
202
+ supported = level > Connection.TRANSACTION_NONE; // > 0
136
203
  }
204
+ return context.getRuntime().newBoolean(supported);
137
205
  }
138
206
  });
139
207
  }
140
-
141
- @JRubyMethod(name = "begin")
142
- public IRubyObject begin(ThreadContext context) throws SQLException {
143
- final Ruby runtime = context.getRuntime();
144
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
145
- public Object call(Connection c) throws SQLException {
146
- getConnection(true).setAutoCommit(false);
147
- return runtime.getNil();
148
- }
149
- });
208
+
209
+ @JRubyMethod(name = "begin", optional = 1) // optional isolation argument for AR-4.0
210
+ public IRubyObject begin(final ThreadContext context, final IRubyObject[] args) {
211
+ final IRubyObject isolation = args.length > 0 ? args[0] : null;
212
+ try { // handleException == false so we can handle setTXIsolation
213
+ return withConnection(context, false, new Callable<IRubyObject>() {
214
+ public IRubyObject call(final Connection connection) throws SQLException {
215
+ connection.setAutoCommit(false);
216
+ if ( isolation != null && ! isolation.isNil() ) {
217
+ final int level = mapTransactionIsolationLevel(isolation);
218
+ try {
219
+ connection.setTransactionIsolation(level);
220
+ }
221
+ catch (SQLException e) {
222
+ RubyClass txError = getTransactionIsolationError(context.getRuntime());
223
+ if ( txError != null ) throw wrapException(context, txError, e);
224
+ throw e; // let it roll - will be wrapped into a JDBCError (non 4.0)
225
+ }
226
+ }
227
+ return context.getRuntime().getNil();
228
+ }
229
+ });
230
+ }
231
+ catch (SQLException e) {
232
+ return handleException(context, e);
233
+ }
150
234
  }
151
235
 
152
236
  @JRubyMethod(name = "commit")
153
- public IRubyObject commit(ThreadContext context) throws SQLException {
154
- Connection connection = getConnection(true);
155
-
156
- if (!connection.getAutoCommit()) {
157
- try {
158
- connection.commit();
159
- } finally {
160
- connection.setAutoCommit(true);
237
+ public IRubyObject commit(final ThreadContext context) {
238
+ final Connection connection = getConnection(true);
239
+ try {
240
+ if ( ! connection.getAutoCommit() ) {
241
+ try {
242
+ connection.commit();
243
+ return context.getRuntime().newBoolean(true);
244
+ }
245
+ finally {
246
+ connection.setAutoCommit(true);
247
+ }
161
248
  }
249
+ return context.getRuntime().getNil();
250
+ }
251
+ catch (SQLException e) {
252
+ return handleException(context, e);
162
253
  }
163
-
164
- return context.getRuntime().getNil();
165
254
  }
166
255
 
256
+ @JRubyMethod(name = "rollback")
257
+ public IRubyObject rollback(final ThreadContext context) {
258
+ final Connection connection = getConnection(true);
259
+ try {
260
+ if ( ! connection.getAutoCommit() ) {
261
+ try {
262
+ connection.rollback();
263
+ return context.getRuntime().newBoolean(true);
264
+ } finally {
265
+ connection.setAutoCommit(true);
266
+ }
267
+ }
268
+ return context.getRuntime().getNil();
269
+ }
270
+ catch (SQLException e) {
271
+ return handleException(context, e);
272
+ }
273
+ }
274
+
167
275
  @JRubyMethod(name = "connection")
168
- public IRubyObject connection() {
276
+ public IRubyObject connection(final ThreadContext context) {
169
277
  if ( getConnection(false) == null ) {
170
278
  synchronized (this) {
171
279
  if ( getConnection(false) == null ) {
172
- reconnect();
280
+ reconnect(context);
173
281
  }
174
282
  }
175
283
  }
@@ -185,15 +293,22 @@ public class RubyJdbcConnection extends RubyObject {
185
293
  final Ruby runtime = context.getRuntime();
186
294
  List backtrace = (List) context.createCallerBacktrace(runtime, 0);
187
295
  runtime.getOut().println(this + " connection.disconnect! occured: ");
188
- for ( Object element : backtrace ) runtime.getOut().println(element);
296
+ for ( Object element : backtrace ) {
297
+ runtime.getOut().println(element);
298
+ }
189
299
  runtime.getOut().flush();
190
300
  }
191
301
  return setConnection(null);
192
302
  }
193
303
 
194
304
  @JRubyMethod(name = "reconnect!")
195
- public IRubyObject reconnect() {
196
- return setConnection( getConnectionFactory().newConnection() );
305
+ public IRubyObject reconnect(final ThreadContext context) {
306
+ try {
307
+ return setConnection( getConnectionFactory().newConnection() );
308
+ }
309
+ catch (SQLException e) {
310
+ return handleException(context, e);
311
+ }
197
312
  }
198
313
 
199
314
  @JRubyMethod(name = "database_name")
@@ -212,63 +327,77 @@ public class RubyJdbcConnection extends RubyObject {
212
327
 
213
328
  @JRubyMethod(name = "execute", required = 1)
214
329
  public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
215
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
216
- public Object call(Connection c) throws SQLException {
217
- Statement stmt = null;
218
- String query = rubyApi.convertToRubyString(sql).getUnicodeValue();
330
+ return withConnection(context, new Callable<IRubyObject>() {
331
+ public IRubyObject call(final Connection connection) throws SQLException {
332
+ Statement statement = null;
333
+ final String query = sql.convertToString().getUnicodeValue();
219
334
  try {
220
- stmt = c.createStatement();
221
- if (genericExecute(stmt, query)) {
222
- return unmarshalResults(context, c.getMetaData(), stmt, false);
335
+ statement = connection.createStatement();
336
+ if ( doExecute(statement, query) ) {
337
+ return unmarshalResults(context, connection.getMetaData(), statement, false);
223
338
  } else {
224
- return unmarshalKeysOrUpdateCount(context, c, stmt);
339
+ return unmarshalKeysOrUpdateCount(context, connection, statement);
225
340
  }
226
- } catch (SQLException sqe) {
227
- if (context.getRuntime().isDebug()) {
228
- System.out.println("Error SQL: " + query);
229
- }
230
- throw sqe;
231
- } finally {
232
- close(stmt);
233
341
  }
342
+ catch (final SQLException e) {
343
+ debugErrorSQL(context, query);
344
+ throw e;
345
+ }
346
+ finally { close(statement); }
234
347
  }
235
348
  });
236
349
  }
237
350
 
238
- protected boolean genericExecute(Statement stmt, String query) throws SQLException {
239
- return stmt.execute(query);
351
+ /**
352
+ * Execute a query using the given statement.
353
+ * @param statement
354
+ * @param query
355
+ * @return true if the first result is a <code>ResultSet</code>;
356
+ * false if it is an update count or there are no results
357
+ * @throws SQLException
358
+ */
359
+ protected boolean doExecute(final Statement statement, final String query) throws SQLException {
360
+ return genericExecute(statement, query);
240
361
  }
241
-
242
- protected IRubyObject unmarshalKeysOrUpdateCount(ThreadContext context, Connection c, Statement stmt) throws SQLException {
243
- IRubyObject key = context.getRuntime().getNil();
244
- if (c.getMetaData().supportsGetGeneratedKeys()) {
245
- key = unmarshal_id_result(context.getRuntime(), stmt.getGeneratedKeys());
362
+
363
+ /**
364
+ * @deprecated renamed to {@link #doExecute(Statement, String)}
365
+ */
366
+ @Deprecated
367
+ protected boolean genericExecute(final Statement statement, final String query) throws SQLException {
368
+ return statement.execute(query);
369
+ }
370
+
371
+ protected IRubyObject unmarshalKeysOrUpdateCount(final ThreadContext context,
372
+ final Connection connection, final Statement statement) throws SQLException {
373
+ final Ruby runtime = context.getRuntime();
374
+ final IRubyObject key;
375
+ if ( connection.getMetaData().supportsGetGeneratedKeys() ) {
376
+ key = unmarshalIdResult(runtime, statement);
246
377
  }
247
- if (key.isNil()) {
248
- return context.getRuntime().newFixnum(stmt.getUpdateCount());
249
- } else {
250
- return key;
378
+ else {
379
+ key = runtime.getNil();
251
380
  }
252
- }
381
+ return key.isNil() ? runtime.newFixnum( statement.getUpdateCount() ) : key;
382
+ }
253
383
 
254
384
  @JRubyMethod(name = "execute_id_insert", required = 2)
255
- public IRubyObject execute_id_insert(final ThreadContext context, final IRubyObject sql,
256
- final IRubyObject id) throws SQLException {
257
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
258
- public Object call(Connection c) throws SQLException {
259
- String insert = rubyApi.convertToRubyString(sql).getUnicodeValue();
260
- PreparedStatement ps = c.prepareStatement(insert);
385
+ public IRubyObject execute_id_insert(final ThreadContext context,
386
+ final IRubyObject sql, final IRubyObject id) throws SQLException {
387
+ return withConnection(context, new Callable<IRubyObject>() {
388
+ public IRubyObject call(final Connection connection) throws SQLException {
389
+ PreparedStatement statement = null;
390
+ final String insertSQL = sql.convertToString().getUnicodeValue();
261
391
  try {
262
- ps.setLong(1, RubyNumeric.fix2long(id));
263
- ps.executeUpdate();
264
- } catch (SQLException sqe) {
265
- if (context.getRuntime().isDebug()) {
266
- System.out.println("Error SQL: " + insert);
267
- }
268
- throw sqe;
269
- } finally {
270
- close(ps);
392
+ statement = connection.prepareStatement(insertSQL);
393
+ statement.setLong(1, RubyNumeric.fix2long(id));
394
+ statement.executeUpdate();
395
+ }
396
+ catch (final SQLException e) {
397
+ debugErrorSQL(context, insertSQL);
398
+ throw e;
271
399
  }
400
+ finally { close(statement); }
272
401
  return id;
273
402
  }
274
403
  });
@@ -276,191 +405,174 @@ public class RubyJdbcConnection extends RubyObject {
276
405
 
277
406
  @JRubyMethod(name = "execute_insert", required = 1)
278
407
  public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql)
279
- throws SQLException {
280
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
281
- public Object call(Connection c) throws SQLException {
282
- Statement stmt = null;
283
- String insert = rubyApi.convertToRubyString(sql).getUnicodeValue();
408
+ throws SQLException {
409
+ return withConnection(context, new Callable<IRubyObject>() {
410
+ public IRubyObject call(final Connection connection) throws SQLException {
411
+ Statement statement = null;
412
+ final String insertSQL = sql.convertToString().getUnicodeValue();
284
413
  try {
285
- stmt = c.createStatement();
286
- stmt.executeUpdate(insert, Statement.RETURN_GENERATED_KEYS);
287
- return unmarshal_id_result(context.getRuntime(), stmt.getGeneratedKeys());
288
- } catch (SQLException sqe) {
289
- if (context.getRuntime().isDebug()) {
290
- System.out.println("Error SQL: " + insert);
291
- }
292
- throw sqe;
293
- } finally {
294
- close(stmt);
414
+ statement = connection.createStatement();
415
+ statement.executeUpdate(insertSQL, Statement.RETURN_GENERATED_KEYS);
416
+ return unmarshalIdResult(context.getRuntime(), statement);
295
417
  }
418
+ catch (final SQLException e) {
419
+ debugErrorSQL(context, insertSQL);
420
+ throw e;
421
+ }
422
+ finally { close(statement); }
296
423
  }
297
424
  });
298
425
  }
299
426
 
300
- @JRubyMethod(name = "execute_query", required = 1)
301
- public IRubyObject execute_query(final ThreadContext context, IRubyObject _sql)
302
- throws SQLException, IOException {
303
- String sql = rubyApi.convertToRubyString(_sql).getUnicodeValue();
304
-
305
- return executeQuery(context, sql, 0);
427
+ /**
428
+ * NOTE: since 1.3 this behaves like <code>execute_query</code> in AR-JDBC 1.2
429
+ * @param context
430
+ * @param sql
431
+ * @param block
432
+ * @return raw query result in case no block given
433
+ * @throws SQLException
434
+ */
435
+ @JRubyMethod(name = "execute_query_raw", required = 1) // optional block
436
+ public IRubyObject execute_query_raw(final ThreadContext context,
437
+ final IRubyObject sql, final Block block) throws SQLException {
438
+ final String query = sql.convertToString().getUnicodeValue();
439
+ return executeQueryRaw(context, query, 0, block);
306
440
  }
307
441
 
308
- @JRubyMethod(name = "execute_query", required = 2)
309
- public IRubyObject execute_query(final ThreadContext context, IRubyObject _sql,
310
- IRubyObject _maxRows) throws SQLException, IOException {
311
- String sql = rubyApi.convertToRubyString(_sql).getUnicodeValue();
312
- int maxrows = RubyNumeric.fix2int(_maxRows);
313
-
314
- return executeQuery(context, sql, maxrows);
442
+ /**
443
+ * NOTE: since 1.3 this behaves like <code>execute_query</code> in AR-JDBC 1.2
444
+ * @param context
445
+ * @param sql
446
+ * @param maxRows
447
+ * @param block
448
+ * @return raw query result in case no block given
449
+ * @throws SQLException
450
+ */
451
+ @JRubyMethod(name = "execute_query_raw", required = 2)
452
+ 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);
315
457
  }
316
458
 
317
- protected IRubyObject executeQuery(final ThreadContext context, final String query, final int maxRows) {
318
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
319
- public Object call(Connection c) throws SQLException {
320
- Statement stmt = null;
459
+ /**
460
+ * @param context
461
+ * @param query
462
+ * @param maxRows
463
+ * @param block
464
+ * @return raw query result (in case no block was given)
465
+ *
466
+ * @see #execute_raw_query(ThreadContext, IRubyObject, Block)
467
+ * @see #execute_raw_query(ThreadContext, IRubyObject, IRubyObject, Block)
468
+ */
469
+ protected IRubyObject executeQueryRaw(final ThreadContext context, final String query, final int maxRows,
470
+ final Block block) { // TODO implement block support
471
+ return withConnection(context, new Callable<IRubyObject>() {
472
+ public IRubyObject call(final Connection connection) throws SQLException {
473
+ final Ruby runtime = context.getRuntime();
474
+ Statement statement = null; ResultSet resultSet = null;
321
475
  try {
322
- DatabaseMetaData metadata = c.getMetaData();
323
- stmt = c.createStatement();
324
- stmt.setMaxRows(maxRows);
325
- return unmarshalResult(context, metadata, stmt.executeQuery(query), false);
326
- } catch (SQLException sqe) {
327
- if (context.getRuntime().isDebug()) {
328
- System.out.println("Error SQL: " + query);
329
- }
330
- throw sqe;
331
- } finally {
332
- close(stmt);
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);
480
+ return mapToRawResult(context, runtime, metaData, resultSet, false);
333
481
  }
334
- }
335
- });
336
- }
337
-
338
- @JRubyMethod(name = "execute_update", required = 1)
339
- public IRubyObject execute_update(final ThreadContext context, final IRubyObject sql)
340
- throws SQLException {
341
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
342
- public Object call(Connection c) throws SQLException {
343
- Statement stmt = null;
344
- String update = rubyApi.convertToRubyString(sql).getUnicodeValue();
345
- try {
346
- stmt = c.createStatement();
347
- return context.getRuntime().newFixnum((long)stmt.executeUpdate(update));
348
- } catch (SQLException sqe) {
349
- if (context.getRuntime().isDebug()) {
350
- System.out.println("Error SQL: " + update);
351
- }
352
- throw sqe;
353
- } finally {
354
- close(stmt);
482
+ catch (final SQLException e) {
483
+ debugErrorSQL(context, query);
484
+ throw e;
355
485
  }
486
+ finally { close(resultSet); close(statement); }
356
487
  }
357
488
  });
358
489
  }
359
490
 
360
- @JRubyMethod(name = "indexes")
361
- public IRubyObject indexes(ThreadContext context, IRubyObject tableName, IRubyObject name, IRubyObject schemaName) {
362
- return indexes(context, toStringOrNull(tableName), toStringOrNull(name), toStringOrNull(schemaName));
491
+ /**
492
+ * Executes a query and returns the (AR) result.
493
+ * @param context
494
+ * @param sql
495
+ * @return
496
+ * @throws SQLException
497
+ *
498
+ * @see #execute_raw_query(ThreadContext, IRubyObject, Block)
499
+ */
500
+ @JRubyMethod(name = "execute_query", required = 1)
501
+ public IRubyObject execute_query(final ThreadContext context, final IRubyObject sql)
502
+ throws SQLException {
503
+ final String query = sql.convertToString().getUnicodeValue();
504
+ return executeQuery(context, query, 0);
363
505
  }
364
506
 
365
- private static final int INDEX_TABLE_NAME = 3;
366
- private static final int INDEX_NON_UNIQUE = 4;
367
- private static final int INDEX_NAME = 6;
368
- private static final int INDEX_COLUMN_NAME = 9;
369
-
370
507
  /**
371
- * Default JDBC introspection for index metadata on the JdbcConnection.
372
- *
373
- * JDBC index metadata is denormalized (multiple rows may be returned for
374
- * one index, one row per column in the index), so a simple block-based
375
- * filter like that used for tables doesn't really work here. Callers
376
- * should filter the return from this method instead.
508
+ * Executes a query and returns the (AR) result.
509
+ * @param context
510
+ * @param sql
511
+ * @param maxRows
512
+ * @return
513
+ * @throws SQLException
514
+ *
515
+ * @see #execute_raw_query(ThreadContext, IRubyObject, IRubyObject, Block)
377
516
  */
378
- protected IRubyObject indexes(final ThreadContext context, final String tableNameArg, final String name, final String schemaNameArg) {
379
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
380
- public Object call(Connection c) throws SQLException {
381
- Ruby runtime = context.getRuntime();
382
- DatabaseMetaData metadata = c.getMetaData();
383
- String tableName = caseConvertIdentifierForJdbc(metadata, tableNameArg);
384
- String schemaName = caseConvertIdentifierForJdbc(metadata, schemaNameArg);
385
-
386
- ResultSet resultSet = null;
387
- List indexes = new ArrayList();
517
+ @JRubyMethod(name = "execute_query", required = 2)
518
+ 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));
522
+ }
523
+
524
+ /**
525
+ * NOTE: This methods behavior changed in AR-JDBC 1.3 the old behavior is
526
+ * achievable using {@link #executeQueryRaw(ThreadContext, String, int, Block)}.
527
+ *
528
+ * @param context
529
+ * @param query
530
+ * @param maxRows
531
+ * @return AR (mapped) query result
532
+ *
533
+ * @see #execute_query(ThreadContext, IRubyObject)
534
+ * @see #execute_query(ThreadContext, IRubyObject, IRubyObject)
535
+ * @see #mapToResult(ThreadContext, Ruby, DatabaseMetaData, ResultSet, RubyJdbcConnection.ColumnData[])
536
+ */
537
+ protected IRubyObject executeQuery(final ThreadContext context, final String query, final int maxRows) {
538
+ return withConnection(context, new Callable<IRubyObject>() {
539
+ public IRubyObject call(final Connection connection) throws SQLException {
540
+ final Ruby runtime = context.getRuntime();
541
+ Statement statement = null; ResultSet resultSet = null;
388
542
  try {
389
- resultSet = metadata.getIndexInfo(null, schemaName, tableName, false, true);
390
- List primaryKeys = primaryKeys(context, tableName);
391
- String currentIndex = null;
392
- RubyModule indexDefinitionClass = getConnectionAdapters(runtime).getClass("IndexDefinition");
393
-
394
- while (resultSet.next()) {
395
- String indexName = resultSet.getString(INDEX_NAME);
396
-
397
- if (indexName == null) continue;
398
-
399
- indexName = caseConvertIdentifierForRails(metadata, indexName);
400
-
401
- RubyString columnName = RubyString.newUnicodeString(runtime, caseConvertIdentifierForRails(metadata, resultSet.getString(INDEX_COLUMN_NAME)));
402
-
403
- if (primaryKeys.contains(columnName)) continue;
404
-
405
- // We are working on a new index
406
- if (!indexName.equals(currentIndex)) {
407
- currentIndex = indexName;
408
-
409
- tableName = caseConvertIdentifierForRails(metadata, resultSet.getString(INDEX_TABLE_NAME));
410
- boolean nonUnique = resultSet.getBoolean(INDEX_NON_UNIQUE);
411
-
412
- IRubyObject indexDefinition = indexDefinitionClass.callMethod(context, "new",
413
- new IRubyObject[] {
414
- RubyString.newUnicodeString(runtime, tableName),
415
- RubyString.newUnicodeString(runtime, indexName),
416
- runtime.newBoolean(!nonUnique),
417
- runtime.newArray()
418
- });
419
-
420
- // empty list for column names, we'll add to that in just a bit
421
- indexes.add(indexDefinition);
422
- }
423
-
424
- // One or more columns can be associated with an index
425
- IRubyObject lastIndex = (IRubyObject) indexes.get(indexes.size() - 1);
426
-
427
- if (lastIndex != null) {
428
- lastIndex.callMethod(context, "columns").callMethod(context, "<<", columnName);
429
- }
430
- }
431
-
432
- return runtime.newArray(indexes);
433
- } finally {
434
- close(resultSet);
543
+ final DatabaseMetaData metaData = connection.getMetaData();
544
+ statement = connection.createStatement();
545
+ statement.setMaxRows(maxRows); // zero means there is no limit
546
+ resultSet = statement.executeQuery(query);
547
+ final ColumnData[] columns = setupColumns(runtime, metaData, resultSet.getMetaData(), false);
548
+ return mapToResult(context, runtime, metaData, resultSet, columns);
549
+ }
550
+ catch (final SQLException e) {
551
+ debugErrorSQL(context, query);
552
+ throw e;
435
553
  }
554
+ finally { close(resultSet); close(statement); }
436
555
  }
437
556
  });
438
557
  }
439
-
440
- @JRubyMethod(name = "insert?", required = 1, meta = true, frame = false)
441
- public static IRubyObject insert_p(ThreadContext context, IRubyObject recv, IRubyObject _sql) {
442
- ByteList sql = rubyApi.convertToRubyString(_sql).getByteList();
443
-
444
- return context.getRuntime().newBoolean(startsWithNoCaseCmp(sql, INSERT));
445
- }
446
-
447
- /*
448
- * sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
449
- */
450
- @JRubyMethod(name = "insert_bind", required = 3, rest = true)
451
- public IRubyObject insert_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
452
- final Ruby runtime = context.getRuntime();
453
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
454
- public Object call(Connection c) throws SQLException {
455
- PreparedStatement ps = null;
558
+
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();
456
566
  try {
457
- ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString(), Statement.RETURN_GENERATED_KEYS);
458
- setValuesOnPS(ps, context, args[1], args[2]);
459
- ps.executeUpdate();
460
- return unmarshal_id_result(runtime, ps.getGeneratedKeys());
461
- } finally {
462
- close(ps);
567
+ statement = connection.createStatement();
568
+ final int rowCount = statement.executeUpdate(updateSQL);
569
+ return context.getRuntime().newFixnum(rowCount);
463
570
  }
571
+ catch (final SQLException e) {
572
+ debugErrorSQL(context, updateSQL);
573
+ throw e;
574
+ }
575
+ finally { close(statement); }
464
576
  }
465
577
  });
466
578
  }
@@ -473,71 +585,49 @@ public class RubyJdbcConnection extends RubyObject {
473
585
 
474
586
  @JRubyMethod(name = "primary_keys", required = 1)
475
587
  public IRubyObject primary_keys(ThreadContext context, IRubyObject tableName) throws SQLException {
476
- return context.getRuntime().newArray(primaryKeys(context, tableName.toString()));
588
+ @SuppressWarnings("unchecked")
589
+ List<IRubyObject> primaryKeys = (List) primaryKeys(context, tableName.toString());
590
+ return context.getRuntime().newArray(primaryKeys);
477
591
  }
478
592
 
479
- protected List primaryKeys(final ThreadContext context, final String tableNameArg) {
480
- return (List) withConnectionAndRetry(context, new SQLBlock() {
481
- public Object call(Connection c) throws SQLException {
482
- Ruby runtime = context.getRuntime();
483
- DatabaseMetaData metadata = c.getMetaData();
484
- String tableName = caseConvertIdentifierForJdbc(metadata, tableNameArg);
593
+ private static final int PRIMARY_KEYS_COLUMN_NAME = 4;
594
+
595
+ protected List<RubyString> primaryKeys(final ThreadContext context, final String tableName) {
596
+ return withConnection(context, new Callable<List<RubyString>>() {
597
+ public List<RubyString> call(final Connection connection) throws SQLException {
598
+ final Ruby runtime = context.getRuntime();
599
+ final DatabaseMetaData metaData = connection.getMetaData();
600
+ final String _tableName = caseConvertIdentifierForJdbc(metaData, tableName);
485
601
  ResultSet resultSet = null;
486
- List keyNames = new ArrayList();
602
+ final List<RubyString> keyNames = new ArrayList<RubyString>();
487
603
  try {
488
- TableNameComponents components = extractTableNameComponents(c, null, tableName);
489
- resultSet = metadata.getPrimaryKeys(components.catalog, components.schema, components.table);
604
+ TableName components = extractTableName(connection, null, _tableName);
605
+ resultSet = metaData.getPrimaryKeys(components.catalog, components.schema, components.name);
490
606
 
491
607
  while (resultSet.next()) {
492
- keyNames.add(RubyString.newUnicodeString(runtime,
493
- caseConvertIdentifierForRails(metadata, resultSet.getString(4))));
608
+ String columnName = resultSet.getString(PRIMARY_KEYS_COLUMN_NAME);
609
+ columnName = caseConvertIdentifierForRails(metaData, columnName);
610
+ keyNames.add( RubyString.newUnicodeString(runtime, columnName) );
494
611
  }
495
- } finally {
496
- close(resultSet);
497
612
  }
498
-
613
+ finally { close(resultSet); }
499
614
  return keyNames;
500
615
  }
501
616
  });
502
617
  }
503
-
504
-
505
- @JRubyMethod(name = "rollback")
506
- public IRubyObject rollback(ThreadContext context) throws SQLException {
507
- final Ruby runtime = context.getRuntime();
508
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
509
- public Object call(Connection c) throws SQLException {
510
- Connection connection = getConnection(true);
511
-
512
- if (!connection.getAutoCommit()) {
513
- try {
514
- connection.rollback();
515
- } finally {
516
- connection.setAutoCommit(true);
517
- }
518
- }
519
-
520
- return runtime.getNil();
521
- }
522
- });
523
- }
524
-
525
- @JRubyMethod(name = "select?", required = 1, meta = true, frame = false)
526
- public static IRubyObject select_p(ThreadContext context, IRubyObject recv, IRubyObject _sql) {
527
- ByteList sql = rubyApi.convertToRubyString(_sql).getByteList();
528
-
529
- return context.getRuntime().newBoolean(startsWithNoCaseCmp(sql, SELECT) || startsWithNoCaseCmp(sql, WITH) ||
530
- startsWithNoCaseCmp(sql, SHOW) || startsWithNoCaseCmp(sql, CALL));
531
- }
532
-
618
+
533
619
  @JRubyMethod(name = "set_native_database_types")
534
- public IRubyObject set_native_database_types(ThreadContext context) throws SQLException, IOException {
535
- Ruby runtime = context.getRuntime();
536
- DatabaseMetaData metadata = getConnection(true).getMetaData();
537
- IRubyObject types = unmarshalResult(context, metadata, metadata.getTypeInfo(), true);
538
- IRubyObject typeConverter = getConnectionAdapters(runtime).getConstant("JdbcTypeConverter");
539
- IRubyObject value = rubyApi.callMethod(rubyApi.callMethod(typeConverter, "new", types), "choose_best_types");
540
- setInstanceVariable("@native_types", value);
620
+ public IRubyObject set_native_database_types(final ThreadContext context) throws SQLException {
621
+ final Ruby runtime = context.getRuntime();
622
+ final DatabaseMetaData metaData = getConnection(true).getMetaData();
623
+ final IRubyObject types; final ResultSet typeDesc = metaData.getTypeInfo();
624
+ try {
625
+ types = mapToRawResult(context, runtime, metaData, typeDesc, true);
626
+ }
627
+ finally { close(typeDesc); }
628
+
629
+ final IRubyObject typeConverter = getJdbcTypeConverter(runtime).callMethod("new", types);
630
+ setInstanceVariable("@native_types", typeConverter.callMethod(context, "choose_best_types"));
541
631
 
542
632
  return runtime.getNil();
543
633
  }
@@ -567,90 +657,267 @@ public class RubyJdbcConnection extends RubyObject {
567
657
  return tables(context, toStringOrNull(args[0]), toStringOrNull(args[1]), toStringOrNull(args[2]), getTypes(args[3]));
568
658
  }
569
659
 
570
- protected IRubyObject tables(ThreadContext context, String catalog, String schemaPattern, String tablePattern, String[] types) {
571
- return (IRubyObject) withConnectionAndRetry(context, tableLookupBlock(context.getRuntime(), catalog, schemaPattern, tablePattern, types, false));
572
- }
573
-
574
- /*
575
- * sql, values, types, name = nil
576
- */
577
- @JRubyMethod(name = "update_bind", required = 3, rest = true)
578
- public IRubyObject update_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
579
- final Ruby runtime = context.getRuntime();
580
- Arity.checkArgumentCount(runtime, args, 3, 4);
581
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
582
- public Object call(Connection c) throws SQLException {
583
- PreparedStatement ps = null;
584
- try {
585
- ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString());
586
- setValuesOnPS(ps, context, args[1], args[2]);
587
- ps.executeUpdate();
588
- } finally {
589
- close(ps);
590
- }
591
- return runtime.getNil();
660
+ protected IRubyObject tables(final ThreadContext context,
661
+ final String catalog, final String schemaPattern, final String tablePattern, final String[] types) {
662
+ return withConnection(context, new Callable<IRubyObject>() {
663
+ public IRubyObject call(final Connection connection) throws SQLException {
664
+ return matchTables(context.getRuntime(), connection, catalog, schemaPattern, tablePattern, types, false);
592
665
  }
593
666
  });
594
667
  }
595
668
 
596
- @JRubyMethod(name = "with_connection_retry_guard", frame = true)
597
- public IRubyObject with_connection_retry_guard(final ThreadContext context, final Block block) {
598
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
599
- public Object call(Connection c) throws SQLException {
600
- return block.call(context, new IRubyObject[] { wrappedConnection(c) });
601
- }
602
- });
669
+ protected String[] getTableTypes() {
670
+ return TABLE_TYPES;
603
671
  }
604
672
 
605
- /*
606
- * (is binary?, colname, tablename, primary key, id, value)
607
- */
608
- @JRubyMethod(name = "write_large_object", required = 6)
609
- public IRubyObject write_large_object(ThreadContext context, final IRubyObject[] args)
610
- throws SQLException, IOException {
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");
678
+ }
679
+ final String tableName = ((RubyString) name).getUnicodeValue();
680
+ final String tableSchema = schema_name == null ? null : schema_name.convertToString().getUnicodeValue();
611
681
  final Ruby runtime = context.getRuntime();
612
- return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
613
- public Object call(Connection c) throws SQLException {
614
- String sql = "UPDATE " + rubyApi.convertToRubyString(args[2])
615
- + " SET " + rubyApi.convertToRubyString(args[1])
616
- + " = ? WHERE " + rubyApi.convertToRubyString(args[3])
617
- + "=" + rubyApi.convertToRubyString(args[4]);
618
- PreparedStatement ps = null;
682
+
683
+ return withConnection(context, new Callable<RubyBoolean>() {
684
+ public RubyBoolean call(final Connection connection) throws SQLException {
685
+ final TableName components = extractTableName(connection, tableSchema, tableName);
686
+ return runtime.newBoolean( tableExists(runtime, connection, components) );
687
+ }
688
+ });
689
+ }
690
+
691
+ @JRubyMethod(name = {"columns", "columns_internal"}, required = 1, optional = 2)
692
+ public IRubyObject columns_internal(final ThreadContext context, final IRubyObject[] args)
693
+ throws SQLException {
694
+ return withConnection(context, new Callable<IRubyObject>() {
695
+ public IRubyObject call(final Connection connection) throws SQLException {
696
+ ResultSet columns = null, primaryKeys = null;
619
697
  try {
620
- ps = c.prepareStatement(sql);
621
- if (args[0].isTrue()) { // binary
622
- ByteList outp = rubyApi.convertToRubyString(args[5]).getByteList();
623
- ps.setBinaryStream(1, new ByteArrayInputStream(outp.bytes,
624
- outp.begin, outp.realSize), outp.realSize);
625
- } else { // clob
626
- String ss = rubyApi.convertToRubyString(args[5]).getUnicodeValue();
627
- ps.setCharacterStream(1, new StringReader(ss), ss.length());
698
+ final String tableName = args[0].convertToString().getUnicodeValue();
699
+ // optionals (NOTE: catalog argumnet was never used before 1.3.0) :
700
+ final String catalog = args.length > 1 ? toStringOrNull(args[1]) : null;
701
+ final String defaultSchema = args.length > 2 ? toStringOrNull(args[2]) : null;
702
+
703
+ final TableName components;
704
+ if ( catalog == null ) { // backwards-compatibility with < 1.3.0
705
+ components = extractTableName(connection, defaultSchema, tableName);
628
706
  }
629
- ps.executeUpdate();
630
- } finally {
631
- close(ps);
707
+ else {
708
+ components = extractTableName(connection, catalog, defaultSchema, tableName);
709
+ }
710
+
711
+ if ( ! tableExists(context.getRuntime(), connection, components) ) {
712
+ throw new SQLException("table: " + tableName + " does not exist");
713
+ }
714
+
715
+ final DatabaseMetaData metaData = connection.getMetaData();
716
+ columns = metaData.getColumns(components.catalog, components.schema, components.name, null);
717
+ primaryKeys = metaData.getPrimaryKeys(components.catalog, components.schema, components.name);
718
+ return unmarshalColumns(context, metaData, columns, primaryKeys);
719
+ }
720
+ finally {
721
+ close(columns);
722
+ close(primaryKeys);
632
723
  }
633
- return runtime.getNil();
634
724
  }
635
725
  });
636
726
  }
727
+
728
+ @JRubyMethod(name = "indexes")
729
+ public IRubyObject indexes(ThreadContext context, IRubyObject tableName, IRubyObject name, IRubyObject schemaName) {
730
+ return indexes(context, toStringOrNull(tableName), toStringOrNull(name), toStringOrNull(schemaName));
731
+ }
732
+
733
+ // NOTE: metaData.getIndexInfo row mappings :
734
+ private static final int INDEX_INFO_TABLE_NAME = 3;
735
+ private static final int INDEX_INFO_NON_UNIQUE = 4;
736
+ private static final int INDEX_INFO_NAME = 6;
737
+ private static final int INDEX_INFO_COLUMN_NAME = 9;
637
738
 
638
739
  /**
639
- * Convert an identifier coming back from the database to a case which Rails is expecting.
740
+ * Default JDBC introspection for index metadata on the JdbcConnection.
640
741
  *
641
- * Assumption: Rails identifiers will be quoted for mixed or will stay mixed
642
- * as identifier names in Rails itself. Otherwise, they expect identifiers to
643
- * be lower-case. Databases which store identifiers uppercase should be made
644
- * lower-case.
742
+ * JDBC index metadata is denormalized (multiple rows may be returned for
743
+ * one index, one row per column in the index), so a simple block-based
744
+ * filter like that used for tables doesn't really work here. Callers
745
+ * should filter the return from this method instead.
746
+ */
747
+ protected IRubyObject indexes(final ThreadContext context, final String tableName, final String name, final String schemaName) {
748
+ return withConnection(context, new Callable<IRubyObject>() {
749
+ public IRubyObject call(final Connection connection) throws SQLException {
750
+ final Ruby runtime = context.getRuntime();
751
+ final RubyClass indexDefinition = getIndexDefinition(runtime);
752
+
753
+ final DatabaseMetaData metaData = connection.getMetaData();
754
+ String _tableName = caseConvertIdentifierForJdbc(metaData, tableName);
755
+ String _schemaName = caseConvertIdentifierForJdbc(metaData, schemaName);
756
+
757
+ final List<RubyString> primaryKeys = primaryKeys(context, _tableName);
758
+ ResultSet indexInfoSet = null;
759
+ final List<IRubyObject> indexes = new ArrayList<IRubyObject>();
760
+ try {
761
+ indexInfoSet = metaData.getIndexInfo(null, _schemaName, _tableName, false, true);
762
+ String currentIndex = null;
763
+
764
+ while ( indexInfoSet.next() ) {
765
+ String indexName = indexInfoSet.getString(INDEX_INFO_NAME);
766
+ if ( indexName == null ) continue;
767
+ indexName = caseConvertIdentifierForRails(metaData, indexName);
768
+
769
+ final String columnName = indexInfoSet.getString(INDEX_INFO_COLUMN_NAME);
770
+ final RubyString rubyColumnName = RubyString.newUnicodeString(
771
+ runtime, caseConvertIdentifierForRails(metaData, columnName)
772
+ );
773
+ if ( primaryKeys.contains(rubyColumnName) ) continue;
774
+
775
+ // We are working on a new index
776
+ if ( ! indexName.equals(currentIndex) ) {
777
+ currentIndex = indexName;
778
+
779
+ String indexTableName = indexInfoSet.getString(INDEX_INFO_TABLE_NAME);
780
+ indexTableName = caseConvertIdentifierForRails(metaData, indexTableName);
781
+
782
+ final boolean nonUnique = indexInfoSet.getBoolean(INDEX_INFO_NON_UNIQUE);
783
+
784
+ IRubyObject[] args = new IRubyObject[] {
785
+ RubyString.newUnicodeString(runtime, indexTableName), // table_name
786
+ RubyString.newUnicodeString(runtime, indexName), // index_name
787
+ runtime.newBoolean( ! nonUnique ), // unique
788
+ runtime.newArray() // [] for column names, we'll add to that in just a bit
789
+ // orders, (since AR 3.2) where, type, using (AR 4.0)
790
+ };
791
+
792
+ indexes.add( indexDefinition.callMethod(context, "new", args) ); // IndexDefinition.new
793
+ }
794
+
795
+ // One or more columns can be associated with an index
796
+ IRubyObject lastIndexDef = indexes.isEmpty() ? null : indexes.get(indexes.size() - 1);
797
+ if (lastIndexDef != null) {
798
+ lastIndexDef.callMethod(context, "columns").callMethod(context, "<<", rubyColumnName);
799
+ }
800
+ }
801
+
802
+ return runtime.newArray(indexes);
803
+
804
+ } finally { close(indexInfoSet); }
805
+ }
806
+ });
807
+ }
808
+
809
+ // NOTE: this seems to be not used ... at all ?!
810
+ /*
811
+ * sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
812
+ */
813
+ @Deprecated
814
+ @JRubyMethod(name = "insert_bind", required = 3, rest = true)
815
+ public IRubyObject insert_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
816
+ final Ruby runtime = context.getRuntime();
817
+ return withConnection(context, new Callable<IRubyObject>() {
818
+ public IRubyObject call(final Connection connection) throws SQLException {
819
+ final String sql = args[0].convertToString().toString();
820
+ PreparedStatement statement = null;
821
+ try {
822
+ statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
823
+ setValues(context, args[1], args[2], statement);
824
+ statement.executeUpdate();
825
+ return unmarshalIdResult(runtime, statement);
826
+ }
827
+ finally { close(statement); }
828
+ }
829
+ });
830
+ }
831
+
832
+ // NOTE: this seems to be not used ... at all ?!
833
+ /*
834
+ * sql, values, types, name = nil
835
+ */
836
+ @Deprecated
837
+ @JRubyMethod(name = "update_bind", required = 3, rest = true)
838
+ public IRubyObject update_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
839
+ final Ruby runtime = context.getRuntime();
840
+ Arity.checkArgumentCount(runtime, args, 3, 4);
841
+ return withConnection(context, new Callable<IRubyObject>() {
842
+ public IRubyObject call(final Connection connection) throws SQLException {
843
+ final String sql = args[0].convertToString().toString();
844
+ PreparedStatement statement = null;
845
+ try {
846
+ statement = connection.prepareStatement(sql);
847
+ setValues(context, args[1], args[2], statement);
848
+ statement.executeUpdate();
849
+ }
850
+ finally { close(statement); }
851
+ return runtime.getNil();
852
+ }
853
+ });
854
+ }
855
+
856
+ @JRubyMethod(name = "with_connection_retry_guard", frame = true)
857
+ public IRubyObject with_connection_retry_guard(final ThreadContext context, final Block block) {
858
+ return withConnection(context, new Callable<IRubyObject>() {
859
+ public IRubyObject call(final Connection connection) throws SQLException {
860
+ return block.call(context, new IRubyObject[] { convertJavaToRuby(connection) });
861
+ }
862
+ });
863
+ }
864
+
865
+ /*
866
+ * (is binary?, colname, tablename, primary_key, id, lob_value)
867
+ */
868
+ @JRubyMethod(name = "write_large_object", required = 6)
869
+ public IRubyObject write_large_object(final ThreadContext context, final IRubyObject[] args)
870
+ throws SQLException {
871
+
872
+ 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();
877
+ final IRubyObject lobValue = args[5];
878
+
879
+ final Ruby runtime = context.getRuntime();
880
+ return withConnection(context, new Callable<IRubyObject>() {
881
+ public IRubyObject call(final Connection connection) throws SQLException {
882
+ final String sql = "UPDATE "+ tableName +
883
+ " SET "+ columnName +" = ? WHERE "+ idKey +" = "+ idVal;
884
+ PreparedStatement statement = null;
885
+ try {
886
+ statement = connection.prepareStatement(sql);
887
+ if ( isBinary ) { // binary
888
+ final ByteList blob = lobValue.convertToString().getByteList();
889
+ final int realSize = blob.getRealSize();
890
+ statement.setBinaryStream(1,
891
+ new ByteArrayInputStream(blob.unsafeBytes(), blob.getBegin(), realSize), realSize
892
+ );
893
+ } else { // clob
894
+ String clob = lobValue.convertToString().getUnicodeValue();
895
+ statement.setCharacterStream(1, new StringReader(clob), clob.length());
896
+ }
897
+ statement.executeUpdate();
898
+ }
899
+ finally { close(statement); }
900
+ return runtime.getNil();
901
+ }
902
+ });
903
+ }
904
+
905
+ /**
906
+ * Convert an identifier coming back from the database to a case which Rails is expecting.
907
+ *
908
+ * Assumption: Rails identifiers will be quoted for mixed or will stay mixed
909
+ * as identifier names in Rails itself. Otherwise, they expect identifiers to
910
+ * be lower-case. Databases which store identifiers uppercase should be made
911
+ * lower-case.
645
912
  *
646
913
  * Assumption 2: It is always safe to convert all upper case names since it appears that
647
914
  * some adapters do not report StoresUpper/Lower/Mixed correctly (am I right postgres/mysql?).
648
915
  */
649
- public static String caseConvertIdentifierForRails(DatabaseMetaData metadata, String value)
650
- throws SQLException {
651
- if (value == null) return null;
652
-
653
- return metadata.storesUpperCaseIdentifiers() ? value.toLowerCase() : value;
916
+ protected static String caseConvertIdentifierForRails(final DatabaseMetaData metaData, final String value)
917
+ throws SQLException {
918
+ if ( value == null ) return null;
919
+
920
+ return metaData.storesUpperCaseIdentifiers() ? value.toLowerCase() : value;
654
921
  }
655
922
 
656
923
  /**
@@ -658,61 +925,37 @@ public class RubyJdbcConnection extends RubyObject {
658
925
  * storage case. Methods like DatabaseMetaData.getPrimaryKeys() needs the table name to match
659
926
  * the internal storage name. Arbtrary queries and the like DO NOT need to do this.
660
927
  */
661
- public static String caseConvertIdentifierForJdbc(DatabaseMetaData metadata, String value)
662
- throws SQLException {
663
- if (value == null) return null;
664
- boolean isPostgres = metadata.getDatabaseProductName().equals("PostgreSQL");
665
-
666
- if (metadata.storesUpperCaseIdentifiers()) {
928
+ protected String caseConvertIdentifierForJdbc(final DatabaseMetaData metaData, final String value)
929
+ throws SQLException {
930
+ if ( value == null ) return null;
931
+
932
+ if ( metaData.storesUpperCaseIdentifiers() ) {
667
933
  return value.toUpperCase();
668
- } else if (metadata.storesLowerCaseIdentifiers() && ! isPostgres) {
934
+ }
935
+ else if ( metaData.storesLowerCaseIdentifiers() ) {
669
936
  return value.toLowerCase();
670
937
  }
671
938
 
672
939
  return value;
673
940
  }
674
941
 
675
- // helpers
676
- protected static void close(Connection connection) {
677
- if (connection != null) {
678
- try {
679
- connection.close();
680
- } catch(Exception e) {}
681
- }
682
- }
683
-
684
- public static void close(ResultSet resultSet) {
685
- if (resultSet != null) {
686
- try {
687
- resultSet.close();
688
- } catch(Exception e) {}
689
- }
690
- }
691
-
692
- public static void close(Statement statement) {
693
- if (statement != null) {
694
- try {
695
- statement.close();
696
- } catch(Exception e) {}
697
- }
942
+ protected IRubyObject getConfigValue(final ThreadContext context, final String key) {
943
+ final IRubyObject config = getInstanceVariable("@config");
944
+ return config.callMethod(context, "[]", context.getRuntime().newSymbol(key));
698
945
  }
699
-
946
+
947
+ /**
948
+ * @deprecated renamed to {@link #getConfigValue(ThreadContext, String)}
949
+ */
950
+ @Deprecated
700
951
  protected IRubyObject config_value(ThreadContext context, String key) {
701
- IRubyObject config_hash = getInstanceVariable("@config");
702
-
703
- return config_hash.callMethod(context, "[]", context.getRuntime().newSymbol(key));
952
+ return getConfigValue(context, key);
704
953
  }
705
954
 
706
955
  private static String toStringOrNull(IRubyObject arg) {
707
956
  return arg.isNil() ? null : arg.toString();
708
957
  }
709
958
 
710
- protected IRubyObject doubleToRuby(Ruby runtime, ResultSet resultSet, double doubleValue)
711
- throws SQLException, IOException {
712
- if (doubleValue == 0 && resultSet.wasNull()) return runtime.getNil();
713
- return runtime.newFloat(doubleValue);
714
- }
715
-
716
959
  protected IRubyObject getAdapter(ThreadContext context) {
717
960
  return callMethod(context, "adapter");
718
961
  }
@@ -723,7 +966,7 @@ public class RubyJdbcConnection extends RubyObject {
723
966
 
724
967
  protected JdbcConnectionFactory getConnectionFactory() throws RaiseException {
725
968
  IRubyObject connection_factory = getInstanceVariable("@connection_factory");
726
- if (connection_factory == null) {
969
+ if ( connection_factory == null ) {
727
970
  throw getRuntime().newRuntimeError("@connection_factory not set");
728
971
  }
729
972
  JdbcConnectionFactory connectionFactory;
@@ -731,161 +974,402 @@ public class RubyJdbcConnection extends RubyObject {
731
974
  connectionFactory = (JdbcConnectionFactory)
732
975
  connection_factory.toJava(JdbcConnectionFactory.class);
733
976
  }
734
- catch (Exception e) { // TODO debug this !
735
- connectionFactory = null;
736
- }
737
- if (connectionFactory == null) {
738
- throw getRuntime().newRuntimeError("@connection_factory not set properly");
977
+ catch (Exception e) {
978
+ throw getRuntime().newRuntimeError("@connection_factory not set properly: " + e);
739
979
  }
740
980
  return connectionFactory;
741
981
  }
742
982
 
743
- private static String[] getTypes(IRubyObject typeArg) {
744
- if (!(typeArg instanceof RubyArray)) return new String[] { typeArg.toString() };
745
-
746
- IRubyObject[] arr = rubyApi.convertToJavaArray(typeArg);
747
- String[] types = new String[arr.length];
748
- for (int i = 0; i < types.length; i++) {
749
- types[i] = arr[i].toString();
750
- }
751
-
752
- return types;
753
- }
754
-
755
- private static int getTypeValueFor(Ruby runtime, IRubyObject type) throws SQLException {
756
- // How could this ever yield anything useful?
757
- if (!(type instanceof RubySymbol)) type = rubyApi.callMethod(type, "class");
758
-
759
- // Assumption; If this is a symbol then it will be backed by an interned string. (enebo)
760
- String internedValue = type.asJavaString();
761
-
762
- if(internedValue == "string") {
763
- return Types.VARCHAR;
764
- } else if(internedValue == "text") {
765
- return Types.CLOB;
766
- } else if(internedValue == "integer") {
767
- return Types.INTEGER;
768
- } else if(internedValue == "decimal") {
769
- return Types.DECIMAL;
770
- } else if(internedValue == "float") {
771
- return Types.FLOAT;
772
- } else if(internedValue == "datetime") {
773
- return Types.TIMESTAMP;
774
- } else if(internedValue == "timestamp") {
775
- return Types.TIMESTAMP;
776
- } else if(internedValue == "time") {
777
- return Types.TIME;
778
- } else if(internedValue == "date") {
779
- return Types.DATE;
780
- } else if(internedValue == "binary") {
781
- return Types.BLOB;
782
- } else if(internedValue == "boolean") {
783
- return Types.BOOLEAN;
784
- } else {
785
- return -1;
983
+ private static String[] getTypes(final IRubyObject typeArg) {
984
+ if ( typeArg instanceof RubyArray ) {
985
+ IRubyObject[] rubyTypes = ((RubyArray) typeArg).toJavaArray();
986
+
987
+ final String[] types = new String[rubyTypes.length];
988
+ for ( int i = 0; i < types.length; i++ ) {
989
+ types[i] = rubyTypes[i].toString();
990
+ }
991
+ return types;
786
992
  }
993
+ return new String[] { typeArg.toString() }; // expect a RubyString
787
994
  }
788
-
789
- private boolean isConnectionBroken(ThreadContext context, Connection c) {
790
- try {
791
- IRubyObject alive = config_value(context, "connection_alive_sql");
792
- if (select_p(context, this, alive).isTrue()) {
793
- String connectionSQL = rubyApi.convertToRubyString(alive).toString();
794
- Statement s = c.createStatement();
795
- try {
796
- s.execute(connectionSQL);
797
- } finally {
798
- close(s);
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();
799
1005
  }
800
- return false;
801
- } else {
802
- return !c.isClosed();
803
1006
  }
804
- } catch (Exception sx) {
805
- return true;
1007
+ else {
1008
+ throw new IllegalArgumentException(
1009
+ "expected a Ruby string/symbol but got: " + type + " (" + type.getMetaClass().getName() + ")"
1010
+ );
1011
+ }
806
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;
807
1030
  }
808
1031
 
809
- protected IRubyObject integerToRuby(Ruby runtime, ResultSet resultSet, long longValue)
810
- throws SQLException {
811
- if (longValue == 0 && resultSet.wasNull()) return runtime.getNil();
812
-
813
- return runtime.newFixnum(longValue);
1032
+ /**
1033
+ * @deprecated this method is no longer used, instead consider overriding
1034
+ * {@link #mapToResult(ThreadContext, Ruby, DatabaseMetaData, ResultSet, RubyJdbcConnection.ColumnData[])}
1035
+ */
1036
+ @Deprecated
1037
+ protected void populateFromResultSet(
1038
+ final ThreadContext context, final Ruby runtime,
1039
+ final List<IRubyObject> results, final ResultSet resultSet,
1040
+ final ColumnData[] columns) throws SQLException {
1041
+ final ResultHandler resultHandler = ResultHandler.getInstance(context);
1042
+ while ( resultSet.next() ) {
1043
+ results.add( resultHandler.mapRawRow(context, runtime, columns, resultSet, this) );
1044
+ }
814
1045
  }
815
1046
 
816
- protected IRubyObject bigIntegerToRuby(Ruby runtime, ResultSet resultSet, String bigint) throws SQLException {
817
- if (bigint == null && resultSet.wasNull()) return runtime.getNil();
1047
+ /**
1048
+ * Maps a query result into a <code>ActiveRecord</code> result.
1049
+ * @param context
1050
+ * @param runtime
1051
+ * @param metaData
1052
+ * @param resultSet
1053
+ * @param columns
1054
+ * @return since 3.1 expected to return a <code>ActiveRecord::Result</code>
1055
+ * @throws SQLException
1056
+ */
1057
+ protected IRubyObject mapToResult(final ThreadContext context, final Ruby runtime,
1058
+ final DatabaseMetaData metaData, final ResultSet resultSet,
1059
+ final ColumnData[] columns) throws SQLException {
818
1060
 
819
- return RubyBignum.bignorm(runtime, new BigInteger(bigint));
1061
+ final ResultHandler resultHandler = ResultHandler.getInstance(context);
1062
+ final RubyArray resultRows = runtime.newArray();
1063
+
1064
+ while ( resultSet.next() ) {
1065
+ resultRows.add( resultHandler.mapRow(context, runtime, columns, resultSet, this) );
1066
+ }
1067
+
1068
+ return resultHandler.newResult(context, runtime, columns, resultRows);
820
1069
  }
821
-
822
- protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
823
- throws SQLException {
1070
+
1071
+ protected IRubyObject jdbcToRuby(final Ruby runtime, final int column,
1072
+ final int type, final ResultSet resultSet) throws SQLException {
824
1073
  try {
825
1074
  switch (type) {
826
- case Types.BINARY:
827
1075
  case Types.BLOB:
828
- case Types.LONGVARBINARY:
1076
+ case Types.BINARY:
829
1077
  case Types.VARBINARY:
830
- return streamToRuby(runtime, resultSet, resultSet.getBinaryStream(column));
831
- case Types.LONGVARCHAR:
832
- return runtime.is1_9() ?
833
- readerToRuby(runtime, resultSet, resultSet.getCharacterStream(column)) :
834
- streamToRuby(runtime, resultSet, resultSet.getBinaryStream(column));
1078
+ case Types.LONGVARBINARY:
1079
+ return streamToRuby(runtime, resultSet, column);
835
1080
  case Types.CLOB:
836
- return readerToRuby(runtime, resultSet, resultSet.getCharacterStream(column));
837
- case Types.TIMESTAMP:
838
- return timestampToRuby(runtime, resultSet, resultSet.getTimestamp(column));
839
- case Types.INTEGER:
840
- case Types.SMALLINT:
1081
+ case Types.NCLOB: // JDBC 4.0
1082
+ return readerToRuby(runtime, resultSet, column);
1083
+ case Types.LONGVARCHAR:
1084
+ case Types.LONGNVARCHAR: // JDBC 4.0
1085
+ if ( runtime.is1_9() ) {
1086
+ return readerToRuby(runtime, resultSet, column);
1087
+ }
1088
+ else {
1089
+ return streamToRuby(runtime, resultSet, column);
1090
+ }
841
1091
  case Types.TINYINT:
842
- return integerToRuby(runtime, resultSet, resultSet.getLong(column));
1092
+ case Types.SMALLINT:
1093
+ case Types.INTEGER:
1094
+ return integerToRuby(runtime, resultSet, column);
843
1095
  case Types.REAL:
844
- return doubleToRuby(runtime, resultSet, resultSet.getDouble(column));
1096
+ case Types.FLOAT:
1097
+ case Types.DOUBLE:
1098
+ return doubleToRuby(runtime, resultSet, column);
845
1099
  case Types.BIGINT:
846
- return bigIntegerToRuby(runtime, resultSet, resultSet.getString(column));
1100
+ return bigIntegerToRuby(runtime, resultSet, column);
1101
+ case Types.NUMERIC:
1102
+ case Types.DECIMAL:
1103
+ return decimalToRuby(runtime, resultSet, column);
1104
+ case Types.DATE:
1105
+ return dateToRuby(runtime, resultSet, column);
1106
+ case Types.TIME:
1107
+ return timeToRuby(runtime, resultSet, column);
1108
+ case Types.TIMESTAMP:
1109
+ return timestampToRuby(runtime, resultSet, column);
1110
+ case Types.BIT:
1111
+ case Types.BOOLEAN:
1112
+ return booleanToRuby(runtime, resultSet, column);
1113
+ case Types.SQLXML: // JDBC 4.0
1114
+ return xmlToRuby(runtime, resultSet, column);
1115
+ case Types.NULL:
1116
+ return runtime.getNil();
1117
+ // NOTE: (JDBC) exotic stuff just cause it's so easy with JRuby :)
1118
+ case Types.JAVA_OBJECT:
1119
+ case Types.OTHER:
1120
+ return objectToRuby(runtime, resultSet, column);
1121
+ case Types.ARRAY: // we handle JDBC Array into (Ruby) []
1122
+ return arrayToRuby(runtime, resultSet, column);
1123
+ // (default) String
1124
+ case Types.CHAR:
1125
+ case Types.VARCHAR:
1126
+ case Types.NCHAR: // JDBC 4.0
1127
+ case Types.NVARCHAR: // JDBC 4.0
847
1128
  default:
848
- return stringToRuby(runtime, resultSet, resultSet.getString(column));
1129
+ return stringToRuby(runtime, resultSet, column);
849
1130
  }
850
- } catch (IOException ioe) {
851
- throw (SQLException) new SQLException(ioe.getMessage()).initCause(ioe);
1131
+ // NOTE: not mapped types :
1132
+ //case Types.DISTINCT:
1133
+ //case Types.STRUCT:
1134
+ //case Types.REF:
1135
+ //case Types.DATALINK:
1136
+ }
1137
+ catch (IOException e) {
1138
+ throw new SQLException(e.getMessage(), e);
852
1139
  }
853
1140
  }
854
1141
 
855
- protected void populateFromResultSet(ThreadContext context, Ruby runtime, List results,
856
- ResultSet resultSet, ColumnData[] columns) throws SQLException {
857
- int columnCount = columns.length;
1142
+ protected IRubyObject integerToRuby(
1143
+ final Ruby runtime, final ResultSet resultSet, final int column)
1144
+ throws SQLException {
1145
+ final long value = resultSet.getLong(column);
1146
+ if ( value == 0 && resultSet.wasNull() ) return runtime.getNil();
1147
+ return integerToRuby(runtime, resultSet, value);
1148
+ }
1149
+
1150
+ @Deprecated
1151
+ protected IRubyObject integerToRuby(
1152
+ final Ruby runtime, final ResultSet resultSet, final long longValue)
1153
+ throws SQLException {
1154
+ if ( longValue == 0 && resultSet.wasNull() ) return runtime.getNil();
858
1155
 
859
- while (resultSet.next()) {
860
- RubyHash row = RubyHash.newHash(runtime);
1156
+ return runtime.newFixnum(longValue);
1157
+ }
861
1158
 
862
- for (int i = 0; i < columnCount; i++) {
863
- row.op_aset(context, columns[i].name, jdbcToRuby(runtime, columns[i].index, columns[i].type, resultSet));
864
- }
1159
+ protected IRubyObject doubleToRuby(Ruby runtime, ResultSet resultSet, final int column)
1160
+ throws SQLException {
1161
+ final double value = resultSet.getDouble(column);
1162
+ if ( value == 0 && resultSet.wasNull() ) return runtime.getNil();
1163
+ return doubleToRuby(runtime, resultSet, value);
1164
+ }
1165
+
1166
+ @Deprecated
1167
+ protected IRubyObject doubleToRuby(Ruby runtime, ResultSet resultSet, double doubleValue)
1168
+ throws SQLException {
1169
+ if ( doubleValue == 0 && resultSet.wasNull() ) return runtime.getNil();
1170
+ return runtime.newFloat(doubleValue);
1171
+ }
865
1172
 
866
- results.add(row);
867
- }
1173
+ protected IRubyObject stringToRuby(
1174
+ final Ruby runtime, final ResultSet resultSet, final int column)
1175
+ throws SQLException {
1176
+ final String value = resultSet.getString(column);
1177
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1178
+ return stringToRuby(runtime, resultSet, value);
868
1179
  }
1180
+
1181
+ @Deprecated
1182
+ protected IRubyObject stringToRuby(
1183
+ final Ruby runtime, final ResultSet resultSet, final String string)
1184
+ throws SQLException {
1185
+ if ( string == null && resultSet.wasNull() ) return runtime.getNil();
869
1186
 
1187
+ return RubyString.newUnicodeString(runtime, string);
1188
+ }
870
1189
 
871
- protected IRubyObject readerToRuby(Ruby runtime, ResultSet resultSet, Reader reader)
872
- throws SQLException, IOException {
873
- if (reader == null && resultSet.wasNull()) return runtime.getNil();
1190
+ protected IRubyObject bigIntegerToRuby(
1191
+ final Ruby runtime, final ResultSet resultSet, final int column)
1192
+ throws SQLException {
1193
+ final String value = resultSet.getString(column);
1194
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1195
+ return bigIntegerToRuby(runtime, resultSet, value);
1196
+ }
1197
+
1198
+ @Deprecated
1199
+ protected IRubyObject bigIntegerToRuby(
1200
+ final Ruby runtime, final ResultSet resultSet, final String intValue)
1201
+ throws SQLException {
1202
+ if ( intValue == null && resultSet.wasNull() ) return runtime.getNil();
874
1203
 
875
- StringBuffer str = new StringBuffer(2048);
1204
+ return RubyBignum.bignorm(runtime, new BigInteger(intValue));
1205
+ }
1206
+
1207
+ protected IRubyObject decimalToRuby(
1208
+ final Ruby runtime, final ResultSet resultSet, final int column)
1209
+ throws SQLException {
1210
+ final String value = resultSet.getString(column);
1211
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1212
+ // NOTE: JRuby 1.6 -> 1.7 API change : moved org.jruby.RubyBigDecimal
1213
+ return runtime.getKernel().callMethod("BigDecimal", runtime.newString(value));
1214
+ }
1215
+
1216
+ private static boolean parseDateTime = false; // TODO
1217
+
1218
+ protected IRubyObject dateToRuby( // TODO
1219
+ final Ruby runtime, final ResultSet resultSet, final int column)
1220
+ throws SQLException {
1221
+ final Date value = resultSet.getDate(column);
1222
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1223
+ return RubyString.newUnicodeString(runtime, value.toString());
1224
+ }
1225
+
1226
+ protected IRubyObject timeToRuby( // TODO
1227
+ final Ruby runtime, final ResultSet resultSet, final int column)
1228
+ throws SQLException {
1229
+ final Time value = resultSet.getTime(column);
1230
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1231
+ return RubyString.newUnicodeString(runtime, value.toString());
1232
+ }
1233
+
1234
+ protected IRubyObject timestampToRuby(
1235
+ final Ruby runtime, final ResultSet resultSet, final int column)
1236
+ throws SQLException {
1237
+ final Timestamp value = resultSet.getTimestamp(column);
1238
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1239
+ return timestampToRuby(runtime, resultSet, value);
1240
+ }
1241
+
1242
+ @Deprecated
1243
+ protected IRubyObject timestampToRuby(
1244
+ final Ruby runtime, final ResultSet resultSet, final Timestamp value)
1245
+ throws SQLException {
1246
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1247
+
1248
+ String format = value.toString(); // yyyy-mm-dd hh:mm:ss.fffffffff
1249
+ if ( format.endsWith(" 00:00:00.0") ) {
1250
+ format = format.substring(0, format.length() - (" 00:00:00.0".length()));
1251
+ }
1252
+ if ( format.endsWith(".0") ) {
1253
+ format = format.substring(0, format.length() - (".0".length()));
1254
+ }
1255
+
1256
+ return RubyString.newUnicodeString(runtime, format);
1257
+ }
1258
+
1259
+ protected IRubyObject booleanToRuby(
1260
+ final Ruby runtime, final ResultSet resultSet, final int column)
1261
+ throws SQLException {
1262
+ final boolean value = resultSet.getBoolean(column);
1263
+ if ( resultSet.wasNull() ) return runtime.getNil();
1264
+ return booleanToRuby(runtime, resultSet, value);
1265
+ }
1266
+
1267
+ @Deprecated
1268
+ protected IRubyObject booleanToRuby(
1269
+ final Ruby runtime, final ResultSet resultSet, final boolean value)
1270
+ throws SQLException {
1271
+ if ( value == false && resultSet.wasNull() ) return runtime.getNil();
1272
+ return runtime.newBoolean(value);
1273
+ }
1274
+
1275
+ protected static int streamBufferSize = 2048;
1276
+
1277
+ protected IRubyObject streamToRuby(
1278
+ final Ruby runtime, final ResultSet resultSet, final int column)
1279
+ throws SQLException, IOException {
1280
+ final InputStream stream = resultSet.getBinaryStream(column);
876
1281
  try {
877
- char[] buf = new char[2048];
1282
+ if ( resultSet.wasNull() ) return runtime.getNil();
1283
+ return streamToRuby(runtime, resultSet, stream);
1284
+ }
1285
+ finally { if ( stream != null ) stream.close(); }
1286
+ }
1287
+
1288
+ @Deprecated
1289
+ protected IRubyObject streamToRuby(
1290
+ final Ruby runtime, final ResultSet resultSet, final InputStream stream)
1291
+ throws SQLException, IOException {
1292
+ if ( stream == null && resultSet.wasNull() ) return runtime.getNil();
878
1293
 
879
- for (int n = reader.read(buf); n != -1; n = reader.read(buf)) {
880
- str.append(buf, 0, n);
881
- }
882
- } finally {
883
- reader.close();
1294
+ final int bufSize = streamBufferSize;
1295
+ final ByteList string = new ByteList(bufSize);
1296
+
1297
+ final byte[] buf = new byte[bufSize];
1298
+ for (int len = stream.read(buf); len != -1; len = stream.read(buf)) {
1299
+ string.append(buf, 0, len);
884
1300
  }
885
1301
 
886
- return RubyString.newUnicodeString(runtime, str.toString());
1302
+ return runtime.newString(string);
887
1303
  }
888
1304
 
1305
+ protected IRubyObject readerToRuby(
1306
+ final Ruby runtime, final ResultSet resultSet, final int column)
1307
+ throws SQLException, IOException {
1308
+ final Reader reader = resultSet.getCharacterStream(column);
1309
+ try {
1310
+ if ( resultSet.wasNull() ) return runtime.getNil();
1311
+ return readerToRuby(runtime, resultSet, reader);
1312
+ }
1313
+ finally { if ( reader != null ) reader.close(); }
1314
+ }
1315
+
1316
+ @Deprecated
1317
+ protected IRubyObject readerToRuby(
1318
+ final Ruby runtime, final ResultSet resultSet, final Reader reader)
1319
+ throws SQLException, IOException {
1320
+ if ( reader == null && resultSet.wasNull() ) return runtime.getNil();
1321
+
1322
+ final int bufSize = streamBufferSize;
1323
+ final StringBuilder string = new StringBuilder(bufSize);
1324
+
1325
+ final char[] buf = new char[bufSize];
1326
+ for (int len = reader.read(buf); len != -1; len = reader.read(buf)) {
1327
+ string.append(buf, 0, len);
1328
+ }
1329
+
1330
+ return RubyString.newUnicodeString(runtime, string.toString());
1331
+ }
1332
+
1333
+ protected IRubyObject objectToRuby(
1334
+ final Ruby runtime, final ResultSet resultSet, final int column)
1335
+ throws SQLException {
1336
+ final Object value = resultSet.getObject(column);
1337
+
1338
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1339
+
1340
+ return JavaUtil.convertJavaToRuby(runtime, value);
1341
+ }
1342
+
1343
+ protected IRubyObject arrayToRuby(
1344
+ final Ruby runtime, final ResultSet resultSet, final int column)
1345
+ throws SQLException {
1346
+ final Array value = resultSet.getArray(column);
1347
+ try {
1348
+ if ( value == null && resultSet.wasNull() ) return runtime.getNil();
1349
+
1350
+ final RubyArray array = runtime.newArray();
1351
+
1352
+ final ResultSet arrayResult = value.getResultSet(); // 1: index, 2: value
1353
+ final int baseType = value.getBaseType();
1354
+ while ( arrayResult.next() ) {
1355
+ IRubyObject element = jdbcToRuby(runtime, 2, baseType, arrayResult);
1356
+ array.append(element);
1357
+ }
1358
+ return array;
1359
+ }
1360
+ finally { value.free(); }
1361
+ }
1362
+
1363
+ protected IRubyObject xmlToRuby(
1364
+ final Ruby runtime, final ResultSet resultSet, final int column)
1365
+ throws SQLException {
1366
+ final SQLXML xml = resultSet.getSQLXML(column);
1367
+ try {
1368
+ return RubyString.newUnicodeString(runtime, xml.getString());
1369
+ }
1370
+ finally { xml.free(); }
1371
+ }
1372
+
889
1373
  protected final Connection getConnection() {
890
1374
  return getConnection(false);
891
1375
  }
@@ -893,8 +1377,8 @@ public class RubyJdbcConnection extends RubyObject {
893
1377
  protected Connection getConnection(boolean error) {
894
1378
  final Connection connection = (Connection) dataGetStruct();
895
1379
  if ( connection == null && error ) {
896
- RubyClass err = getRuntime().getModule("ActiveRecord").getClass("ConnectionNotEstablished");
897
- throw new RaiseException(getRuntime(), err, "no connection available", false);
1380
+ final RubyClass errorClass = getConnectionNotEstablished( getRuntime() );
1381
+ throw new RaiseException(getRuntime(), errorClass, "no connection available", false);
898
1382
  }
899
1383
  return connection;
900
1384
  }
@@ -903,166 +1387,195 @@ public class RubyJdbcConnection extends RubyObject {
903
1387
  close( getConnection(false) ); // close previously open connection if there is one
904
1388
 
905
1389
  final IRubyObject rubyConnectionObject =
906
- connection != null ? wrappedConnection(connection) : getRuntime().getNil();
1390
+ connection != null ? convertJavaToRuby(connection) : getRuntime().getNil();
907
1391
  setInstanceVariable( "@connection", rubyConnectionObject );
908
1392
  dataWrapStruct(connection);
909
1393
  return this;
910
1394
  }
911
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
1404
+ }
1405
+ else { // alive_sql nil (or not a statement we can execute)
1406
+ return ! connection.isClosed(); // if closed than broken
1407
+ }
1408
+ }
1409
+ catch (Exception e) {
1410
+ debugMessage(context, "connection considered broken due: " + e.toString());
1411
+ return true;
1412
+ }
1413
+ finally { close(statement); }
1414
+ }
1415
+
912
1416
  private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
913
1417
 
914
- private static void setValue(PreparedStatement ps, int index, ThreadContext context,
915
- IRubyObject value, IRubyObject type) throws SQLException {
916
- final int tp = getTypeValueFor(context.getRuntime(), type);
917
- if(value.isNil()) {
918
- ps.setNull(index, tp);
1418
+ private static void setValue(final ThreadContext context,
1419
+ final IRubyObject value, final IRubyObject type,
1420
+ final PreparedStatement statement, final int index) throws SQLException {
1421
+
1422
+ final int jdbcType = jdbcTypeFor(context, type);
1423
+
1424
+ if ( value.isNil() ) {
1425
+ statement.setNull(index, jdbcType);
919
1426
  return;
920
1427
  }
921
1428
 
922
- switch(tp) {
1429
+ switch (jdbcType) {
923
1430
  case Types.VARCHAR:
924
1431
  case Types.CLOB:
925
- ps.setString(index, RubyString.objAsString(context, value).toString());
1432
+ statement.setString(index, RubyString.objAsString(context, value).toString());
926
1433
  break;
927
1434
  case Types.INTEGER:
928
- ps.setLong(index, RubyNumeric.fix2long(value));
1435
+ statement.setLong(index, RubyNumeric.fix2long(value));
929
1436
  break;
930
1437
  case Types.FLOAT:
931
- ps.setDouble(index, ((RubyNumeric)value).getDoubleValue());
1438
+ statement.setDouble(index, ((RubyNumeric) value).getDoubleValue());
932
1439
  break;
933
1440
  case Types.TIMESTAMP:
934
1441
  case Types.TIME:
935
1442
  case Types.DATE:
936
- if(!(value instanceof RubyTime)) {
1443
+ if ( ! ( value instanceof RubyTime ) ) {
1444
+ final String stringValue = RubyString.objAsString(context, value).toString();
937
1445
  try {
938
- Date dd = FORMAT.parse(RubyString.objAsString(context, value).toString());
939
- ps.setTimestamp(index, new java.sql.Timestamp(dd.getTime()), Calendar.getInstance());
940
- } catch(Exception e) {
941
- ps.setString(index, RubyString.objAsString(context, value).toString());
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 );
942
1451
  }
943
1452
  } else {
944
- RubyTime rubyTime = (RubyTime) value;
945
- java.util.Date date = rubyTime.getJavaDate();
946
- long millis = date.getTime();
947
- long micros = rubyTime.microseconds() - millis / 1000;
948
- java.sql.Timestamp ts = new java.sql.Timestamp(millis);
949
- java.util.Calendar cal = Calendar.getInstance();
950
- cal.setTime(date);
951
- ts.setNanos((int)(micros * 1000));
952
- ps.setTimestamp(index, ts, cal);
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
1463
+ }
1464
+ statement.setTimestamp( index, timestamp, calendar );
953
1465
  }
954
1466
  break;
955
1467
  case Types.BOOLEAN:
956
- ps.setBoolean(index, value.isTrue());
1468
+ statement.setBoolean(index, value.isTrue());
957
1469
  break;
958
- default: throw new RuntimeException("type " + type + " not supported in _bind yet");
1470
+ default: throw new RuntimeException("type " + jdbcType + " not supported in _bind (yet)");
959
1471
  }
960
1472
  }
961
1473
 
962
- private static void setValuesOnPS(PreparedStatement ps, ThreadContext context,
963
- IRubyObject valuesArg, IRubyObject typesArg) throws SQLException {
964
- RubyArray values = (RubyArray) valuesArg;
965
- RubyArray types = (RubyArray) typesArg;
966
-
967
- for(int i=0, j=values.getLength(); i<j; i++) {
968
- setValue(ps, i+1, context, values.eltInternal(i), types.eltInternal(i));
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);
969
1481
  }
970
1482
  }
971
-
972
- protected IRubyObject streamToRuby(Ruby runtime, ResultSet resultSet, InputStream is)
973
- throws SQLException, IOException {
974
- if (is == null && resultSet.wasNull()) return runtime.getNil();
975
-
976
- ByteList str = new ByteList(2048);
1483
+
1484
+ private boolean tableExists(final Ruby runtime,
1485
+ final Connection connection, final TableName tableName) throws SQLException {
1486
+ final IRubyObject matchedTables =
1487
+ matchTables(runtime, connection, tableName.catalog, tableName.schema, tableName.name, getTableTypes(), true);
1488
+ // NOTE: allow implementers to ignore checkExistsOnly paramater - empty array means does not exists
1489
+ return matchedTables != null && ! matchedTables.isNil() &&
1490
+ ( ! (matchedTables instanceof RubyArray) || ! ((RubyArray) matchedTables).isEmpty() );
1491
+ }
1492
+
1493
+ /**
1494
+ * Match table names for given table name (pattern).
1495
+ * @param runtime
1496
+ * @param connection
1497
+ * @param catalog
1498
+ * @param schemaPattern
1499
+ * @param tablePattern
1500
+ * @param types table types
1501
+ * @param checkExistsOnly an optimization flag (that might be ignored by sub-classes)
1502
+ * whether the result really matters if true no need to map table names and a truth-y
1503
+ * value is sufficient (except for an empty array which is considered that the table
1504
+ * did not exists).
1505
+ * @return matched (and Ruby mapped) table names
1506
+ * @see #mapTables(Ruby, DatabaseMetaData, String, String, String, ResultSet)
1507
+ * @throws SQLException
1508
+ */
1509
+ protected IRubyObject matchTables(final Ruby runtime,
1510
+ final Connection connection,
1511
+ final String catalog, final String schemaPattern,
1512
+ final String tablePattern, final String[] types,
1513
+ final boolean checkExistsOnly) throws SQLException {
1514
+
1515
+ final DatabaseMetaData metaData = connection.getMetaData();
1516
+
1517
+ final String _tablePattern = caseConvertIdentifierForJdbc(metaData, tablePattern);
1518
+ final String _schemaPattern = caseConvertIdentifierForJdbc(metaData, schemaPattern);
1519
+
1520
+ ResultSet tablesSet = null;
977
1521
  try {
978
- byte[] buf = new byte[2048];
979
-
980
- for (int n = is.read(buf); n != -1; n = is.read(buf)) {
981
- str.append(buf, 0, n);
1522
+ tablesSet = metaData.getTables(catalog, _schemaPattern, _tablePattern, types);
1523
+ if ( checkExistsOnly ) { // only check if given table exists
1524
+ return tablesSet.next() ? runtime.getTrue() : null;
1525
+ }
1526
+ else {
1527
+ return mapTables(runtime, metaData, catalog, _schemaPattern, _tablePattern, tablesSet);
982
1528
  }
983
- } finally {
984
- is.close();
985
1529
  }
986
-
987
- return runtime.newString(str);
1530
+ finally { close(tablesSet); }
988
1531
  }
989
-
990
- protected IRubyObject stringToRuby(Ruby runtime, ResultSet resultSet, String string)
991
- throws SQLException, IOException {
992
- if (string == null && resultSet.wasNull()) return runtime.getNil();
993
-
994
- return RubyString.newUnicodeString(runtime, string);
1532
+
1533
+ // NOTE java.sql.DatabaseMetaData.getTables :
1534
+ protected final static int TABLES_TABLE_CAT = 1;
1535
+ protected final static int TABLES_TABLE_SCHEM = 2;
1536
+ protected final static int TABLES_TABLE_NAME = 3;
1537
+ protected final static int TABLES_TABLE_TYPE = 4;
1538
+
1539
+ /**
1540
+ * @param runtime
1541
+ * @param metaData
1542
+ * @param catalog
1543
+ * @param schemaPattern
1544
+ * @param tablePattern
1545
+ * @param tablesSet
1546
+ * @return List<RubyString>
1547
+ * @throws SQLException
1548
+ */
1549
+ protected RubyArray mapTables(final Ruby runtime, final DatabaseMetaData metaData,
1550
+ final String catalog, final String schemaPattern, final String tablePattern,
1551
+ final ResultSet tablesSet) throws SQLException {
1552
+ final RubyArray tables = runtime.newArray();
1553
+ while ( tablesSet.next() ) {
1554
+ String name = tablesSet.getString(TABLES_TABLE_NAME);
1555
+ name = caseConvertIdentifierForRails(metaData, name);
1556
+ tables.add(RubyString.newUnicodeString(runtime, name));
1557
+ }
1558
+ return tables;
995
1559
  }
996
1560
 
997
-
1561
+ /**
1562
+ * NOTE: since 1.3.0 only present for binary compatibility (with extensions).
1563
+ *
1564
+ * @depreacated no longer used - replaced with
1565
+ * {@link #matchTables(Ruby, Connection, String, String, String, String[], boolean)}
1566
+ * please update your sub-class esp. if you're overriding this method !
1567
+ */
1568
+ @Deprecated
998
1569
  protected SQLBlock tableLookupBlock(final Ruby runtime,
999
- final String catalog, final String schemapat,
1000
- final String tablepat, final String[] types, final boolean downCase) {
1001
- final int TABLE_SCHEM = 2;
1002
- final int TABLE_NAME = 3;
1003
- final int TABLE_TYPE = 4;
1570
+ final String catalog, final String schemaPattern,
1571
+ final String tablePattern, final String[] types) {
1004
1572
  return new SQLBlock() {
1005
- public Object call(Connection c) throws SQLException {
1006
- ResultSet rs = null;
1007
- try {
1008
- DatabaseMetaData metadata = c.getMetaData();
1009
- String clzName = metadata.getClass().getName().toLowerCase();
1010
- boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
1011
- boolean isDerby = clzName.indexOf("derby") != -1;
1012
- boolean isMssql = clzName.indexOf("sqlserver") != -1 || clzName.indexOf("tds") != -1;
1013
-
1014
- String realschema = schemapat;
1015
- String realtablepat = tablepat;
1016
-
1017
- if (isDerby && realschema != null && realschema.equals("")) realschema = null; // Derby doesn't like empty-string schema name
1018
- if (realtablepat != null) realtablepat = caseConvertIdentifierForJdbc(metadata, realtablepat);
1019
- if (realschema != null) realschema = caseConvertIdentifierForJdbc(metadata, realschema);
1020
-
1021
- rs = metadata.getTables(catalog, realschema, realtablepat, types);
1022
- List arr = new ArrayList();
1023
- while (rs.next()) {
1024
- String name;
1025
- String schema = rs.getString(TABLE_SCHEM) != null ? rs.getString(TABLE_SCHEM).toLowerCase() : null;
1026
-
1027
- if (downCase) {
1028
- name = rs.getString(TABLE_NAME).toLowerCase();
1029
- } else {
1030
- name = caseConvertIdentifierForRails(metadata, rs.getString(TABLE_NAME));
1031
- }
1032
- // Handle stupid Oracle 10g RecycleBin feature
1033
- if (isOracle && name.startsWith("bin$")) {
1034
- continue;
1035
- }
1036
- // Under mssql, don't return system tables/views unless they're explicitly asked for.
1037
- if (isMssql && realschema==null &&
1038
- ("sys".equals(schema) || "information_schema".equals(schema))) {
1039
- continue;
1040
- }
1041
- arr.add(RubyString.newUnicodeString(runtime, name));
1042
- }
1043
- return runtime.newArray(arr);
1044
- } finally {
1045
- close(rs);
1046
- }
1573
+ public IRubyObject call(final Connection connection) throws SQLException {
1574
+ return matchTables(runtime, connection, catalog, schemaPattern, tablePattern, types, false);
1047
1575
  }
1048
1576
  };
1049
1577
  }
1050
-
1051
- protected IRubyObject timestampToRuby(Ruby runtime, ResultSet resultSet, Timestamp time)
1052
- throws SQLException {
1053
- if (time == null && resultSet.wasNull()) return runtime.getNil();
1054
-
1055
- String str = time.toString();
1056
- if (str.endsWith(" 00:00:00.0")) {
1057
- str = str.substring(0, str.length() - (" 00:00:00.0".length()));
1058
- }
1059
- if (str.endsWith(".0")) {
1060
- str = str.substring(0, str.length() - (".0".length()));
1061
- }
1062
-
1063
- return RubyString.newUnicodeString(runtime, str);
1064
- }
1065
-
1578
+
1066
1579
  protected static final int COLUMN_NAME = 4;
1067
1580
  protected static final int DATA_TYPE = 5;
1068
1581
  protected static final int TYPE_NAME = 6;
@@ -1071,15 +1584,10 @@ public class RubyJdbcConnection extends RubyObject {
1071
1584
  protected static final int COLUMN_DEF = 13;
1072
1585
  protected static final int IS_NULLABLE = 18;
1073
1586
 
1074
- protected int intFromResultSet(ResultSet resultSet, int column) throws SQLException {
1075
- int precision = resultSet.getInt(column);
1076
-
1077
- return precision == 0 && resultSet.wasNull() ? -1 : precision;
1078
- }
1079
-
1080
1587
  /**
1081
- * Create a string which represents a sql type usable by Rails from the resultSet column
1082
- * metadata object.
1588
+ * Create a string which represents a SQL type usable by Rails from the
1589
+ * resultSet column meta-data
1590
+ * @param resultSet.
1083
1591
  */
1084
1592
  protected String typeFromResultSet(final ResultSet resultSet) throws SQLException {
1085
1593
  final int precision = intFromResultSet(resultSet, COLUMN_SIZE);
@@ -1088,8 +1596,16 @@ public class RubyJdbcConnection extends RubyObject {
1088
1596
  final String type = resultSet.getString(TYPE_NAME);
1089
1597
  return formatTypeWithPrecisionAndScale(type, precision, scale);
1090
1598
  }
1599
+
1600
+ protected static int intFromResultSet(
1601
+ final ResultSet resultSet, final int column) throws SQLException {
1602
+ final int precision = resultSet.getInt(column);
1603
+ return precision == 0 && resultSet.wasNull() ? -1 : precision;
1604
+ }
1091
1605
 
1092
- protected static String formatTypeWithPrecisionAndScale(final String type, final int precision, final int scale) {
1606
+ protected static String formatTypeWithPrecisionAndScale(
1607
+ final String type, final int precision, final int scale) {
1608
+
1093
1609
  if ( precision <= 0 ) return type;
1094
1610
 
1095
1611
  final StringBuilder typeStr = new StringBuilder().append(type);
@@ -1098,156 +1614,272 @@ public class RubyJdbcConnection extends RubyObject {
1098
1614
  return typeStr.append(')').toString(); // type += ")";
1099
1615
  }
1100
1616
 
1101
- private IRubyObject defaultValueFromResultSet(Ruby runtime, ResultSet resultSet)
1102
- throws SQLException {
1103
- String defaultValue = resultSet.getString(COLUMN_DEF);
1104
-
1617
+ private static IRubyObject defaultValueFromResultSet(final Ruby runtime, final ResultSet resultSet)
1618
+ throws SQLException {
1619
+ final String defaultValue = resultSet.getString(COLUMN_DEF);
1105
1620
  return defaultValue == null ? runtime.getNil() : RubyString.newUnicodeString(runtime, defaultValue);
1106
1621
  }
1107
1622
 
1108
- private IRubyObject unmarshal_columns(ThreadContext context, DatabaseMetaData metadata,
1109
- ResultSet rs, ResultSet pkeys) throws SQLException {
1110
- try {
1111
- Ruby runtime = context.getRuntime();
1112
- List columns = new ArrayList();
1113
- List pkeyNames = new ArrayList();
1114
- String clzName = metadata.getClass().getName().toLowerCase();
1115
-
1116
- RubyHash types = (RubyHash) native_database_types();
1117
- IRubyObject jdbcCol = getJdbcColumnClass(context);
1623
+ private IRubyObject unmarshalColumns(final ThreadContext context,
1624
+ final DatabaseMetaData metaData, final ResultSet results, final ResultSet primaryKeys)
1625
+ throws SQLException {
1626
+
1627
+ final Ruby runtime = context.getRuntime();
1628
+ // RubyHash types = (RubyHash) native_database_types();
1629
+ final IRubyObject jdbcColumn = getJdbcColumnClass(context);
1118
1630
 
1119
- while (pkeys.next()) {
1120
- pkeyNames.add(pkeys.getString(COLUMN_NAME));
1121
- }
1631
+ final List<String> primarykeyNames = new ArrayList<String>();
1632
+ while ( primaryKeys.next() ) {
1633
+ primarykeyNames.add( primaryKeys.getString(COLUMN_NAME) );
1634
+ }
1122
1635
 
1123
- while (rs.next()) {
1124
- String colName = rs.getString(COLUMN_NAME);
1125
- IRubyObject column = jdbcCol.callMethod(context, "new",
1126
- new IRubyObject[] {
1127
- getInstanceVariable("@config"),
1128
- RubyString.newUnicodeString(runtime,
1129
- caseConvertIdentifierForRails(metadata, colName)),
1130
- defaultValueFromResultSet(runtime, rs),
1131
- RubyString.newUnicodeString(runtime, typeFromResultSet(rs)),
1132
- runtime.newBoolean(!rs.getString(IS_NULLABLE).trim().equals("NO"))
1133
- });
1134
- columns.add(column);
1135
-
1136
- if (pkeyNames.contains(colName)) {
1137
- column.callMethod(context, "primary=", runtime.getTrue());
1138
- }
1636
+ final List<IRubyObject> columns = new ArrayList<IRubyObject>();
1637
+ while ( results.next() ) {
1638
+ final String colName = results.getString(COLUMN_NAME);
1639
+ IRubyObject column = jdbcColumn.callMethod(context, "new",
1640
+ new IRubyObject[] {
1641
+ getInstanceVariable("@config"),
1642
+ RubyString.newUnicodeString( runtime, caseConvertIdentifierForRails(metaData, colName) ),
1643
+ defaultValueFromResultSet( runtime, results ),
1644
+ RubyString.newUnicodeString( runtime, typeFromResultSet(results) ),
1645
+ runtime.newBoolean( ! results.getString(IS_NULLABLE).trim().equals("NO") )
1646
+ });
1647
+ columns.add(column);
1648
+
1649
+ if ( primarykeyNames.contains(colName) ) {
1650
+ column.callMethod(context, "primary=", runtime.getTrue());
1139
1651
  }
1140
- return runtime.newArray(columns);
1141
- } finally {
1142
- close(rs);
1143
1652
  }
1653
+ return runtime.newArray(columns);
1144
1654
  }
1145
1655
 
1146
-
1147
- public static IRubyObject unmarshal_id_result(Ruby runtime, ResultSet rs) throws SQLException {
1656
+ protected static IRubyObject unmarshalIdResult(
1657
+ final Ruby runtime, final Statement statement) throws SQLException {
1658
+ final ResultSet genKeys = statement.getGeneratedKeys();
1148
1659
  try {
1149
- if (rs.next() && rs.getMetaData().getColumnCount() > 0) {
1150
- return runtime.newFixnum(rs.getLong(1));
1660
+ if (genKeys.next() && genKeys.getMetaData().getColumnCount() > 0) {
1661
+ return runtime.newFixnum( genKeys.getLong(1) );
1151
1662
  }
1152
1663
  return runtime.getNil();
1153
- } finally {
1154
- close(rs);
1155
1664
  }
1665
+ finally { close(genKeys); }
1156
1666
  }
1667
+
1668
+ /**
1669
+ * @deprecated no longer used - kept for binary compatibility, this method
1670
+ * is confusing since it closes the result set it receives and thus was
1671
+ * replaced with {@link #unmarshalIdResult(Ruby, Statement)}
1672
+ */
1673
+ @Deprecated
1674
+ public static IRubyObject unmarshal_id_result(
1675
+ final Ruby runtime, final ResultSet genKeys) throws SQLException {
1676
+ try {
1677
+ if (genKeys.next() && genKeys.getMetaData().getColumnCount() > 0) {
1678
+ return runtime.newFixnum( genKeys.getLong(1) );
1679
+ }
1680
+ return runtime.getNil();
1681
+ }
1682
+ finally { close(genKeys); }
1683
+ }
1157
1684
 
1158
- protected IRubyObject unmarshalResults(ThreadContext context, DatabaseMetaData metadata,
1159
- Statement stmt, boolean downCase) throws SQLException {
1685
+ protected IRubyObject unmarshalResults(final ThreadContext context,
1686
+ final DatabaseMetaData metaData, final Statement statement,
1687
+ final boolean downCase) throws SQLException {
1160
1688
 
1161
- IRubyObject result = unmarshalResult(context, metadata, stmt.getResultSet(), downCase);
1689
+ final Ruby runtime = context.getRuntime();
1690
+ IRubyObject result;
1691
+ ResultSet resultSet = statement.getResultSet();
1692
+ try {
1693
+ result = mapToRawResult(context, runtime, metaData, resultSet, downCase);
1694
+ }
1695
+ finally { close(resultSet); }
1162
1696
 
1163
- if ( ! stmt.getMoreResults() ) return result;
1697
+ if ( ! statement.getMoreResults() ) return result;
1164
1698
 
1165
1699
  final List<IRubyObject> results = new ArrayList<IRubyObject>();
1166
1700
  results.add(result);
1701
+
1167
1702
  do {
1168
- result = unmarshalResult(context, metadata, stmt.getResultSet(), downCase);
1703
+ resultSet = statement.getResultSet();
1704
+ try {
1705
+ result = mapToRawResult(context, runtime, metaData, resultSet, downCase);
1706
+ }
1707
+ finally { close(resultSet); }
1708
+
1169
1709
  results.add(result);
1170
1710
  }
1171
- while ( stmt.getMoreResults() );
1711
+ while ( statement.getMoreResults() );
1172
1712
 
1173
- return context.getRuntime().newArray(results);
1713
+ return runtime.newArray(results);
1174
1714
  }
1175
1715
 
1176
1716
  /**
1177
- * Converts a jdbc resultset into an array (rows) of hashes (row) that AR expects.
1717
+ * @deprecated no longer used but kept for binary compatibility
1718
+ */
1719
+ @Deprecated
1720
+ protected IRubyObject unmarshalResult(final ThreadContext context,
1721
+ final DatabaseMetaData metaData, final ResultSet resultSet,
1722
+ final boolean downCase) throws SQLException {
1723
+ return mapToRawResult(context, context.getRuntime(), metaData, resultSet, downCase);
1724
+ }
1725
+
1726
+ /**
1727
+ * Converts a JDBC result set into an array (rows) of hashes (row).
1178
1728
  *
1179
1729
  * @param downCase should column names only be in lower case?
1180
1730
  */
1181
- protected IRubyObject unmarshalResult(ThreadContext context, DatabaseMetaData metadata,
1182
- ResultSet resultSet, boolean downCase) throws SQLException {
1183
- Ruby runtime = context.getRuntime();
1184
- List results = new ArrayList();
1731
+ @SuppressWarnings("unchecked")
1732
+ private IRubyObject mapToRawResult(final ThreadContext context, final Ruby runtime,
1733
+ final DatabaseMetaData metaData, final ResultSet resultSet,
1734
+ final boolean downCase) throws SQLException {
1735
+
1736
+ ColumnData[] columns = extractColumns(runtime, metaData, resultSet, downCase);
1185
1737
 
1738
+ final RubyArray results = runtime.newArray();
1739
+ // [ { 'col1': 1, 'col2': 2 }, { 'col1': 3, 'col2': 4 } ]
1740
+ populateFromResultSet(context, runtime, (List<IRubyObject>) results, resultSet, columns);
1741
+ return results;
1742
+ }
1743
+
1744
+ /**
1745
+ * Extract columns from result set.
1746
+ * @param runtime
1747
+ * @param metaData
1748
+ * @param resultSet
1749
+ * @param downCase
1750
+ * @return columns data
1751
+ * @throws SQLException
1752
+ */
1753
+ protected ColumnData[] extractColumns(final Ruby runtime,
1754
+ final DatabaseMetaData metaData, final ResultSet resultSet,
1755
+ final boolean downCase) throws SQLException {
1756
+ return setupColumns(runtime, metaData, resultSet.getMetaData(), downCase);
1757
+ }
1758
+
1759
+ /**
1760
+ * @deprecated renamed and parameterized to {@link #withConnection(ThreadContext, SQLBlock)}
1761
+ */
1762
+ @Deprecated
1763
+ @SuppressWarnings("unchecked")
1764
+ protected Object withConnectionAndRetry(final ThreadContext context, final SQLBlock block)
1765
+ throws RaiseException {
1766
+ return withConnection(context, block);
1767
+ }
1768
+
1769
+ protected <T> T withConnection(final ThreadContext context, final Callable<T> block)
1770
+ throws RaiseException {
1186
1771
  try {
1187
- ColumnData[] columns = ColumnData.setup(runtime, metadata, resultSet.getMetaData(), downCase);
1188
-
1189
- populateFromResultSet(context, runtime, results, resultSet, columns);
1190
- } finally {
1191
- close(resultSet);
1772
+ return withConnection(context, true, block);
1773
+ }
1774
+ catch (final SQLException e) {
1775
+ return handleException(context, e); // should never happen
1192
1776
  }
1193
-
1194
- return runtime.newArray(results);
1195
1777
  }
1196
-
1197
- protected Object withConnectionAndRetry(ThreadContext context, SQLBlock block) {
1198
- int tries = 1;
1199
- int i = 0;
1200
- Throwable toWrap = null;
1201
- boolean autoCommit = false;
1202
- while (i < tries) {
1203
- Connection c = getConnection(true);
1778
+
1779
+ private <T> T withConnection(final ThreadContext context, final boolean handleException, final Callable<T> block)
1780
+ throws RaiseException, RuntimeException, SQLException {
1781
+
1782
+ Throwable exception = null; int tries = 1; int i = 0;
1783
+
1784
+ while ( i++ < tries ) {
1785
+ final Connection connection = getConnection(true);
1786
+ boolean autoCommit = true; // retry in-case getAutoCommit throws
1204
1787
  try {
1205
- autoCommit = c.getAutoCommit();
1206
- return block.call(c);
1207
- } catch (Exception e) {
1208
- toWrap = e;
1209
- while (toWrap.getCause() != null && toWrap.getCause() != toWrap) {
1210
- toWrap = toWrap.getCause();
1211
- }
1212
-
1213
- if (context.getRuntime().isDebug()) {
1214
- toWrap.printStackTrace(System.out);
1215
- }
1216
-
1217
- i++;
1218
- if (autoCommit) {
1219
- if (i == 1) {
1220
- tries = (int) rubyApi.convertToRubyInteger(config_value(context, "retry_count")).getLongValue();
1221
- if (tries <= 0) {
1222
- tries = 1;
1223
- }
1788
+ autoCommit = connection.getAutoCommit();
1789
+ return block.call(connection);
1790
+ }
1791
+ catch (final Exception e) { // SQLException or RuntimeException
1792
+ exception = e;
1793
+
1794
+ if ( autoCommit ) { // do not retry if (inside) transactions
1795
+ if ( i == 1 ) {
1796
+ IRubyObject retryCount = getConfigValue(context, "retry_count");
1797
+ tries = (int) retryCount.convertToInteger().getLongValue();
1798
+ if ( tries <= 0 ) tries = 1;
1224
1799
  }
1225
- if (isConnectionBroken(context, c)) {
1226
- reconnect();
1227
- } else {
1228
- throw wrap(context, toWrap);
1800
+ if ( isConnectionBroken(context, connection) ) {
1801
+ reconnect(context); continue; // retry connection (block) again
1229
1802
  }
1803
+ break; // connection not broken yet failed
1230
1804
  }
1231
1805
  }
1232
1806
  }
1233
- throw wrap(context, toWrap);
1807
+ // (retry) loop ended and we did not return ... exception != null
1808
+ if ( handleException ) {
1809
+ return handleException(context, getCause(exception)); // throws
1810
+ }
1811
+ else {
1812
+ if ( exception instanceof SQLException ) {
1813
+ throw (SQLException) exception;
1814
+ }
1815
+ if ( exception instanceof RuntimeException ) {
1816
+ throw (RuntimeException) exception;
1817
+ }
1818
+ // won't happen - our try block only throws SQL or Runtime exceptions
1819
+ throw new RuntimeException(exception);
1820
+ }
1821
+ }
1822
+
1823
+ private static Throwable getCause(Throwable exception) {
1824
+ Throwable cause = exception.getCause();
1825
+ while (cause != null && cause != exception) {
1826
+ // SQLException's cause might be DB specific (checked/unchecked) :
1827
+ if ( exception instanceof SQLException ) break;
1828
+ exception = cause; cause = exception.getCause();
1829
+ }
1830
+ return exception;
1234
1831
  }
1235
1832
 
1236
- protected RuntimeException wrap(ThreadContext context, Throwable exception) {
1237
- Ruby runtime = context.getRuntime();
1238
- RaiseException arError = new RaiseException(runtime, runtime.getModule("ActiveRecord").getClass("JDBCError"),
1239
- exception.getMessage(), true);
1240
- arError.initCause(exception);
1241
- if (exception instanceof SQLException) {
1242
- RuntimeHelpers.invoke(context, arError.getException(),
1243
- "errno=", runtime.newFixnum(((SQLException) exception).getErrorCode()));
1244
- RuntimeHelpers.invoke(context, arError.getException(),
1245
- "sql_exception=", JavaEmbedUtils.javaToRuby(runtime, exception));
1833
+ protected <T> T handleException(final ThreadContext context, Throwable exception)
1834
+ throws RaiseException {
1835
+ // NOTE: we shall not wrap unchecked (runtime) exceptions into AR::Error
1836
+ // if it's really a misbehavior of the driver throwing a RuntimeExcepion
1837
+ // instead of SQLException than this should be overriden for the adapter
1838
+ if ( exception instanceof RuntimeException ) {
1839
+ throw (RuntimeException) exception;
1246
1840
  }
1247
- return (RuntimeException) arError;
1841
+ debugStackTrace(context, exception);
1842
+ throw wrapException(context, exception);
1843
+ }
1844
+
1845
+ /**
1846
+ * @deprecated use {@link #wrapException(ThreadContext, Throwable)} instead
1847
+ * for overriding how exceptions are handled use {@link #handleException(ThreadContext, Throwable)}
1848
+ */
1849
+ @Deprecated
1850
+ protected RuntimeException wrap(final ThreadContext context, final Throwable exception) {
1851
+ return wrapException(context, exception);
1852
+ }
1853
+
1854
+ protected RaiseException wrapException(final ThreadContext context, final Throwable exception) {
1855
+ final Ruby runtime = context.getRuntime();
1856
+ if ( exception instanceof SQLException ) {
1857
+ final String message = SQLException.class == exception.getClass() ?
1858
+ exception.getMessage() : exception.toString(); // useful to easily see type on Ruby side
1859
+ final RaiseException error = wrapException(context, getJDBCError(runtime), exception, message);
1860
+ 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) );
1865
+ return error;
1866
+ }
1867
+ return wrapException(context, getJDBCError(runtime), exception);
1868
+ }
1869
+
1870
+ protected static RaiseException wrapException(final ThreadContext context,
1871
+ final RubyClass errorClass, final Throwable exception) {
1872
+ return wrapException(context, errorClass, exception, exception.toString());
1248
1873
  }
1249
1874
 
1250
- private IRubyObject wrappedConnection(final Connection connection) {
1875
+ protected static RaiseException wrapException(final ThreadContext context,
1876
+ final RubyClass errorClass, final Throwable exception, final String message) {
1877
+ final RaiseException error = new RaiseException(context.getRuntime(), errorClass, message, true);
1878
+ error.initCause(exception);
1879
+ return error;
1880
+ }
1881
+
1882
+ private IRubyObject convertJavaToRuby(final Connection connection) {
1251
1883
  return JavaUtil.convertJavaToRuby( getRuntime(), connection );
1252
1884
  }
1253
1885
 
@@ -1259,110 +1891,319 @@ public class RubyJdbcConnection extends RubyObject {
1259
1891
  return false;
1260
1892
  }
1261
1893
 
1262
- private static int whitespace(int start, ByteList bl) {
1263
- int end = bl.begin + bl.realSize;
1894
+ private static final byte[] SELECT = new byte[] { 's','e','l','e','c','t' };
1895
+ private static final byte[] WITH = new byte[] { 'w','i','t','h' };
1896
+ private static final byte[] SHOW = new byte[] { 's','h','o','w' };
1897
+ private static final byte[] CALL = new byte[]{ 'c','a','l','l' };
1898
+
1899
+ @JRubyMethod(name = "select?", required = 1, meta = true, frame = false)
1900
+ public static IRubyObject select_p(final ThreadContext context,
1901
+ final IRubyObject self, final IRubyObject sql) {
1902
+ return context.getRuntime().newBoolean( isSelect(sql.convertToString()) );
1903
+ }
1904
+
1905
+ private static boolean isSelect(final RubyString sql) {
1906
+ final ByteList sqlBytes = sql.getByteList();
1907
+ return startsWithIgnoreCase(sqlBytes, SELECT) ||
1908
+ startsWithIgnoreCase(sqlBytes, WITH) ||
1909
+ startsWithIgnoreCase(sqlBytes, SHOW) ||
1910
+ startsWithIgnoreCase(sqlBytes, CALL);
1911
+ }
1912
+
1913
+ private static final byte[] INSERT = new byte[] { 'i','n','s','e','r','t' };
1914
+
1915
+ @JRubyMethod(name = "insert?", required = 1, meta = true, frame = false)
1916
+ public static IRubyObject insert_p(final ThreadContext context,
1917
+ final IRubyObject self, final IRubyObject sql) {
1918
+ final ByteList sqlBytes = sql.convertToString().getByteList();
1919
+ return context.getRuntime().newBoolean(startsWithIgnoreCase(sqlBytes, INSERT));
1920
+ }
1921
+
1922
+ protected static boolean startsWithIgnoreCase(final ByteList string, final byte[] start) {
1923
+ int p = skipWhitespace(string, string.getBegin());
1924
+ final byte[] stringBytes = string.unsafeBytes();
1925
+ if ( stringBytes[p] == '(' ) p = skipWhitespace(string, p + 1);
1264
1926
 
1265
- for (int i = start; i < end; i++) {
1266
- if (!Character.isWhitespace(bl.bytes[i])) return i;
1927
+ for ( int i = 0; i < string.getRealSize() && i < start.length; i++ ) {
1928
+ if ( Character.toLowerCase(stringBytes[p + i]) != start[i] ) return false;
1267
1929
  }
1930
+ return true;
1931
+ }
1268
1932
 
1933
+ private static int skipWhitespace(final ByteList string, final int from) {
1934
+ final int end = string.getBegin() + string.getRealSize();
1935
+ final byte[] stringBytes = string.unsafeBytes();
1936
+ for ( int i = from; i < end; i++ ) {
1937
+ if ( ! Character.isWhitespace( stringBytes[i] ) ) return i;
1938
+ }
1269
1939
  return end;
1270
1940
  }
1941
+
1942
+ /**
1943
+ * JDBC connection helper that handles mapping results to
1944
+ * <code>ActiveRecord::Result</code> (available since AR-3.1).
1945
+ *
1946
+ * @see #populateFromResultSet(ThreadContext, Ruby, List, ResultSet, RubyJdbcConnection.ColumnData[])
1947
+ * @author kares
1948
+ */
1949
+ protected static class ResultHandler {
1271
1950
 
1272
- private static byte[] CALL = new byte[]{'c', 'a', 'l', 'l'};
1273
- private static byte[] INSERT = new byte[] {'i', 'n', 's', 'e', 'r', 't'};
1274
- private static byte[] SELECT = new byte[] {'s', 'e', 'l', 'e', 'c', 't'};
1275
- private static byte[] WITH = new byte[] {'w', 'i', 't', 'h'};
1276
- private static byte[] SHOW = new byte[] {'s', 'h', 'o', 'w'};
1951
+ protected static Boolean USE_RESULT;
1277
1952
 
1278
- private static boolean startsWithNoCaseCmp(ByteList bytelist, byte[] compare) {
1279
- int p = whitespace(bytelist.begin, bytelist);
1953
+ // AR-3.2 : initialize(columns, rows)
1954
+ // AR-4.0 : initialize(columns, rows, column_types = {})
1955
+ protected static Boolean INIT_COLUMN_TYPES = Boolean.FALSE;
1280
1956
 
1281
- // What the hell is this for?
1282
- if (bytelist.bytes[p] == '(') p = whitespace(p, bytelist);
1957
+ protected static Boolean FORCE_HASH_ROWS = Boolean.FALSE;
1283
1958
 
1284
- for (int i = 0; i < bytelist.realSize && i < compare.length; i++) {
1285
- if (Character.toLowerCase(bytelist.bytes[p + i]) != compare[i]) return false;
1959
+ private static volatile ResultHandler instance;
1960
+
1961
+ public static ResultHandler getInstance(final ThreadContext context) {
1962
+ if ( instance == null ) {
1963
+ synchronized(ResultHandler.class) {
1964
+ if ( instance == null ) { // fine to initialize twice
1965
+ setInstance( new ResultHandler(context) );
1966
+ }
1967
+ }
1968
+ }
1969
+ return instance;
1286
1970
  }
1287
1971
 
1288
- return true;
1289
- }
1972
+ protected static synchronized void setInstance(final ResultHandler instance) {
1973
+ ResultHandler.instance = instance;
1974
+ }
1290
1975
 
1291
- private TableNameComponents extractTableNameComponents(Connection connection, String defaultSchema, String tableName) throws SQLException {
1292
- String schemaName = null;
1976
+ protected ResultHandler(final ThreadContext context) {
1977
+ final Ruby runtime = context.getRuntime();
1978
+ final RubyClass result = getResult(runtime);
1979
+ USE_RESULT = result != null && result != runtime.getNilClass();
1980
+ }
1293
1981
 
1294
- final String[] name_parts = tableName.split("\\.");
1295
- if (name_parts.length > 3) {
1296
- throw new SQLException("Table name '" + tableName + "' should not contain more than 2 '.'");
1982
+ public IRubyObject mapRow(final ThreadContext context, final Ruby runtime,
1983
+ final ColumnData[] columns, final ResultSet resultSet,
1984
+ final RubyJdbcConnection connection) throws SQLException {
1985
+
1986
+ if ( USE_RESULT ) { // maps a AR::Result row
1987
+ final RubyArray row = runtime.newArray(columns.length);
1988
+
1989
+ for ( int i = 0; i < columns.length; i++ ) {
1990
+ final ColumnData column = columns[i];
1991
+ row.append( connection.jdbcToRuby(runtime, column.index, column.type, resultSet) );
1992
+ }
1993
+
1994
+ return row;
1995
+ }
1996
+ else {
1997
+ return mapRawRow(context, runtime, columns, resultSet, connection);
1998
+ }
1297
1999
  }
1298
2000
 
1299
- DatabaseMetaData metadata = connection.getMetaData();
1300
- String clzName = metadata.getClass().getName().toLowerCase();
1301
- boolean isPostgres = clzName.contains("postgresql");
2001
+ IRubyObject mapRawRow(final ThreadContext context, final Ruby runtime,
2002
+ final ColumnData[] columns, final ResultSet resultSet,
2003
+ final RubyJdbcConnection connection) throws SQLException {
2004
+
2005
+ final RubyHash row = RubyHash.newHash(runtime);
2006
+
2007
+ for ( int i = 0; i < columns.length; i++ ) {
2008
+ final ColumnData column = columns[i];
2009
+ row.op_aset( context, column.name, connection.jdbcToRuby(runtime, column.index, column.type, resultSet) );
2010
+ }
1302
2011
 
1303
- String catalog = connection.getCatalog();
1304
- if (name_parts.length == 2) {
1305
- schemaName = name_parts[0];
1306
- tableName = name_parts[1];
1307
- } else if (name_parts.length == 3) {
1308
- catalog = name_parts[0];
1309
- schemaName = name_parts[1];
1310
- tableName = name_parts[2];
2012
+ return row;
1311
2013
  }
2014
+
2015
+ public IRubyObject newResult(final ThreadContext context, final Ruby runtime,
2016
+ final ColumnData[] columns, final IRubyObject rows) { // rows array
2017
+ if ( USE_RESULT ) { // ActiveRecord::Result.new(columns, rows)
2018
+ final RubyClass result = getResult(runtime);
2019
+ return result.callMethod( context, "new", initArgs(runtime, columns, rows), Block.NULL_BLOCK );
2020
+ }
2021
+ return rows; // contains { 'col1' => 1, ... } Hash-es
2022
+ }
2023
+
2024
+ private IRubyObject[] initArgs(final Ruby runtime,
2025
+ final ColumnData[] columns, final IRubyObject rows) {
2026
+
2027
+ final IRubyObject[] args;
2028
+
2029
+ final RubyArray cols = runtime.newArray(columns.length);
2030
+
2031
+ if ( INIT_COLUMN_TYPES ) { // NOTE: NOT IMPLEMENTED
2032
+ for ( int i=0; i<columns.length; i++ ) {
2033
+ cols.add( columns[i].name );
2034
+ }
2035
+ args = new IRubyObject[] { cols, rows };
2036
+ }
2037
+ else {
2038
+ for ( int i=0; i<columns.length; i++ ) {
2039
+ cols.add( columns[i].name );
2040
+ }
2041
+ args = new IRubyObject[] { cols, rows };
2042
+ }
2043
+ return args;
2044
+ }
2045
+
2046
+ }
1312
2047
 
1313
- if (schemaName == null && defaultSchema != null) schemaName = defaultSchema;
2048
+
2049
+ protected static final class TableName {
2050
+
2051
+ public final String catalog, schema, name;
1314
2052
 
1315
- // The postgres JDBC driver will default to searching every schema if no
1316
- // schema search path is given. Default to the public schema instead.
1317
- if (schemaName == null && isPostgres) schemaName = "public";
1318
- if (schemaName != null) schemaName = caseConvertIdentifierForJdbc(metadata, schemaName);
1319
- tableName = caseConvertIdentifierForJdbc(metadata, tableName);
2053
+ public TableName(String catalog, String schema, String table) {
2054
+ this.catalog = catalog;
2055
+ this.schema = schema;
2056
+ this.name = table;
2057
+ }
2058
+
2059
+ }
2060
+
2061
+ /**
2062
+ * Extract the table name components for the given name e.g. "mycat.sys.entries"
2063
+ *
2064
+ * @param connection
2065
+ * @param catalog (optional) catalog to use if table name does not contain
2066
+ * the catalog prefix
2067
+ * @param schema (optional) schema to use if table name does not have one
2068
+ * @param tableName the table name
2069
+ * @return (parsed) table name
2070
+ *
2071
+ * @throws IllegalArgumentException for invalid table name format
2072
+ * @throws SQLException
2073
+ */
2074
+ protected TableName extractTableName(
2075
+ final Connection connection, String catalog, String schema,
2076
+ final String tableName) throws IllegalArgumentException, SQLException {
2077
+
2078
+ final String[] nameParts = tableName.split("\\.");
2079
+ if ( nameParts.length > 3 ) {
2080
+ throw new IllegalArgumentException("table name: " + tableName + " should not contain more than 2 '.'");
2081
+ }
2082
+
2083
+ String name = tableName;
2084
+
2085
+ if ( nameParts.length == 2 ) {
2086
+ schema = nameParts[0];
2087
+ name = nameParts[1];
2088
+ }
2089
+ else if ( nameParts.length == 3 ) {
2090
+ catalog = nameParts[0];
2091
+ schema = nameParts[1];
2092
+ name = nameParts[2];
2093
+ }
2094
+
2095
+ final DatabaseMetaData metaData = connection.getMetaData();
2096
+
2097
+ if (schema != null) {
2098
+ schema = caseConvertIdentifierForJdbc(metaData, schema);
2099
+ }
2100
+ name = caseConvertIdentifierForJdbc(metaData, name);
1320
2101
 
1321
- if (schemaName != null && !databaseSupportsSchemas()) { catalog = schemaName; }
2102
+ if (schema != null && ! databaseSupportsSchemas()) {
2103
+ catalog = schema;
2104
+ }
2105
+ if (catalog == null) catalog = connection.getCatalog();
1322
2106
 
1323
- return new TableNameComponents(catalog, schemaName, tableName);
2107
+ return new TableName(catalog, schema, name);
2108
+ }
2109
+
2110
+ /**
2111
+ * @deprecated use {@link #extractTableName(Connection, String, String, String)}
2112
+ */
2113
+ @Deprecated
2114
+ protected TableName extractTableName(
2115
+ final Connection connection, final String schema,
2116
+ final String tableName) throws IllegalArgumentException, SQLException {
2117
+ return extractTableName(connection, null, schema, tableName);
1324
2118
  }
1325
2119
 
1326
- public static class ColumnData {
1327
- public IRubyObject name;
1328
- public int index;
1329
- public int type;
2120
+ protected static final class ColumnData {
2121
+
2122
+ public final RubyString name;
2123
+ public final int index;
2124
+ public final int type;
1330
2125
 
1331
- public ColumnData(IRubyObject name, int type, int idx) {
2126
+ public ColumnData(RubyString name, int type, int idx) {
1332
2127
  this.name = name;
1333
2128
  this.type = type;
1334
2129
  this.index = idx;
1335
2130
  }
1336
-
1337
- public static ColumnData[] setup(Ruby runtime, DatabaseMetaData databaseMetadata,
1338
- ResultSetMetaData metadata, boolean downCase) throws SQLException {
1339
- int columnsCount = metadata.getColumnCount();
1340
- ColumnData[] columns = new ColumnData[columnsCount];
1341
-
1342
- for (int i = 1; i <= columnsCount; i++) { // metadata is one-based
1343
- String name;
1344
- if (downCase) {
1345
- name = metadata.getColumnLabel(i).toLowerCase();
1346
- } else {
1347
- name = RubyJdbcConnection.caseConvertIdentifierForRails(databaseMetadata, metadata.getColumnLabel(i));
1348
- }
1349
-
1350
- columns[i - 1] = new ColumnData(RubyString.newUnicodeString(runtime, name), metadata.getColumnType(i), i);
2131
+
2132
+ }
2133
+
2134
+ private static ColumnData[] setupColumns(
2135
+ final Ruby runtime,
2136
+ final DatabaseMetaData metaData,
2137
+ final ResultSetMetaData resultMetaData,
2138
+ final boolean downCase) throws SQLException {
2139
+
2140
+ final int columnCount = resultMetaData.getColumnCount();
2141
+ final ColumnData[] columns = new ColumnData[columnCount];
2142
+
2143
+ for ( int i = 1; i <= columnCount; i++ ) { // metadata is one-based
2144
+ final String name;
2145
+ if (downCase) {
2146
+ name = resultMetaData.getColumnLabel(i).toLowerCase();
2147
+ } else {
2148
+ name = caseConvertIdentifierForRails(metaData, resultMetaData.getColumnLabel(i));
1351
2149
  }
2150
+ final int columnType = resultMetaData.getColumnType(i);
2151
+ final RubyString columnName = RubyString.newUnicodeString(runtime, name);
2152
+ columns[i - 1] = new ColumnData(columnName, columnType, i);
2153
+ }
1352
2154
 
1353
- return columns;
2155
+ return columns;
2156
+ }
2157
+
2158
+ // JDBC API Helpers :
2159
+
2160
+ protected static void close(final Connection connection) {
2161
+ if ( connection != null ) {
2162
+ try { connection.close(); }
2163
+ catch (final Exception e) { /* NOOP */ }
1354
2164
  }
1355
2165
  }
1356
2166
 
1357
- private static class TableNameComponents {
1358
- private String catalog;
1359
- private String schema;
1360
- private String table;
2167
+ public static void close(final ResultSet resultSet) {
2168
+ if (resultSet != null) {
2169
+ try { resultSet.close(); }
2170
+ catch (final Exception e) { /* NOOP */ }
2171
+ }
2172
+ }
1361
2173
 
1362
- private TableNameComponents(String catalog, String schema, String table) {
1363
- this.catalog = catalog;
1364
- this.schema = schema;
1365
- this.table = table;
2174
+ public static void close(final Statement statement) {
2175
+ if (statement != null) {
2176
+ try { statement.close(); }
2177
+ catch (final Exception e) { /* NOOP */ }
1366
2178
  }
1367
2179
  }
2180
+
2181
+ // DEBUG-ing helpers :
2182
+
2183
+ private static boolean debug = Boolean.getBoolean("arjdbc.debug");
2184
+
2185
+ public static boolean isDebug() { return debug; }
2186
+
2187
+ public static void setDebug(boolean debug) {
2188
+ RubyJdbcConnection.debug = debug;
2189
+ }
2190
+
2191
+ public static void debugMessage(final ThreadContext context, final String msg) {
2192
+ if ( debug || context.runtime.isDebug() ) {
2193
+ context.runtime.getOut().println(msg);
2194
+ }
2195
+ }
2196
+
2197
+ protected static void debugErrorSQL(final ThreadContext context, final String sql) {
2198
+ if ( debug || context.runtime.isDebug() ) {
2199
+ context.runtime.getOut().println("Error SQL: " + sql);
2200
+ }
2201
+ }
2202
+
2203
+ public static void debugStackTrace(final ThreadContext context, final Throwable e) {
2204
+ if ( debug || context.runtime.isDebug() ) {
2205
+ e.printStackTrace(context.runtime.getOut());
2206
+ }
2207
+ }
2208
+
1368
2209
  }