activerecord-jdbc-alt-adapter 50.3.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.travis.yml +100 -0
  4. data/.yardopts +4 -0
  5. data/CONTRIBUTING.md +50 -0
  6. data/Gemfile +92 -0
  7. data/History.md +1191 -0
  8. data/LICENSE.txt +26 -0
  9. data/README.md +240 -0
  10. data/RUNNING_TESTS.md +127 -0
  11. data/Rakefile +336 -0
  12. data/Rakefile.jdbc +20 -0
  13. data/activerecord-jdbc-adapter.gemspec +55 -0
  14. data/activerecord-jdbc-alt-adapter.gemspec +56 -0
  15. data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
  16. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
  18. data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
  19. data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
  20. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
  21. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  22. data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
  23. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  24. data/lib/active_record/connection_adapters/mariadb_adapter.rb +1 -0
  25. data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
  26. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
  31. data/lib/activerecord-jdbc-adapter.rb +1 -0
  32. data/lib/arel/visitors/compat.rb +60 -0
  33. data/lib/arel/visitors/db2.rb +137 -0
  34. data/lib/arel/visitors/derby.rb +112 -0
  35. data/lib/arel/visitors/firebird.rb +79 -0
  36. data/lib/arel/visitors/h2.rb +25 -0
  37. data/lib/arel/visitors/hsqldb.rb +32 -0
  38. data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
  39. data/lib/arel/visitors/sql_server.rb +225 -0
  40. data/lib/arel/visitors/sql_server/ng42.rb +294 -0
  41. data/lib/arel/visitors/sqlserver.rb +214 -0
  42. data/lib/arjdbc.rb +19 -0
  43. data/lib/arjdbc/abstract/connection_management.rb +35 -0
  44. data/lib/arjdbc/abstract/core.rb +74 -0
  45. data/lib/arjdbc/abstract/database_statements.rb +64 -0
  46. data/lib/arjdbc/abstract/statement_cache.rb +58 -0
  47. data/lib/arjdbc/abstract/transaction_support.rb +86 -0
  48. data/lib/arjdbc/db2.rb +4 -0
  49. data/lib/arjdbc/db2/adapter.rb +789 -0
  50. data/lib/arjdbc/db2/as400.rb +130 -0
  51. data/lib/arjdbc/db2/column.rb +167 -0
  52. data/lib/arjdbc/db2/connection_methods.rb +44 -0
  53. data/lib/arjdbc/derby.rb +3 -0
  54. data/lib/arjdbc/derby/active_record_patch.rb +13 -0
  55. data/lib/arjdbc/derby/adapter.rb +540 -0
  56. data/lib/arjdbc/derby/connection_methods.rb +20 -0
  57. data/lib/arjdbc/derby/schema_creation.rb +15 -0
  58. data/lib/arjdbc/discover.rb +104 -0
  59. data/lib/arjdbc/firebird.rb +4 -0
  60. data/lib/arjdbc/firebird/adapter.rb +434 -0
  61. data/lib/arjdbc/firebird/connection_methods.rb +23 -0
  62. data/lib/arjdbc/h2.rb +3 -0
  63. data/lib/arjdbc/h2/adapter.rb +303 -0
  64. data/lib/arjdbc/h2/connection_methods.rb +27 -0
  65. data/lib/arjdbc/hsqldb.rb +3 -0
  66. data/lib/arjdbc/hsqldb/adapter.rb +297 -0
  67. data/lib/arjdbc/hsqldb/connection_methods.rb +28 -0
  68. data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
  69. data/lib/arjdbc/hsqldb/schema_creation.rb +11 -0
  70. data/lib/arjdbc/informix.rb +5 -0
  71. data/lib/arjdbc/informix/adapter.rb +162 -0
  72. data/lib/arjdbc/informix/connection_methods.rb +9 -0
  73. data/lib/arjdbc/jdbc.rb +59 -0
  74. data/lib/arjdbc/jdbc/adapter.rb +475 -0
  75. data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
  76. data/lib/arjdbc/jdbc/base_ext.rb +15 -0
  77. data/lib/arjdbc/jdbc/callbacks.rb +53 -0
  78. data/lib/arjdbc/jdbc/column.rb +97 -0
  79. data/lib/arjdbc/jdbc/connection.rb +14 -0
  80. data/lib/arjdbc/jdbc/connection_methods.rb +37 -0
  81. data/lib/arjdbc/jdbc/error.rb +65 -0
  82. data/lib/arjdbc/jdbc/extension.rb +59 -0
  83. data/lib/arjdbc/jdbc/java.rb +13 -0
  84. data/lib/arjdbc/jdbc/railtie.rb +2 -0
  85. data/lib/arjdbc/jdbc/rake_tasks.rb +3 -0
  86. data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -0
  87. data/lib/arjdbc/jdbc/type_cast.rb +166 -0
  88. data/lib/arjdbc/jdbc/type_converter.rb +142 -0
  89. data/lib/arjdbc/mssql.rb +7 -0
  90. data/lib/arjdbc/mssql/adapter.rb +384 -0
  91. data/lib/arjdbc/mssql/column.rb +29 -0
  92. data/lib/arjdbc/mssql/connection_methods.rb +79 -0
  93. data/lib/arjdbc/mssql/database_statements.rb +134 -0
  94. data/lib/arjdbc/mssql/errors.rb +6 -0
  95. data/lib/arjdbc/mssql/explain_support.rb +129 -0
  96. data/lib/arjdbc/mssql/extensions.rb +36 -0
  97. data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
  98. data/lib/arjdbc/mssql/lock_methods.rb +77 -0
  99. data/lib/arjdbc/mssql/old_adapter.rb +804 -0
  100. data/lib/arjdbc/mssql/old_column.rb +200 -0
  101. data/lib/arjdbc/mssql/quoting.rb +101 -0
  102. data/lib/arjdbc/mssql/schema_creation.rb +31 -0
  103. data/lib/arjdbc/mssql/schema_definitions.rb +74 -0
  104. data/lib/arjdbc/mssql/schema_statements.rb +329 -0
  105. data/lib/arjdbc/mssql/transaction.rb +69 -0
  106. data/lib/arjdbc/mssql/types.rb +52 -0
  107. data/lib/arjdbc/mssql/types/binary_types.rb +33 -0
  108. data/lib/arjdbc/mssql/types/date_and_time_types.rb +134 -0
  109. data/lib/arjdbc/mssql/types/deprecated_types.rb +40 -0
  110. data/lib/arjdbc/mssql/types/numeric_types.rb +71 -0
  111. data/lib/arjdbc/mssql/types/string_types.rb +56 -0
  112. data/lib/arjdbc/mssql/utils.rb +66 -0
  113. data/lib/arjdbc/mysql.rb +3 -0
  114. data/lib/arjdbc/mysql/adapter.rb +140 -0
  115. data/lib/arjdbc/mysql/connection_methods.rb +166 -0
  116. data/lib/arjdbc/oracle/adapter.rb +863 -0
  117. data/lib/arjdbc/postgresql.rb +3 -0
  118. data/lib/arjdbc/postgresql/adapter.rb +687 -0
  119. data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
  120. data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
  121. data/lib/arjdbc/postgresql/base/array_parser.rb +95 -0
  122. data/lib/arjdbc/postgresql/base/pgconn.rb +11 -0
  123. data/lib/arjdbc/postgresql/column.rb +51 -0
  124. data/lib/arjdbc/postgresql/connection_methods.rb +67 -0
  125. data/lib/arjdbc/postgresql/name.rb +24 -0
  126. data/lib/arjdbc/postgresql/oid_types.rb +266 -0
  127. data/lib/arjdbc/railtie.rb +11 -0
  128. data/lib/arjdbc/sqlite3.rb +3 -0
  129. data/lib/arjdbc/sqlite3/adapter.rb +678 -0
  130. data/lib/arjdbc/sqlite3/connection_methods.rb +59 -0
  131. data/lib/arjdbc/sybase.rb +2 -0
  132. data/lib/arjdbc/sybase/adapter.rb +47 -0
  133. data/lib/arjdbc/tasks.rb +13 -0
  134. data/lib/arjdbc/tasks/database_tasks.rb +31 -0
  135. data/lib/arjdbc/tasks/databases.rake +48 -0
  136. data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
  137. data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
  138. data/lib/arjdbc/tasks/h2_database_tasks.rb +31 -0
  139. data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
  140. data/lib/arjdbc/tasks/jdbc_database_tasks.rb +169 -0
  141. data/lib/arjdbc/tasks/mssql_database_tasks.rb +46 -0
  142. data/lib/arjdbc/util/quoted_cache.rb +60 -0
  143. data/lib/arjdbc/util/serialized_attributes.rb +98 -0
  144. data/lib/arjdbc/util/table_copier.rb +110 -0
  145. data/lib/arjdbc/version.rb +3 -0
  146. data/lib/generators/jdbc/USAGE +9 -0
  147. data/lib/generators/jdbc/jdbc_generator.rb +17 -0
  148. data/lib/jdbc_adapter.rb +2 -0
  149. data/lib/jdbc_adapter/rake_tasks.rb +4 -0
  150. data/lib/jdbc_adapter/version.rb +4 -0
  151. data/pom.xml +114 -0
  152. data/rails_generators/jdbc_generator.rb +15 -0
  153. data/rails_generators/templates/config/initializers/jdbc.rb +10 -0
  154. data/rails_generators/templates/lib/tasks/jdbc.rake +11 -0
  155. data/rakelib/01-tomcat.rake +51 -0
  156. data/rakelib/02-test.rake +132 -0
  157. data/rakelib/bundler_ext.rb +11 -0
  158. data/rakelib/db.rake +75 -0
  159. data/rakelib/rails.rake +223 -0
  160. data/src/java/arjdbc/ArJdbcModule.java +276 -0
  161. data/src/java/arjdbc/db2/DB2Module.java +76 -0
  162. data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +126 -0
  163. data/src/java/arjdbc/derby/DerbyModule.java +178 -0
  164. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +152 -0
  165. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +174 -0
  166. data/src/java/arjdbc/h2/H2Module.java +50 -0
  167. data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +85 -0
  168. data/src/java/arjdbc/hsqldb/HSQLDBModule.java +73 -0
  169. data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +75 -0
  170. data/src/java/arjdbc/jdbc/AdapterJavaService.java +43 -0
  171. data/src/java/arjdbc/jdbc/Callable.java +44 -0
  172. data/src/java/arjdbc/jdbc/ConnectionFactory.java +45 -0
  173. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +156 -0
  174. data/src/java/arjdbc/jdbc/DriverConnectionFactory.java +63 -0
  175. data/src/java/arjdbc/jdbc/DriverWrapper.java +119 -0
  176. data/src/java/arjdbc/jdbc/JdbcResult.java +130 -0
  177. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
  178. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3979 -0
  179. data/src/java/arjdbc/mssql/MSSQLModule.java +90 -0
  180. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +508 -0
  181. data/src/java/arjdbc/mysql/MySQLModule.java +152 -0
  182. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +294 -0
  183. data/src/java/arjdbc/oracle/OracleModule.java +80 -0
  184. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +455 -0
  185. data/src/java/arjdbc/postgresql/ByteaUtils.java +157 -0
  186. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
  187. data/src/java/arjdbc/postgresql/PostgreSQLModule.java +77 -0
  188. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +192 -0
  189. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +948 -0
  190. data/src/java/arjdbc/sqlite3/SQLite3Module.java +73 -0
  191. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +525 -0
  192. data/src/java/arjdbc/util/CallResultSet.java +826 -0
  193. data/src/java/arjdbc/util/DateTimeUtils.java +699 -0
  194. data/src/java/arjdbc/util/ObjectSupport.java +65 -0
  195. data/src/java/arjdbc/util/QuotingUtils.java +137 -0
  196. data/src/java/arjdbc/util/StringCache.java +63 -0
  197. data/src/java/arjdbc/util/StringHelper.java +145 -0
  198. metadata +269 -0
@@ -0,0 +1,130 @@
1
+ package arjdbc.jdbc;
2
+
3
+ import java.sql.ResultSet;
4
+ import java.sql.ResultSetMetaData;
5
+ import java.sql.SQLException;
6
+
7
+ import org.jruby.Ruby;
8
+ import org.jruby.RubyArray;
9
+ import org.jruby.RubyClass;
10
+ import org.jruby.RubyHash;
11
+ import org.jruby.RubyObject;
12
+ import org.jruby.RubyString;
13
+ import org.jruby.runtime.Block;
14
+ import org.jruby.runtime.ThreadContext;
15
+ import org.jruby.runtime.builtin.IRubyObject;
16
+
17
+ /**
18
+ * This is a base Result class to be returned as the "raw" result.
19
+ * It should be overridden for specific adapters to manage type maps
20
+ * and provide any additional methods needed.
21
+ */
22
+ public class JdbcResult extends RubyObject {
23
+ // Should these be private with accessors?
24
+ protected final RubyArray values;
25
+ protected RubyHash[] tuples;
26
+
27
+ protected final int[] columnTypes;
28
+ protected RubyString[] columnNames;
29
+ protected final RubyJdbcConnection connection;
30
+
31
+ protected JdbcResult(ThreadContext context, RubyClass clazz, RubyJdbcConnection connection, ResultSet resultSet) throws SQLException {
32
+ super(context.runtime, clazz);
33
+
34
+ values = context.runtime.newArray();
35
+ this.connection = connection;
36
+
37
+ final ResultSetMetaData resultMetaData = resultSet.getMetaData();
38
+ final int columnCount = resultMetaData.getColumnCount();
39
+ // FIXME: if we support MSSQL we may need to change how we deal with omitting elements
40
+ columnNames = new RubyString[columnCount];
41
+ columnTypes = new int[columnCount];
42
+ extractColumnInfo(context, resultMetaData);
43
+ processResultSet(context, resultSet);
44
+ }
45
+
46
+ /**
47
+ * Builds a type map for creating the AR::Result, most adapters don't need it
48
+ * @param context which thread this is running on.
49
+ * @return RubyNil
50
+ * @throws SQLException postgres result can throw an exception
51
+ */
52
+ protected IRubyObject columnTypeMap(ThreadContext context) throws SQLException {
53
+ return context.nil;
54
+ }
55
+
56
+ /**
57
+ * Build an array of column types
58
+ * @param resultMetaData metadata from a ResultSet to determine column information from
59
+ * @throws SQLException throws error!
60
+ */
61
+ private void extractColumnInfo(ThreadContext context, ResultSetMetaData resultMetaData) throws SQLException {
62
+ final int columnCount = resultMetaData.getColumnCount();
63
+
64
+ for (int i = 1; i <= columnCount; i++) { // metadata is one-based
65
+ // This appears to not be used by Postgres, MySQL, or SQLite so leaving it off for now
66
+ //name = caseConvertIdentifierForRails(connection, name);
67
+ columnNames[i - 1] = RubyJdbcConnection.STRING_CACHE.get(context, resultMetaData.getColumnLabel(i));
68
+ columnTypes[i - 1] = resultMetaData.getColumnType(i);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @return an array with the column names as Ruby strings
74
+ */
75
+ protected RubyString[] getColumnNames() {
76
+ return columnNames;
77
+ }
78
+
79
+ /**
80
+ * Builds an array of hashes with column names to column values
81
+ * @param context current thread context
82
+ */
83
+ protected void populateTuples(final ThreadContext context) {
84
+ int columnCount = columnNames.length;
85
+ tuples = new RubyHash[values.size()];
86
+
87
+ for (int i = 0; i < tuples.length; i++) {
88
+ RubyArray currentRow = (RubyArray) values.eltInternal(i);
89
+ RubyHash hash = RubyHash.newHash(context.runtime);
90
+ for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
91
+ hash.fastASet(columnNames[columnIndex], currentRow.eltInternal(columnIndex));
92
+ }
93
+ tuples[i] = hash;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Does the heavy lifting of turning the JDBC objects into Ruby objects
99
+ * @param context current thread context
100
+ * @param resultSet the set of results we are converting
101
+ * @throws SQLException throws!
102
+ */
103
+ private void processResultSet(final ThreadContext context, final ResultSet resultSet) throws SQLException {
104
+ Ruby runtime = context.runtime;
105
+ int columnCount = columnNames.length;
106
+
107
+ while (resultSet.next()) {
108
+ final IRubyObject[] row = new IRubyObject[columnCount];
109
+
110
+ for (int i = 0; i < columnCount; i++) {
111
+ row[i] = connection.jdbcToRuby(context, runtime, i + 1, columnTypes[i], resultSet); // Result Set is 1 based
112
+ }
113
+
114
+ values.append(RubyArray.newArrayNoCopy(context.runtime, row));
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Creates an <code>ActiveRecord::Result</code> with the data from this result
120
+ * @param context current thread context
121
+ * @return ActiveRecord::Result object with the data from this result set
122
+ * @throws SQLException can be caused by postgres generating its type map
123
+ */
124
+ public IRubyObject toARResult(final ThreadContext context) throws SQLException {
125
+ final RubyClass Result = RubyJdbcConnection.getResult(context.runtime);
126
+ // FIXME: Is this broken? no copy of an array AR::Result can modify? or should it be frozen?
127
+ final RubyArray rubyColumnNames = RubyArray.newArrayNoCopy(context.runtime, getColumnNames());
128
+ return Result.newInstance(context, rubyColumnNames, values, columnTypeMap(context), Block.NULL_BLOCK);
129
+ }
130
+ }
@@ -0,0 +1,61 @@
1
+ /***** BEGIN LICENSE BLOCK *****
2
+ * Copyright (c) 2012-2014 Karol Bucek <self@kares.org>
3
+ * Copyright (c) 2006-2010 Nick Sieger <nick@nicksieger.com>
4
+ * Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining
7
+ * a copy of this software and associated documentation files (the
8
+ * "Software"), to deal in the Software without restriction, including
9
+ * without limitation the rights to use, copy, modify, merge, publish,
10
+ * distribute, sublicense, and/or sell copies of the Software, and to
11
+ * permit persons to whom the Software is furnished to do so, subject to
12
+ * the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be
15
+ * included in all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ ***** END LICENSE BLOCK *****/
25
+
26
+ package arjdbc.jdbc;
27
+
28
+ import java.sql.Connection;
29
+ import java.sql.SQLException;
30
+
31
+ import org.jruby.RubyObject;
32
+ import org.jruby.RubyString;
33
+ import org.jruby.runtime.ThreadContext;
34
+ import org.jruby.runtime.builtin.IRubyObject;
35
+
36
+ // @legacy ActiveRecord::ConnectionAdapters::JdbcDriver
37
+ class RubyConnectionFactory implements ConnectionFactory {
38
+
39
+ private final IRubyObject driver;
40
+ final RubyString url;
41
+ final IRubyObject username, password; // null allowed
42
+
43
+ private final RubyObject contextProvider;
44
+
45
+ public RubyConnectionFactory(final IRubyObject driver, final RubyString url,
46
+ final IRubyObject username, final IRubyObject password) {
47
+ this.driver = driver; this.url = url;
48
+ this.username = username; this.password = password;
49
+ contextProvider = (RubyObject) driver;
50
+ }
51
+
52
+ @Override
53
+ public Connection newConnection() throws SQLException {
54
+ final ThreadContext context = contextProvider.getRuntime().getCurrentContext();
55
+ final IRubyObject connection = driver.callMethod(context, "connection", new IRubyObject[] { url, username, password });
56
+ return (Connection) connection.toJava(Connection.class);
57
+ }
58
+
59
+ IRubyObject getDriver() { return driver; } /* for tests */
60
+
61
+ }
@@ -0,0 +1,3979 @@
1
+ /***** BEGIN LICENSE BLOCK *****
2
+ * Copyright (c) 2012-2013 Karol Bucek <self@kares.org>
3
+ * Copyright (c) 2006-2011 Nick Sieger <nick@nicksieger.com>
4
+ * Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
5
+ * Copyright (c) 2008-2009 Thomas E Enebo <enebo@acm.org>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining
8
+ * a copy of this software and associated documentation files (the
9
+ * "Software"), to deal in the Software without restriction, including
10
+ * without limitation the rights to use, copy, modify, merge, publish,
11
+ * distribute, sublicense, and/or sell copies of the Software, and to
12
+ * permit persons to whom the Software is furnished to do so, subject to
13
+ * the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be
16
+ * included in all copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ ***** END LICENSE BLOCK *****/
26
+ package arjdbc.jdbc;
27
+
28
+ import java.io.IOException;
29
+ import java.io.InputStream;
30
+ import java.io.InputStreamReader;
31
+ import java.io.PrintStream;
32
+ import java.io.Reader;
33
+ import java.io.StringReader;
34
+ import java.math.BigDecimal;
35
+ import java.math.BigInteger;
36
+ import java.sql.Array;
37
+ import java.sql.Connection;
38
+ import java.sql.DatabaseMetaData;
39
+ import java.sql.PreparedStatement;
40
+ import java.sql.ResultSet;
41
+ import java.sql.ResultSetMetaData;
42
+ import java.sql.SQLException;
43
+ import java.sql.SQLXML;
44
+ import java.sql.Statement;
45
+ import java.sql.Date;
46
+ import java.sql.SQLFeatureNotSupportedException;
47
+ import java.sql.SQLRecoverableException;
48
+ import java.sql.SQLTransientException;
49
+ import java.sql.Savepoint;
50
+ import java.sql.Time;
51
+ import java.sql.Timestamp;
52
+ import java.sql.Types;
53
+ import java.util.ArrayList;
54
+ import java.util.Calendar;
55
+ import java.util.Collection;
56
+ import java.util.GregorianCalendar;
57
+ import java.util.HashMap;
58
+ import java.util.LinkedHashMap;
59
+ import java.util.List;
60
+ import java.util.Locale;
61
+ import java.util.Map;
62
+ import java.util.Properties;
63
+ import java.util.TimeZone;
64
+
65
+ import arjdbc.util.StringHelper;
66
+ import org.joda.time.DateTime;
67
+ import org.joda.time.DateTimeZone;
68
+ import org.jruby.Ruby;
69
+ import org.jruby.RubyArray;
70
+ import org.jruby.RubyBasicObject;
71
+ import org.jruby.RubyBignum;
72
+ import org.jruby.RubyBoolean;
73
+ import org.jruby.RubyClass;
74
+ import org.jruby.RubyException;
75
+ import org.jruby.RubyFixnum;
76
+ import org.jruby.RubyFloat;
77
+ import org.jruby.RubyHash;
78
+ import org.jruby.RubyIO;
79
+ import org.jruby.RubyInteger;
80
+ import org.jruby.RubyModule;
81
+ import org.jruby.RubyNumeric;
82
+ import org.jruby.RubyObject;
83
+ import org.jruby.RubyString;
84
+ import org.jruby.RubySymbol;
85
+ import org.jruby.RubyTime;
86
+ import org.jruby.anno.JRubyMethod;
87
+ import org.jruby.exceptions.RaiseException;
88
+ import org.jruby.ext.bigdecimal.RubyBigDecimal;
89
+ import org.jruby.javasupport.JavaEmbedUtils;
90
+ import org.jruby.javasupport.JavaUtil;
91
+ import org.jruby.runtime.Block;
92
+ import org.jruby.runtime.ObjectAllocator;
93
+ import org.jruby.runtime.ThreadContext;
94
+ import org.jruby.runtime.Visibility;
95
+ import org.jruby.runtime.builtin.IRubyObject;
96
+ import org.jruby.runtime.builtin.Variable;
97
+ import org.jruby.runtime.callsite.CachingCallSite;
98
+ import org.jruby.runtime.callsite.FunctionalCachingCallSite;
99
+ import org.jruby.runtime.component.VariableEntry;
100
+ import org.jruby.util.ByteList;
101
+ import org.jruby.util.SafePropertyAccessor;
102
+ import org.jruby.util.TypeConverter;
103
+
104
+ import arjdbc.util.DateTimeUtils;
105
+ import arjdbc.util.ObjectSupport;
106
+ import arjdbc.util.StringCache;
107
+
108
+ import static arjdbc.jdbc.DataSourceConnectionFactory.*;
109
+ import static arjdbc.util.StringHelper.*;
110
+ import static org.jruby.RubyTime.getLocalTimeZone;
111
+
112
+
113
+ /**
114
+ * Most of our ActiveRecord::ConnectionAdapters::JdbcConnection implementation.
115
+ */
116
+ public class RubyJdbcConnection extends RubyObject {
117
+
118
+ private static final long serialVersionUID = 3803945791317576818L;
119
+
120
+ private static final String[] TABLE_TYPE = new String[] { "TABLE" };
121
+ private static final String[] TABLE_TYPES = new String[] { "TABLE", "VIEW", "SYNONYM" };
122
+
123
+ private ConnectionFactory connectionFactory;
124
+ private IRubyObject config;
125
+ private IRubyObject adapter; // the AbstractAdapter instance we belong to
126
+ private volatile boolean connected = true;
127
+
128
+ private boolean lazy = false; // final once set on initialize
129
+ private boolean jndi; // final once set on initialize
130
+ private boolean configureConnection = true; // final once initialized
131
+ private int fetchSize = 0; // 0 = JDBC default
132
+
133
+ protected RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
134
+ super(runtime, metaClass);
135
+ }
136
+
137
+ private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
138
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
139
+ return new RubyJdbcConnection(runtime, klass);
140
+ }
141
+ };
142
+
143
+ public static RubyClass createJdbcConnectionClass(final Ruby runtime) {
144
+ final RubyClass JdbcConnection = getConnectionAdapters(runtime).
145
+ defineClassUnder("JdbcConnection", runtime.getObject(), ALLOCATOR);
146
+ JdbcConnection.defineAnnotatedMethods(RubyJdbcConnection.class);
147
+ return JdbcConnection;
148
+ }
149
+
150
+ @Deprecated
151
+ public static RubyClass getJdbcConnectionClass(final Ruby runtime) {
152
+ return getConnectionAdapters(runtime).getClass("JdbcConnection");
153
+ }
154
+
155
+ public static RubyClass getJdbcConnection(final Ruby runtime) {
156
+ return (RubyClass) getConnectionAdapters(runtime).getConstantAt("JdbcConnection");
157
+ }
158
+
159
+ protected static RubyModule ActiveRecord(ThreadContext context) {
160
+ return context.runtime.getModule("ActiveRecord");
161
+ }
162
+
163
+ public static RubyClass getBase(final Ruby runtime) {
164
+ return (RubyClass) runtime.getModule("ActiveRecord").getConstantAt("Base");
165
+ }
166
+
167
+ /**
168
+ * @param runtime
169
+ * @return <code>ActiveRecord::Result</code>
170
+ */
171
+ public static RubyClass getResult(final Ruby runtime) {
172
+ return (RubyClass) runtime.getModule("ActiveRecord").getConstantAt("Result");
173
+ }
174
+
175
+ /**
176
+ * @param runtime
177
+ * @return <code>ActiveRecord::ConnectionAdapters</code>
178
+ */
179
+ public static RubyModule getConnectionAdapters(final Ruby runtime) {
180
+ return (RubyModule) runtime.getModule("ActiveRecord").getConstantAt("ConnectionAdapters");
181
+ }
182
+
183
+ /**
184
+ * @param runtime
185
+ * @return <code>ActiveRecord::ConnectionAdapters::IndexDefinition</code>
186
+ */
187
+ protected static RubyClass getIndexDefinition(final Ruby runtime) {
188
+ return getConnectionAdapters(runtime).getClass("IndexDefinition");
189
+ }
190
+
191
+ /**
192
+ * @param runtime
193
+ * @return <code>ActiveRecord::ConnectionAdapters::ForeignKeyDefinition</code>
194
+ * @note only since AR 4.2
195
+ */
196
+ protected static RubyClass getForeignKeyDefinition(final Ruby runtime) {
197
+ return getConnectionAdapters(runtime).getClass("ForeignKeyDefinition");
198
+ }
199
+
200
+ /**
201
+ * @param runtime
202
+ * @return <code>ActiveRecord::JDBCError</code>
203
+ */
204
+ protected static RubyClass getJDBCError(final Ruby runtime) {
205
+ return runtime.getModule("ActiveRecord").getClass("JDBCError");
206
+ }
207
+
208
+ /**
209
+ * @param runtime
210
+ * @return <code>ActiveRecord::ConnectionNotEstablished</code>
211
+ */
212
+ protected static RubyClass getConnectionNotEstablished(final Ruby runtime) {
213
+ return runtime.getModule("ActiveRecord").getClass("ConnectionNotEstablished");
214
+ }
215
+
216
+ /**
217
+ * @param runtime
218
+ * @return <code>ActiveRecord::NoDatabaseError</code>
219
+ */
220
+ protected static RubyClass getNoDatabaseError(final Ruby runtime) {
221
+ return runtime.getModule("ActiveRecord").getClass("NoDatabaseError");
222
+ }
223
+
224
+ /**
225
+ * @param runtime
226
+ * @return <code>ActiveRecord::TransactionIsolationError</code>
227
+ */
228
+ protected static RubyClass getTransactionIsolationError(final Ruby runtime) {
229
+ return (RubyClass) runtime.getModule("ActiveRecord").getConstant("TransactionIsolationError");
230
+ }
231
+
232
+ @JRubyMethod(name = "transaction_isolation", alias = "get_transaction_isolation")
233
+ public IRubyObject get_transaction_isolation(final ThreadContext context) {
234
+ return withConnection(context, new Callable<IRubyObject>() {
235
+ public IRubyObject call(final Connection connection) throws SQLException {
236
+ final int level = connection.getTransactionIsolation();
237
+ final String isolationSymbol = formatTransactionIsolationLevel(level);
238
+ if ( isolationSymbol == null ) return context.nil;
239
+ return context.runtime.newSymbol(isolationSymbol);
240
+ }
241
+ });
242
+ }
243
+
244
+ @JRubyMethod(name = "transaction_isolation=", alias = "set_transaction_isolation")
245
+ public IRubyObject set_transaction_isolation(final ThreadContext context, final IRubyObject isolation) {
246
+ return withConnection(context, new Callable<IRubyObject>() {
247
+ public IRubyObject call(final Connection connection) throws SQLException {
248
+ final int level;
249
+ if ( isolation.isNil() ) {
250
+ level = connection.getMetaData().getDefaultTransactionIsolation();
251
+ }
252
+ else {
253
+ level = mapTransactionIsolationLevel(isolation);
254
+ }
255
+
256
+ connection.setTransactionIsolation(level);
257
+
258
+ final String isolationSymbol = formatTransactionIsolationLevel(level);
259
+ if ( isolationSymbol == null ) return context.nil;
260
+ return context.runtime.newSymbol(isolationSymbol);
261
+ }
262
+ });
263
+ }
264
+
265
+ public static String formatTransactionIsolationLevel(final int level) {
266
+ if ( level == Connection.TRANSACTION_READ_UNCOMMITTED ) return "read_uncommitted"; // 1
267
+ if ( level == Connection.TRANSACTION_READ_COMMITTED ) return "read_committed"; // 2
268
+ if ( level == Connection.TRANSACTION_REPEATABLE_READ ) return "repeatable_read"; // 4
269
+ if ( level == Connection.TRANSACTION_SERIALIZABLE ) return "serializable"; // 8
270
+ if ( level == 0 ) return null;
271
+ throw new IllegalArgumentException("unexpected transaction isolation level: " + level);
272
+ }
273
+
274
+ /*
275
+ def transaction_isolation_levels
276
+ {
277
+ read_uncommitted: "READ UNCOMMITTED",
278
+ read_committed: "READ COMMITTED",
279
+ repeatable_read: "REPEATABLE READ",
280
+ serializable: "SERIALIZABLE"
281
+ }
282
+ end
283
+ */
284
+
285
+ public static int mapTransactionIsolationLevel(final IRubyObject isolation) {
286
+ final Object isolationString;
287
+ if ( isolation instanceof RubySymbol ) {
288
+ isolationString = ((RubySymbol) isolation).asJavaString(); // RubySymbol (interned)
289
+ }
290
+ else {
291
+ isolationString = isolation.asString().toString().toLowerCase(Locale.ENGLISH).intern();
292
+ }
293
+
294
+ if ( isolationString == "read_uncommitted" ) return Connection.TRANSACTION_READ_UNCOMMITTED; // 1
295
+ if ( isolationString == "read_committed" ) return Connection.TRANSACTION_READ_COMMITTED; // 2
296
+ if ( isolationString == "repeatable_read" ) return Connection.TRANSACTION_REPEATABLE_READ; // 4
297
+ if ( isolationString == "serializable" ) return Connection.TRANSACTION_SERIALIZABLE; // 8
298
+
299
+ throw new IllegalArgumentException(
300
+ "unexpected isolation level: " + isolation + " (" + isolationString + ")"
301
+ );
302
+ }
303
+
304
+ @JRubyMethod(name = "supports_transaction_isolation?", optional = 1)
305
+ public IRubyObject supports_transaction_isolation_p(final ThreadContext context,
306
+ final IRubyObject[] args) throws SQLException {
307
+ final IRubyObject isolation = args.length > 0 ? args[0] : null;
308
+
309
+ return withConnection(context, new Callable<IRubyObject>() {
310
+ public IRubyObject call(final Connection connection) throws SQLException {
311
+ final DatabaseMetaData metaData = connection.getMetaData();
312
+ final boolean supported;
313
+ if ( isolation != null && ! isolation.isNil() ) {
314
+ final int level = mapTransactionIsolationLevel(isolation);
315
+ supported = metaData.supportsTransactionIsolationLevel(level);
316
+ }
317
+ else {
318
+ final int level = metaData.getDefaultTransactionIsolation();
319
+ supported = level > Connection.TRANSACTION_NONE; // > 0
320
+ }
321
+ return context.runtime.newBoolean(supported);
322
+ }
323
+ });
324
+ }
325
+
326
+ @JRubyMethod(name = {"begin", "transaction"}, required = 1) // optional isolation argument for AR-4.0
327
+ public IRubyObject begin(final ThreadContext context, final IRubyObject isolation) {
328
+ try { // handleException == false so we can handle setTXIsolation
329
+ return withConnection(context, false, new Callable<IRubyObject>() {
330
+ public IRubyObject call(final Connection connection) throws SQLException {
331
+ return beginTransaction(context, connection, isolation == context.nil ? null : isolation);
332
+ }
333
+ });
334
+ } catch (SQLException e) {
335
+ return handleException(context, e);
336
+ }
337
+ }
338
+
339
+ @JRubyMethod(name = {"begin", "transaction"}) // optional isolation argument for AR-4.0
340
+ public IRubyObject begin(final ThreadContext context) {
341
+ try { // handleException == false so we can handle setTXIsolation
342
+ return withConnection(context, false, new Callable<IRubyObject>() {
343
+ public IRubyObject call(final Connection connection) throws SQLException {
344
+ return beginTransaction(context, connection, null);
345
+ }
346
+ });
347
+ } catch (SQLException e) {
348
+ return handleException(context, e);
349
+ }
350
+ }
351
+
352
+ protected IRubyObject beginTransaction(final ThreadContext context, final Connection connection,
353
+ final IRubyObject isolation) throws SQLException {
354
+ if ( isolation != null ) {
355
+ setTransactionIsolation(context, connection, isolation);
356
+ }
357
+ if ( connection.getAutoCommit() ) connection.setAutoCommit(false);
358
+ return context.nil;
359
+ }
360
+
361
+ protected void setTransactionIsolation(final ThreadContext context, final Connection connection,
362
+ final IRubyObject isolation) throws SQLException {
363
+ final int level = mapTransactionIsolationLevel(isolation);
364
+ try {
365
+ connection.setTransactionIsolation(level);
366
+ }
367
+ catch (SQLException e) {
368
+ RubyClass txError = ActiveRecord(context).getClass("TransactionIsolationError");
369
+ if ( txError != null ) throw wrapException(context, txError, e);
370
+ throw e; // let it roll - will be wrapped into a JDBCError (non 4.0)
371
+ }
372
+ }
373
+
374
+ @JRubyMethod(name = "commit")
375
+ public IRubyObject commit(final ThreadContext context) {
376
+ final Connection connection = getConnection(true);
377
+ try {
378
+ if ( ! connection.getAutoCommit() ) {
379
+ try {
380
+ connection.commit();
381
+ resetSavepoints(context); // if any
382
+ return context.runtime.newBoolean(true);
383
+ }
384
+ finally {
385
+ connection.setAutoCommit(true);
386
+ }
387
+ }
388
+ return context.nil;
389
+ }
390
+ catch (SQLException e) {
391
+ return handleException(context, e);
392
+ }
393
+ }
394
+
395
+ @JRubyMethod(name = "rollback")
396
+ public IRubyObject rollback(final ThreadContext context) {
397
+ final Connection connection = getConnection(true);
398
+ try {
399
+ if ( ! connection.getAutoCommit() ) {
400
+ try {
401
+ connection.rollback();
402
+ resetSavepoints(context); // if any
403
+ return context.runtime.getTrue();
404
+ } finally {
405
+ connection.setAutoCommit(true);
406
+ }
407
+ }
408
+ return context.nil;
409
+ }
410
+ catch (SQLException e) {
411
+ return handleException(context, e);
412
+ }
413
+ }
414
+
415
+ @JRubyMethod(name = "supports_savepoints?")
416
+ public IRubyObject supports_savepoints_p(final ThreadContext context) throws SQLException {
417
+ return withConnection(context, new Callable<IRubyObject>() {
418
+ public IRubyObject call(final Connection connection) throws SQLException {
419
+ final DatabaseMetaData metaData = connection.getMetaData();
420
+ return context.runtime.newBoolean( metaData.supportsSavepoints() );
421
+ }
422
+ });
423
+ }
424
+
425
+ @JRubyMethod(name = "create_savepoint") // not used
426
+ public IRubyObject create_savepoint(final ThreadContext context) {
427
+ return create_savepoint(context, context.nil);
428
+ }
429
+
430
+ @JRubyMethod(name = "create_savepoint", required = 1)
431
+ public IRubyObject create_savepoint(final ThreadContext context, IRubyObject name) {
432
+ final Connection connection = getConnection(true);
433
+ try {
434
+ connection.setAutoCommit(false);
435
+
436
+ final Savepoint savepoint ;
437
+ // NOTE: this will auto-start a DB transaction even invoked outside
438
+ // of a AR (Ruby) transaction (`transaction { ... create_savepoint }`)
439
+ // it would be nice if AR knew about this TX although that's kind of
440
+ // "really advanced" functionality - likely not to be implemented ...
441
+ if ( name != context.nil ) {
442
+ savepoint = connection.setSavepoint(name.toString());
443
+ }
444
+ else {
445
+ savepoint = connection.setSavepoint();
446
+ name = RubyString.newString( context.runtime, Integer.toString( savepoint.getSavepointId() ));
447
+ }
448
+ getSavepoints(context).put(name, savepoint);
449
+
450
+ return name;
451
+ }
452
+ catch (SQLException e) {
453
+ return handleException(context, e);
454
+ }
455
+ }
456
+
457
+ @JRubyMethod(name = "rollback_savepoint", required = 1)
458
+ public IRubyObject rollback_savepoint(final ThreadContext context, final IRubyObject name) {
459
+ if (name == context.nil) throw context.runtime.newArgumentError("nil savepoint name given");
460
+
461
+ final Connection connection = getConnection(true);
462
+ try {
463
+ Savepoint savepoint = getSavepoints(context).get(name);
464
+ if ( savepoint == null ) {
465
+ throw context.runtime.newRuntimeError("could not rollback savepoint: '" + name + "' (not set)");
466
+ }
467
+ connection.rollback(savepoint);
468
+ return context.nil;
469
+ }
470
+ catch (SQLException e) {
471
+ return handleException(context, e);
472
+ }
473
+ }
474
+
475
+ @JRubyMethod(name = "release_savepoint", required = 1)
476
+ public IRubyObject release_savepoint(final ThreadContext context, final IRubyObject name) {
477
+ if (name == context.nil) throw context.runtime.newArgumentError("nil savepoint name given");
478
+
479
+ final Connection connection = getConnection(true);
480
+ try {
481
+ Object savepoint = getSavepoints(context).remove(name);
482
+
483
+ if (savepoint == null) throw newSavepointNotSetError(context, name, "release");
484
+
485
+ // NOTE: RubyHash.remove does not convert to Java as get does :
486
+ if (!(savepoint instanceof Savepoint)) {
487
+ savepoint = ((IRubyObject) savepoint).toJava(Savepoint.class);
488
+ }
489
+
490
+ connection.releaseSavepoint((Savepoint) savepoint);
491
+ return context.nil;
492
+ }
493
+ catch (SQLException e) {
494
+ return handleException(context, e);
495
+ }
496
+ }
497
+
498
+ protected static RuntimeException newSavepointNotSetError(final ThreadContext context, final IRubyObject name, final String op) {
499
+ RubyClass StatementInvalid = ActiveRecord(context).getClass("StatementInvalid");
500
+ return context.runtime.newRaiseException(StatementInvalid, "could not " + op + " savepoint: '" + name + "' (not set)");
501
+ }
502
+
503
+ // NOTE: this is iternal API - not to be used by user-code !
504
+ @JRubyMethod(name = "marked_savepoint_names")
505
+ public IRubyObject marked_savepoint_names(final ThreadContext context) {
506
+ @SuppressWarnings("unchecked")
507
+ final Map<IRubyObject, Savepoint> savepoints = getSavepoints(false);
508
+ if ( savepoints != null ) {
509
+ final RubyArray names = context.runtime.newArray(savepoints.size());
510
+ for ( Map.Entry<IRubyObject, ?> entry : savepoints.entrySet() ) {
511
+ names.append( entry.getKey() ); // keys are RubyString instances
512
+ }
513
+ return names;
514
+ }
515
+ return context.runtime.newEmptyArray();
516
+ }
517
+
518
+ protected Map<IRubyObject, Savepoint> getSavepoints(final ThreadContext context) {
519
+ return getSavepoints(true);
520
+ }
521
+
522
+ @SuppressWarnings("unchecked")
523
+ private final Map<IRubyObject, Savepoint> getSavepoints(final boolean init) {
524
+ if ( hasInternalVariable("savepoints") ) {
525
+ return (Map<IRubyObject, Savepoint>) getInternalVariable("savepoints");
526
+ }
527
+ if ( init ) {
528
+ Map<IRubyObject, Savepoint> savepoints = new LinkedHashMap<>(4);
529
+ setInternalVariable("savepoints", savepoints);
530
+ return savepoints;
531
+ }
532
+ return null;
533
+ }
534
+
535
+ protected boolean resetSavepoints(final ThreadContext context) {
536
+ if ( hasInternalVariable("savepoints") ) {
537
+ removeInternalVariable("savepoints");
538
+ return true;
539
+ }
540
+ return false;
541
+ }
542
+
543
+ @Deprecated // second argument is now mandatory - only kept for compatibility
544
+ @JRubyMethod(required = 1)
545
+ public final IRubyObject initialize(final ThreadContext context, final IRubyObject config) {
546
+ doInitialize(context, config, context.nil);
547
+ return this;
548
+ }
549
+
550
+ @JRubyMethod(required = 2)
551
+ public final IRubyObject initialize(final ThreadContext context, final IRubyObject config, final IRubyObject adapter) {
552
+ doInitialize(context, config, adapter);
553
+ return this;
554
+ }
555
+
556
+ protected void doInitialize(final ThreadContext context, final IRubyObject config, final IRubyObject adapter) {
557
+ this.config = config; this.adapter = adapter;
558
+
559
+ this.jndi = setupConnectionFactory(context);
560
+ this.lazy = jndi; // JNDIs are lazy by default otherwise eager
561
+ try {
562
+ initConnection(context);
563
+ }
564
+ catch (SQLException e) {
565
+ String message = e.getMessage();
566
+ if ( message == null ) message = e.getSQLState();
567
+ throw wrapException(context, e, message);
568
+ }
569
+
570
+ IRubyObject value = getConfigValue(context, "configure_connection");
571
+ if ( value == context.nil ) this.configureConnection = true;
572
+ else {
573
+ this.configureConnection = value != context.runtime.getFalse();
574
+ }
575
+
576
+ IRubyObject jdbcFetchSize = getConfigValue(context, "jdbc_fetch_size");
577
+ if (jdbcFetchSize != context.nil) {
578
+ this.fetchSize = RubyNumeric.fix2int(jdbcFetchSize);
579
+ }
580
+ }
581
+
582
+ @JRubyMethod(name = "adapter")
583
+ public IRubyObject adapter(final ThreadContext context) {
584
+ final IRubyObject adapter = getAdapter();
585
+ return adapter == null ? context.nil : adapter;
586
+ }
587
+
588
+ @JRubyMethod(name = "connection_factory")
589
+ public IRubyObject connection_factory() {
590
+ return convertJavaToRuby( getConnectionFactory() );
591
+ }
592
+
593
+ @JRubyMethod(name = "connection_factory=", required = 1)
594
+ public IRubyObject set_connection_factory(final IRubyObject factory) {
595
+ setConnectionFactory( (ConnectionFactory) factory.toJava(ConnectionFactory.class) );
596
+ return factory;
597
+ }
598
+
599
+ /**
600
+ * Called during <code>initialize</code> after the connection factory
601
+ * has been set to check if we can connect and/or perform any initialization
602
+ * necessary.
603
+ * <br/>
604
+ * NOTE: connection has not been configured at this point,
605
+ * nor should we retry - we're creating a brand new JDBC connection
606
+ *
607
+ * @param context
608
+ * @return connection
609
+ */
610
+ @Deprecated
611
+ @JRubyMethod(name = "init_connection")
612
+ public synchronized IRubyObject init_connection(final ThreadContext context) {
613
+ try {
614
+ return initConnection(context);
615
+ }
616
+ catch (SQLException e) {
617
+ return handleException(context, e); // throws
618
+ }
619
+ }
620
+
621
+ private IRubyObject initConnection(final ThreadContext context) throws SQLException {
622
+ final IRubyObject adapter = getAdapter(); // self.adapter
623
+ if ( adapter == null || adapter == context.nil ) {
624
+ warn(context, "adapter not set, please pass adapter on JdbcConnection#initialize(config, adapter)");
625
+ }
626
+
627
+ if ( ! lazy ) setConnection( newConnection() );
628
+
629
+ return context.nil;
630
+ }
631
+
632
+ private void configureConnection() {
633
+ if ( ! configureConnection ) return; // return false;
634
+
635
+ final IRubyObject adapter = getAdapter(); // self.adapter
636
+ if ( adapter != null && ! adapter.isNil() ) {
637
+ if ( adapter.respondsTo("configure_connection") ) {
638
+ final ThreadContext context = getRuntime().getCurrentContext();
639
+ adapter.callMethod(context, "configure_connection");
640
+ }
641
+ }
642
+ }
643
+
644
+ @JRubyMethod(name = "configure_connection")
645
+ public IRubyObject configure_connection(final ThreadContext context) {
646
+ if ( ! lazy || getConnectionImpl() != null ) configureConnection();
647
+ return context.nil;
648
+ }
649
+
650
+ @JRubyMethod(name = "jdbc_connection", alias = "connection")
651
+ public final IRubyObject connection(final ThreadContext context) {
652
+ return convertJavaToRuby( connectionImpl(context) );
653
+ }
654
+
655
+ @JRubyMethod(name = "jdbc_connection", alias = "connection", required = 1)
656
+ public final IRubyObject connection(final ThreadContext context, final IRubyObject unwrap) {
657
+ if ( unwrap == context.nil || unwrap == context.runtime.getFalse() ) {
658
+ return connection(context);
659
+ }
660
+ Connection connection = connectionImpl(context);
661
+ try {
662
+ if ( connection.isWrapperFor(Connection.class) ) {
663
+ return convertJavaToRuby( connection.unwrap(Connection.class) );
664
+ }
665
+ }
666
+ catch (AbstractMethodError e) {
667
+ debugStackTrace(context, e);
668
+ warn(context, "driver/pool connection does not support unwrapping: " + e);
669
+ }
670
+ catch (SQLException e) {
671
+ debugStackTrace(context, e);
672
+ warn(context, "driver/pool connection does not support unwrapping: " + e);
673
+ }
674
+ return convertJavaToRuby( connection );
675
+ }
676
+
677
+ private Connection connectionImpl(final ThreadContext context) {
678
+ Connection connection = getConnection(false);
679
+ if ( connection == null ) {
680
+ synchronized (this) {
681
+ connection = getConnection(false);
682
+ if ( connection == null ) {
683
+ reconnect(context);
684
+ connection = getConnection(false);
685
+ }
686
+ }
687
+ }
688
+ return connection;
689
+ }
690
+
691
+ @JRubyMethod(name = "active?", alias = "valid?")
692
+ public RubyBoolean active_p(final ThreadContext context) {
693
+ if ( ! connected ) return context.runtime.getFalse();
694
+ if ( isJndi() ) {
695
+ // for JNDI the data-source / pool is supposed to
696
+ // manage connections for us thus no valid check!
697
+ boolean active = getConnectionFactory() != null;
698
+ return context.runtime.newBoolean( active );
699
+ }
700
+ final Connection connection = getConnection();
701
+ if ( connection == null ) return context.runtime.getFalse(); // unlikely
702
+ return context.runtime.newBoolean( isConnectionValid(context, connection) );
703
+ }
704
+
705
+ @JRubyMethod(name = "disconnect!")
706
+ public synchronized IRubyObject disconnect(final ThreadContext context) {
707
+ setConnection(null); connected = false;
708
+ return context.nil;
709
+ }
710
+
711
+ @JRubyMethod(name = "reconnect!")
712
+ public synchronized IRubyObject reconnect(final ThreadContext context) {
713
+ try {
714
+ connectImpl( ! lazy ); connected = true;
715
+ }
716
+ catch (SQLException e) {
717
+ debugStackTrace(context, e);
718
+ handleException(context, e);
719
+ }
720
+ return context.nil;
721
+ }
722
+
723
+ private void connectImpl(final boolean forceConnection) throws SQLException {
724
+ setConnection( forceConnection ? newConnection() : null );
725
+ if ( forceConnection ) configureConnection();
726
+ }
727
+
728
+ @JRubyMethod(name = "read_only?")
729
+ public IRubyObject is_read_only(final ThreadContext context) {
730
+ final Connection connection = getConnection(false);
731
+ if ( connection != null ) {
732
+ try {
733
+ return context.runtime.newBoolean( connection.isReadOnly() );
734
+ }
735
+ catch (SQLException e) { return handleException(context, e); }
736
+ }
737
+ return context.nil;
738
+ }
739
+
740
+ @JRubyMethod(name = "read_only=")
741
+ public IRubyObject set_read_only(final ThreadContext context, final IRubyObject flag) {
742
+ final Connection connection = getConnection(true);
743
+ try {
744
+ connection.setReadOnly( flag.isTrue() );
745
+ return context.runtime.newBoolean( connection.isReadOnly() );
746
+ }
747
+ catch (SQLException e) { return handleException(context, e); }
748
+ }
749
+
750
+ @JRubyMethod(name = { "open?" /* "conn?" */ })
751
+ public IRubyObject open_p(final ThreadContext context) {
752
+ final Connection connection = getConnection(false);
753
+
754
+ if (connection == null) return context.runtime.getFalse();
755
+
756
+ try {
757
+ // NOTE: isClosed method generally cannot be called to determine
758
+ // whether a connection to a database is valid or invalid ...
759
+ return context.runtime.newBoolean(!connection.isClosed());
760
+ } catch (SQLException e) {
761
+ return handleException(context, e);
762
+ }
763
+ }
764
+
765
+ @JRubyMethod(name = "close")
766
+ public IRubyObject close(final ThreadContext context) {
767
+ final Connection connection = getConnection(false);
768
+
769
+ if (connection == null) return context.runtime.getFalse();
770
+
771
+ try {
772
+ if (connection.isClosed()) return context.runtime.getFalse();
773
+
774
+ setConnection(null); // does connection.close();
775
+ } catch (Exception e) {
776
+ debugStackTrace(context, e);
777
+ return context.nil;
778
+ }
779
+
780
+ // ActiveRecord expects a closed connection to not try and re-open a connection
781
+ // whereas JNDI expects that.
782
+ if (!isJndi()) disconnect(context);
783
+
784
+ return context.runtime.getTrue();
785
+ }
786
+
787
+ @JRubyMethod(name = "database_name")
788
+ public IRubyObject database_name(final ThreadContext context) {
789
+ return withConnection(context, new Callable<IRubyObject>() {
790
+ public IRubyObject call(final Connection connection) throws SQLException {
791
+ String name = connection.getCatalog();
792
+ if ( name == null ) {
793
+ name = connection.getMetaData().getUserName();
794
+ if ( name == null ) return context.nil;
795
+ }
796
+ return context.runtime.newString(name);
797
+ }
798
+ });
799
+ }
800
+
801
+ @JRubyMethod(name = "execute", required = 1)
802
+ public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
803
+ final String query = sqlString(sql);
804
+ return withConnection(context, new Callable<IRubyObject>() {
805
+ public IRubyObject call(final Connection connection) throws SQLException {
806
+ Statement statement = null;
807
+ try {
808
+ statement = createStatement(context, connection);
809
+
810
+ // For DBs that do support multiple statements, lets return the last result set
811
+ // to be consistent with AR
812
+ boolean hasResultSet = doExecute(statement, query);
813
+ int updateCount = statement.getUpdateCount();
814
+
815
+ IRubyObject result = context.nil; // If no results, return nil
816
+ ResultSet resultSet;
817
+
818
+ while (hasResultSet || updateCount != -1) {
819
+
820
+ if (hasResultSet) {
821
+ resultSet = statement.getResultSet();
822
+
823
+ // Unfortunately the result set gets closed when getMoreResults()
824
+ // is called, so we have to process the result sets as we get them
825
+ // this shouldn't be an issue in most cases since we're only getting 1 result set anyways
826
+ result = mapExecuteResult(context, connection, resultSet);
827
+ resultSet.close();
828
+ } else {
829
+ result = context.runtime.newFixnum(updateCount);
830
+ }
831
+
832
+ // Check to see if there is another result set
833
+ hasResultSet = statement.getMoreResults();
834
+ updateCount = statement.getUpdateCount();
835
+ }
836
+
837
+ return result;
838
+
839
+ } catch (final SQLException e) {
840
+ debugErrorSQL(context, query);
841
+ throw e;
842
+ } finally {
843
+ close(statement);
844
+ }
845
+ }
846
+ });
847
+ }
848
+
849
+ protected Statement createStatement(final ThreadContext context, final Connection connection)
850
+ throws SQLException {
851
+ final Statement statement = connection.createStatement();
852
+ IRubyObject escapeProcessing = getConfigValue(context, "statement_escape_processing");
853
+ // NOTE: disable (driver) escape processing by default, it's not really
854
+ // needed for AR statements ... if users need it they might configure :
855
+ if ( escapeProcessing == context.nil ) {
856
+ statement.setEscapeProcessing(false);
857
+ }
858
+ else {
859
+ statement.setEscapeProcessing(escapeProcessing.isTrue());
860
+ }
861
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
862
+ return statement;
863
+ }
864
+
865
+ /**
866
+ * Execute a query using the given statement.
867
+ * @param statement
868
+ * @param query
869
+ * @return true if the first result is a <code>ResultSet</code>;
870
+ * false if it is an update count or there are no results
871
+ * @throws SQLException
872
+ */
873
+ protected boolean doExecute(final Statement statement, final String query) throws SQLException {
874
+ return statement.execute(query);
875
+ }
876
+
877
+ protected IRubyObject mapExecuteResult(final ThreadContext context,
878
+ final Connection connection, final ResultSet resultSet) throws SQLException{
879
+
880
+ return mapQueryResult(context, connection, resultSet);
881
+ }
882
+
883
+ /**
884
+ * Executes an INSERT SQL statement
885
+ * @param context
886
+ * @param sql
887
+ * @return ActiveRecord::Result
888
+ * @throws SQLException
889
+ */
890
+ @JRubyMethod(name = "execute_insert", required = 1)
891
+ public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql) {
892
+ return withConnection(context, new Callable<IRubyObject>() {
893
+ public IRubyObject call(final Connection connection) throws SQLException {
894
+ Statement statement = null;
895
+ final String query = sqlString(sql);
896
+ try {
897
+
898
+ statement = createStatement(context, connection);
899
+ statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
900
+ return mapGeneratedKeys(context, connection, statement);
901
+
902
+ } catch (final SQLException e) {
903
+ debugErrorSQL(context, query);
904
+ throw e;
905
+ } finally {
906
+ close(statement);
907
+ }
908
+ }
909
+ });
910
+ }
911
+
912
+ /**
913
+ * Executes an INSERT SQL statement using a prepared statement
914
+ * @param context
915
+ * @param sql
916
+ * @param binds RubyArray of values to be bound to the query
917
+ * @return ActiveRecord::Result
918
+ * @throws SQLException
919
+ */
920
+ @JRubyMethod(name = "execute_insert", required = 2)
921
+ public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql, final IRubyObject binds) {
922
+ return withConnection(context, new Callable<IRubyObject>() {
923
+ public IRubyObject call(final Connection connection) throws SQLException {
924
+ PreparedStatement statement = null;
925
+ final String query = sqlString(sql);
926
+ try {
927
+
928
+ statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
929
+ setStatementParameters(context, connection, statement, (RubyArray) binds);
930
+ statement.executeUpdate();
931
+ return mapGeneratedKeys(context, connection, statement);
932
+
933
+ } catch (final SQLException e) {
934
+ debugErrorSQL(context, query);
935
+ throw e;
936
+ } finally {
937
+ close(statement);
938
+ }
939
+ }
940
+ });
941
+ }
942
+
943
+ /**
944
+ * Executes an UPDATE (DELETE) SQL statement
945
+ * @param context
946
+ * @param sql
947
+ * @return affected row count
948
+ * @throws SQLException
949
+ */
950
+ @JRubyMethod(name = {"execute_update", "execute_delete"}, required = 1)
951
+ public IRubyObject execute_update(final ThreadContext context, final IRubyObject sql) {
952
+ return withConnection(context, new Callable<IRubyObject>() {
953
+ public IRubyObject call(final Connection connection) throws SQLException {
954
+ Statement statement = null;
955
+ final String query = sqlString(sql);
956
+
957
+ try {
958
+ statement = createStatement(context, connection);
959
+
960
+ final int rowCount = statement.executeUpdate(query);
961
+ return context.runtime.newFixnum(rowCount);
962
+ } catch (final SQLException e) {
963
+ debugErrorSQL(context, query);
964
+ throw e;
965
+ } finally {
966
+ close(statement);
967
+ }
968
+ }
969
+ });
970
+ }
971
+
972
+ /**
973
+ * Executes an UPDATE (DELETE) SQL using a prepared statement
974
+ * @param context
975
+ * @param sql
976
+ * @return affected row count
977
+ * @throws SQLException
978
+ *
979
+ * @see #execute_update(ThreadContext, IRubyObject)
980
+ */
981
+ @JRubyMethod(name = {"execute_prepared_update", "execute_prepared_delete"}, required = 2)
982
+ public IRubyObject execute_prepared_update(final ThreadContext context, final IRubyObject sql, final IRubyObject binds) {
983
+ return withConnection(context, new Callable<IRubyObject>() {
984
+ public IRubyObject call(final Connection connection) throws SQLException {
985
+ PreparedStatement statement = null;
986
+ final String query = sqlString(sql);
987
+ try {
988
+ statement = connection.prepareStatement(query);
989
+ setStatementParameters(context, connection, statement, (RubyArray) binds);
990
+ final int rowCount = statement.executeUpdate();
991
+ return context.runtime.newFixnum(rowCount);
992
+ } catch (final SQLException e) {
993
+ debugErrorSQL(context, query);
994
+ throw e;
995
+ } finally {
996
+ close(statement);
997
+ }
998
+ }
999
+ });
1000
+ }
1001
+
1002
+ /**
1003
+ * This is the same as execute_query but it will return a list of hashes.
1004
+ *
1005
+ * @see RubyJdbcConnection#execute_query(ThreadContext, IRubyObject)
1006
+ * @param context which context this method is executing on.
1007
+ * @param args arguments being supplied to this method.
1008
+ * @param block (optional) block to yield row values (Hash(name: value))
1009
+ * @return List of Hash(name: value) unless block is given.
1010
+ * @throws SQLException when a database error occurs<
1011
+ */
1012
+ @JRubyMethod(required = 1, optional = 2)
1013
+ public IRubyObject execute_query_raw(final ThreadContext context, final IRubyObject[] args, final Block block) {
1014
+ final String query = sqlString( args[0] ); // sql
1015
+ final RubyArray binds;
1016
+ final int maxRows;
1017
+
1018
+ // args: (sql), (sql, max_rows), (sql, binds), (sql, max_rows, binds)
1019
+ switch (args.length) {
1020
+ case 2:
1021
+ if (args[1] instanceof RubyNumeric) { // (sql, max_rows)
1022
+ maxRows = RubyNumeric.fix2int(args[1]);
1023
+ binds = null;
1024
+ } else { // (sql, binds)
1025
+ maxRows = 0;
1026
+ binds = (RubyArray) TypeConverter.checkArrayType(args[1]);
1027
+ }
1028
+ break;
1029
+ case 3: // (sql, max_rows, binds)
1030
+ maxRows = RubyNumeric.fix2int(args[1]);
1031
+ binds = (RubyArray) TypeConverter.checkArrayType(args[2]);
1032
+ break;
1033
+ default: // (sql) 1-arg
1034
+ maxRows = 0;
1035
+ binds = null;
1036
+ break;
1037
+ }
1038
+
1039
+ return doExecuteQueryRaw(context, query, maxRows, block, binds);
1040
+ }
1041
+
1042
+ private IRubyObject doExecuteQueryRaw(final ThreadContext context,
1043
+ final String query, final int maxRows, final Block block, final RubyArray binds) {
1044
+ return withConnection(context, new Callable<IRubyObject>() {
1045
+ public IRubyObject call(final Connection connection) throws SQLException {
1046
+ Statement statement = null; boolean hasResult;
1047
+ try {
1048
+ if ( binds == null || binds.isEmpty()) { // plain statement
1049
+ statement = createStatement(context, connection);
1050
+ statement.setMaxRows(maxRows); // zero means there is no limit
1051
+ hasResult = statement.execute(query);
1052
+ }
1053
+ else {
1054
+ final PreparedStatement prepStatement;
1055
+ statement = prepStatement = connection.prepareStatement(query);
1056
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
1057
+ statement.setMaxRows(maxRows); // zero means there is no limit
1058
+ setStatementParameters(context, connection, prepStatement, binds);
1059
+ hasResult = prepStatement.execute();
1060
+ }
1061
+
1062
+ if (block.isGiven()) {
1063
+ if (hasResult) {
1064
+ // yield(id1, name1) ... row 1 result data
1065
+ // yield(id2, name2) ... row 2 result data
1066
+ return yieldResultRows(context, connection, statement.getResultSet(), block);
1067
+ }
1068
+ return context.nil;
1069
+ }
1070
+ if (hasResult) {
1071
+ return mapToRawResult(context, connection, statement.getResultSet(), false);
1072
+ }
1073
+ return context.runtime.newEmptyArray();
1074
+ }
1075
+ catch (final SQLException e) {
1076
+ debugErrorSQL(context, query);
1077
+ throw e;
1078
+ }
1079
+ finally {
1080
+ close(statement);
1081
+ }
1082
+ }
1083
+ });
1084
+ }
1085
+
1086
+ protected static String sqlString(final IRubyObject sql) {
1087
+ return sql instanceof RubyString ? ((RubyString) sql).decodeString() : sql.convertToString().decodeString();
1088
+ }
1089
+
1090
+ /**
1091
+ * Executes a query and returns the (AR) result
1092
+ *
1093
+ * @param context which context this method is executing on
1094
+ * @param sql the query to execute
1095
+ * @return a Ruby <code>ActiveRecord::Result</code> instance
1096
+ * @throws SQLException when a database error occurs
1097
+ */
1098
+ @JRubyMethod(required = 1)
1099
+ public IRubyObject execute_query(final ThreadContext context, final IRubyObject sql) {
1100
+ return withConnection(context, new Callable<IRubyObject>() {
1101
+ public IRubyObject call(final Connection connection) throws SQLException {
1102
+ Statement statement = null;
1103
+ final String query = sqlString(sql);
1104
+ try {
1105
+ statement = createStatement(context, connection);
1106
+
1107
+ // At least until AR 5.1 #exec_query still gets called for things that don't return results in some cases :(
1108
+ if (statement.execute(query)) {
1109
+ return mapQueryResult(context, connection, statement.getResultSet());
1110
+ }
1111
+
1112
+ return context.nil;
1113
+
1114
+ } catch (final SQLException e) {
1115
+ debugErrorSQL(context, query);
1116
+ throw e;
1117
+ } finally {
1118
+ close(statement);
1119
+ }
1120
+ }
1121
+ });
1122
+ }
1123
+
1124
+ /**
1125
+ * Prepares a query, returns a wrapped PreparedStatement. This takes care of exception wrapping
1126
+ * @param context which context this method is executing on.
1127
+ * @param sql the query to prepare-
1128
+ * @return a Ruby <code>PreparedStatement</code>
1129
+ */
1130
+ @JRubyMethod(required = 1)
1131
+ public IRubyObject prepare_statement(final ThreadContext context, final IRubyObject sql) {
1132
+ return withConnection(context, new Callable<IRubyObject>() {
1133
+ public IRubyObject call(Connection connection) throws SQLException {
1134
+ final String query = sql.convertToString().getUnicodeValue();
1135
+ PreparedStatement statement = connection.prepareStatement(query);
1136
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
1137
+ return JavaUtil.convertJavaToRuby(context.runtime, statement);
1138
+ }
1139
+ });
1140
+ }
1141
+
1142
+ // Called from exec_query in abstract/database_statements
1143
+ /**
1144
+ * Executes a query and returns the (AR) result. There are three parameters:
1145
+ * <ul>
1146
+ * <li>sql - String of sql</li>
1147
+ * <li>binds - Array of bindings for a prepared statement</li>
1148
+ * <li>cached_statement - A prepared statement object that should be used instead of creating a new statement</li>
1149
+ * </ul>
1150
+ *
1151
+ * @param context which context this method is executing on.
1152
+ * @param sql the query to execute.
1153
+ * @param binds an array of values to be set as parameters
1154
+ * @param cachedStatement a wrapped <code>PreparedStatement</code> to use instead of creating a new <code>Statement</code>
1155
+ * @return a Ruby <code>ActiveRecord::Result</code> instance
1156
+ * @throws SQLException when a database error occurs
1157
+ */
1158
+ @JRubyMethod(required = 3)
1159
+ public IRubyObject execute_prepared_query(final ThreadContext context, final IRubyObject sql,
1160
+ final IRubyObject binds, final IRubyObject cachedStatement) {
1161
+ return withConnection(context, new Callable<IRubyObject>() {
1162
+ public IRubyObject call(final Connection connection) throws SQLException {
1163
+ final boolean cached = !(cachedStatement == null || cachedStatement.isNil());
1164
+ final String query = sql.convertToString().getUnicodeValue();
1165
+ PreparedStatement statement = null;
1166
+
1167
+ try {
1168
+ if (cached) {
1169
+ statement = (PreparedStatement) JavaEmbedUtils.rubyToJava(cachedStatement);
1170
+ } else {
1171
+ statement = connection.prepareStatement(query);
1172
+ if (fetchSize != 0) statement.setFetchSize(fetchSize);
1173
+ }
1174
+
1175
+ setStatementParameters(context, connection, statement, (RubyArray) binds);
1176
+
1177
+ if (statement.execute()) {
1178
+ ResultSet resultSet = statement.getResultSet();
1179
+ IRubyObject results = mapQueryResult(context, connection, resultSet);
1180
+ resultSet.close();
1181
+
1182
+ return results;
1183
+ } else {
1184
+ return context.nil;
1185
+ }
1186
+ } catch (final SQLException e) {
1187
+ debugErrorSQL(context, query);
1188
+ throw e;
1189
+ } finally {
1190
+ if ( cached ) {
1191
+ statement.clearParameters();
1192
+ } else {
1193
+ close(statement);
1194
+ }
1195
+ }
1196
+ }
1197
+ });
1198
+ }
1199
+
1200
+ protected IRubyObject mapQueryResult(final ThreadContext context,
1201
+ final Connection connection, final ResultSet resultSet) throws SQLException {
1202
+ final ColumnData[] columns = extractColumns(context, connection, resultSet, false);
1203
+ return mapToResult(context, connection, resultSet, columns);
1204
+ }
1205
+
1206
+ /**
1207
+ * @deprecated please do not use this method
1208
+ */
1209
+ @Deprecated // only used by Oracle adapter - also it's really a bad idea
1210
+ @JRubyMethod(name = "execute_id_insert", required = 2)
1211
+ public IRubyObject execute_id_insert(final ThreadContext context, final IRubyObject sql, final IRubyObject id) {
1212
+ final Ruby runtime = context.runtime;
1213
+
1214
+ callMethod("warn", RubyString.newUnicodeString(runtime, "DEPRECATED: execute_id_insert(sql, id) will be removed"));
1215
+
1216
+ return withConnection(context, new Callable<IRubyObject>() {
1217
+ public IRubyObject call(final Connection connection) throws SQLException {
1218
+ PreparedStatement statement = null;
1219
+ final String insertSQL = sql.convertToString().getUnicodeValue();
1220
+ try {
1221
+ statement = connection.prepareStatement(insertSQL);
1222
+ statement.setLong(1, RubyNumeric.fix2long(id));
1223
+ statement.executeUpdate();
1224
+ }
1225
+ catch (final SQLException e) {
1226
+ debugErrorSQL(context, insertSQL);
1227
+ throw e;
1228
+ }
1229
+ finally { close(statement); }
1230
+ return id;
1231
+ }
1232
+ });
1233
+ }
1234
+
1235
+ @JRubyMethod(name = "supported_data_types")
1236
+ public IRubyObject supported_data_types(final ThreadContext context) throws SQLException {
1237
+ final Connection connection = getConnection(true);
1238
+ final ResultSet typeDesc = connection.getMetaData().getTypeInfo();
1239
+ final IRubyObject types;
1240
+ try {
1241
+ types = mapToRawResult(context, connection, typeDesc, true);
1242
+ }
1243
+ finally { close(typeDesc); }
1244
+
1245
+ return types;
1246
+ }
1247
+
1248
+ @JRubyMethod(name = "primary_keys", required = 1)
1249
+ public IRubyObject primary_keys(ThreadContext context, IRubyObject tableName) throws SQLException {
1250
+ @SuppressWarnings("unchecked")
1251
+ List<IRubyObject> primaryKeys = (List) primaryKeys(context, tableName.toString());
1252
+ return context.runtime.newArray(primaryKeys);
1253
+ }
1254
+
1255
+ protected static final int PRIMARY_KEYS_COLUMN_NAME = 4;
1256
+
1257
+ private List<RubyString> primaryKeys(final ThreadContext context, final String tableName) {
1258
+ return withConnection(context, new Callable<List<RubyString>>() {
1259
+ public List<RubyString> call(final Connection connection) throws SQLException {
1260
+ final String _tableName = caseConvertIdentifierForJdbc(connection, tableName);
1261
+ final TableName table = extractTableName(connection, null, null, _tableName);
1262
+ return primaryKeys(context, connection, table);
1263
+ }
1264
+ });
1265
+ }
1266
+
1267
+ protected List<RubyString> primaryKeys(final ThreadContext context,
1268
+ final Connection connection, final TableName table) throws SQLException {
1269
+ final DatabaseMetaData metaData = connection.getMetaData();
1270
+ ResultSet resultSet = null;
1271
+ final List<RubyString> keyNames = new ArrayList<RubyString>();
1272
+ try {
1273
+ resultSet = metaData.getPrimaryKeys(table.catalog, table.schema, table.name);
1274
+ final Ruby runtime = context.runtime;
1275
+ while ( resultSet.next() ) {
1276
+ String columnName = resultSet.getString(PRIMARY_KEYS_COLUMN_NAME);
1277
+ columnName = caseConvertIdentifierForRails(connection, columnName);
1278
+ keyNames.add( RubyString.newUnicodeString(runtime, columnName) );
1279
+ }
1280
+ }
1281
+ finally { close(resultSet); }
1282
+ return keyNames;
1283
+ }
1284
+
1285
+ @Deprecated //@JRubyMethod(name = "tables")
1286
+ public IRubyObject tables(ThreadContext context) {
1287
+ return tables(context, null, null, null, TABLE_TYPE);
1288
+ }
1289
+
1290
+ @Deprecated //@JRubyMethod(name = "tables")
1291
+ public IRubyObject tables(ThreadContext context, IRubyObject catalog) {
1292
+ return tables(context, toStringOrNull(catalog), null, null, TABLE_TYPE);
1293
+ }
1294
+
1295
+ @Deprecated //@JRubyMethod(name = "tables")
1296
+ public IRubyObject tables(ThreadContext context, IRubyObject catalog, IRubyObject schemaPattern) {
1297
+ return tables(context, toStringOrNull(catalog), toStringOrNull(schemaPattern), null, TABLE_TYPE);
1298
+ }
1299
+
1300
+ @Deprecated //@JRubyMethod(name = "tables")
1301
+ public IRubyObject tables(ThreadContext context, IRubyObject catalog, IRubyObject schemaPattern, IRubyObject tablePattern) {
1302
+ return tables(context, toStringOrNull(catalog), toStringOrNull(schemaPattern), toStringOrNull(tablePattern), TABLE_TYPE);
1303
+ }
1304
+
1305
+ @JRubyMethod(name = "tables", required = 0, optional = 4)
1306
+ public IRubyObject tables(final ThreadContext context, final IRubyObject[] args) {
1307
+ switch ( args.length ) {
1308
+ case 0: // ()
1309
+ return tables(context, null, null, null, TABLE_TYPE);
1310
+ case 1: // (catalog)
1311
+ return tables(context, toStringOrNull(args[0]), null, null, TABLE_TYPE);
1312
+ case 2: // (catalog, schemaPattern)
1313
+ return tables(context, toStringOrNull(args[0]), toStringOrNull(args[1]), null, TABLE_TYPE);
1314
+ case 3: // (catalog, schemaPattern, tablePattern)
1315
+ return tables(context, toStringOrNull(args[0]), toStringOrNull(args[1]), toStringOrNull(args[2]), TABLE_TYPE);
1316
+ }
1317
+ return tables(context, toStringOrNull(args[0]), toStringOrNull(args[1]), toStringOrNull(args[2]), getTypes(args[3]));
1318
+ }
1319
+
1320
+ protected IRubyObject tables(final ThreadContext context,
1321
+ final String catalog, final String schemaPattern, final String tablePattern, final String[] types) {
1322
+ return withConnection(context, new Callable<IRubyObject>() {
1323
+ public IRubyObject call(final Connection connection) throws SQLException {
1324
+ return matchTables(context, connection, catalog, schemaPattern, tablePattern, types, false);
1325
+ }
1326
+ });
1327
+ }
1328
+
1329
+ protected String[] getTableTypes() {
1330
+ return TABLE_TYPES;
1331
+ }
1332
+
1333
+ @JRubyMethod(name = "table_exists?")
1334
+ public IRubyObject table_exists_p(final ThreadContext context, IRubyObject table) {
1335
+ if ( table.isNil() ) {
1336
+ throw context.runtime.newArgumentError("nil table name");
1337
+ }
1338
+ final String tableName = table.toString();
1339
+
1340
+ return tableExists(context, null, tableName);
1341
+ }
1342
+
1343
+ @JRubyMethod(name = "table_exists?")
1344
+ public IRubyObject table_exists_p(final ThreadContext context, IRubyObject table, IRubyObject schema) {
1345
+ if ( table.isNil() ) {
1346
+ throw context.runtime.newArgumentError("nil table name");
1347
+ }
1348
+ final String tableName = table.toString();
1349
+ final String defaultSchema = schema.isNil() ? null : schema.toString();
1350
+
1351
+ return tableExists(context, defaultSchema, tableName);
1352
+ }
1353
+
1354
+ protected IRubyObject tableExists(final ThreadContext context,
1355
+ final String defaultSchema, final String tableName) {
1356
+ return withConnection(context, new Callable<RubyBoolean>() {
1357
+ public RubyBoolean call(final Connection connection) throws SQLException {
1358
+ final TableName components = extractTableName(connection, defaultSchema, tableName);
1359
+ return context.runtime.newBoolean( tableExists(context, connection, components) );
1360
+ }
1361
+ });
1362
+ }
1363
+
1364
+ @JRubyMethod(name = {"columns", "columns_internal"}, required = 1, optional = 2)
1365
+ public RubyArray columns_internal(final ThreadContext context, final IRubyObject[] args)
1366
+ throws SQLException {
1367
+ return withConnection(context, new Callable<RubyArray>() {
1368
+ public RubyArray call(final Connection connection) throws SQLException {
1369
+ ResultSet columns = null;
1370
+ try {
1371
+ final String tableName = args[0].toString();
1372
+ // optionals (NOTE: catalog argumnet was never used before 1.3.0) :
1373
+ final String catalog = args.length > 1 ? toStringOrNull(args[1]) : null;
1374
+ final String defaultSchema = args.length > 2 ? toStringOrNull(args[2]) : null;
1375
+
1376
+ final TableName components;
1377
+ components = extractTableName(connection, catalog, defaultSchema, tableName);
1378
+
1379
+ if ( ! tableExists(context, connection, components) ) {
1380
+ throw new SQLException("table: " + tableName + " does not exist");
1381
+ }
1382
+
1383
+ final DatabaseMetaData metaData = connection.getMetaData();
1384
+ columns = metaData.getColumns(components.catalog, components.schema, components.name, null);
1385
+ return mapColumnsResult(context, metaData, components, columns);
1386
+ }
1387
+ finally {
1388
+ close(columns);
1389
+ }
1390
+ }
1391
+ });
1392
+ }
1393
+
1394
+ @JRubyMethod(name = "indexes")
1395
+ public IRubyObject indexes(final ThreadContext context, IRubyObject tableName, IRubyObject name) {
1396
+ return indexes(context, toStringOrNull(tableName), toStringOrNull(name), null);
1397
+ }
1398
+
1399
+ @JRubyMethod(name = "indexes")
1400
+ public IRubyObject indexes(final ThreadContext context, IRubyObject tableName, IRubyObject name, IRubyObject schemaName) {
1401
+ return indexes(context, toStringOrNull(tableName), toStringOrNull(name), toStringOrNull(schemaName));
1402
+ }
1403
+
1404
+ // NOTE: metaData.getIndexInfo row mappings :
1405
+ protected static final int INDEX_INFO_TABLE_NAME = 3;
1406
+ protected static final int INDEX_INFO_NON_UNIQUE = 4;
1407
+ protected static final int INDEX_INFO_NAME = 6;
1408
+ protected static final int INDEX_INFO_COLUMN_NAME = 9;
1409
+
1410
+ /**
1411
+ * Default JDBC introspection for index metadata on the JdbcConnection.
1412
+ *
1413
+ * JDBC index metadata is denormalized (multiple rows may be returned for
1414
+ * one index, one row per column in the index), so a simple block-based
1415
+ * filter like that used for tables doesn't really work here. Callers
1416
+ * should filter the return from this method instead.
1417
+ */
1418
+ protected IRubyObject indexes(final ThreadContext context, final String tableName, final String name, final String schemaName) {
1419
+ return withConnection(context, new Callable<IRubyObject>() {
1420
+ public IRubyObject call(final Connection connection) throws SQLException {
1421
+ final Ruby runtime = context.runtime;
1422
+ final RubyClass IndexDefinition = getIndexDefinition(context);
1423
+
1424
+ String _tableName = caseConvertIdentifierForJdbc(connection, tableName);
1425
+ String _schemaName = caseConvertIdentifierForJdbc(connection, schemaName);
1426
+ final TableName table = extractTableName(connection, null, _schemaName, _tableName);
1427
+
1428
+ final List<RubyString> primaryKeys = primaryKeys(context, connection, table);
1429
+
1430
+ ResultSet indexInfoSet = null;
1431
+ final RubyArray indexes = RubyArray.newArray(runtime, 8);
1432
+ try {
1433
+ final DatabaseMetaData metaData = connection.getMetaData();
1434
+ indexInfoSet = metaData.getIndexInfo(table.catalog, table.schema, table.name, false, true);
1435
+ String currentIndex = null;
1436
+ RubyArray currentColumns = null;
1437
+
1438
+ while ( indexInfoSet.next() ) {
1439
+ String indexName = indexInfoSet.getString(INDEX_INFO_NAME);
1440
+ if ( indexName == null ) continue;
1441
+
1442
+ indexName = caseConvertIdentifierForRails(metaData, indexName);
1443
+
1444
+ final String columnName = indexInfoSet.getString(INDEX_INFO_COLUMN_NAME);
1445
+ final RubyString rubyColumnName = cachedString(
1446
+ context, caseConvertIdentifierForRails(metaData, columnName)
1447
+ );
1448
+ if ( primaryKeys.contains(rubyColumnName) ) continue;
1449
+
1450
+ // We are working on a new index
1451
+ if ( ! indexName.equals(currentIndex) ) {
1452
+ currentIndex = indexName;
1453
+
1454
+ String indexTableName = indexInfoSet.getString(INDEX_INFO_TABLE_NAME);
1455
+ indexTableName = caseConvertIdentifierForRails(metaData, indexTableName);
1456
+
1457
+ final boolean nonUnique = indexInfoSet.getBoolean(INDEX_INFO_NON_UNIQUE);
1458
+
1459
+ IRubyObject[] args = new IRubyObject[] {
1460
+ cachedString(context, indexTableName), // table_name
1461
+ cachedString(context, indexName), // index_name
1462
+ nonUnique ? runtime.getFalse() : runtime.getTrue(), // unique
1463
+ currentColumns = RubyArray.newArray(runtime, 4) // [] column names
1464
+ // orders, (since AR 3.2) where, type, using (AR 4.0)
1465
+ };
1466
+
1467
+ indexes.append( IndexDefinition.newInstance(context, args, Block.NULL_BLOCK) ); // IndexDefinition.new
1468
+ }
1469
+
1470
+ // one or more columns can be associated with an index
1471
+ if ( currentColumns != null ) currentColumns.append(rubyColumnName);
1472
+ }
1473
+
1474
+ return indexes;
1475
+
1476
+ } finally { close(indexInfoSet); }
1477
+ }
1478
+ });
1479
+ }
1480
+
1481
+ protected RubyClass getIndexDefinition(final ThreadContext context) {
1482
+ final RubyClass adapterClass = getAdapter().getMetaClass();
1483
+ IRubyObject IDef = adapterClass.getConstantAt("IndexDefinition");
1484
+ return IDef != null ? (RubyClass) IDef : getIndexDefinition(context.runtime);
1485
+ }
1486
+
1487
+ @JRubyMethod
1488
+ public IRubyObject foreign_keys(final ThreadContext context, IRubyObject table_name) {
1489
+ return foreignKeys(context, table_name.toString(), null, null);
1490
+ }
1491
+
1492
+ protected IRubyObject foreignKeys(final ThreadContext context, final String tableName, final String schemaName, final String catalog) {
1493
+ return withConnection(context, new Callable<IRubyObject>() {
1494
+ public IRubyObject call(final Connection connection) throws SQLException {
1495
+ final Ruby runtime = context.runtime;
1496
+ final RubyClass FKDefinition = getForeignKeyDefinition(context);
1497
+
1498
+ String _tableName = caseConvertIdentifierForJdbc(connection, tableName);
1499
+ String _schemaName = caseConvertIdentifierForJdbc(connection, schemaName);
1500
+ final TableName table = extractTableName(connection, catalog, _schemaName, _tableName);
1501
+
1502
+ ResultSet fkInfoSet = null;
1503
+ final List<IRubyObject> fKeys = new ArrayList<IRubyObject>(8);
1504
+ try {
1505
+ final DatabaseMetaData metaData = connection.getMetaData();
1506
+ fkInfoSet = metaData.getImportedKeys(table.catalog, table.schema, table.name);
1507
+
1508
+ while ( fkInfoSet.next() ) {
1509
+ final RubyHash options = RubyHash.newHash(runtime);
1510
+
1511
+ String fkName = fkInfoSet.getString("FK_NAME");
1512
+ if (fkName != null) {
1513
+ fkName = caseConvertIdentifierForRails(metaData, fkName);
1514
+ options.put(runtime.newSymbol("name"), fkName);
1515
+ }
1516
+
1517
+ String columnName = fkInfoSet.getString("FKCOLUMN_NAME");
1518
+ options.put(runtime.newSymbol("column"), caseConvertIdentifierForRails(metaData, columnName));
1519
+
1520
+ columnName = fkInfoSet.getString("PKCOLUMN_NAME");
1521
+ options.put(runtime.newSymbol("primary_key"), caseConvertIdentifierForRails(metaData, columnName));
1522
+
1523
+ String fkTableName = fkInfoSet.getString("FKTABLE_NAME");
1524
+ fkTableName = caseConvertIdentifierForRails(metaData, fkTableName);
1525
+
1526
+ String pkTableName = fkInfoSet.getString("PKTABLE_NAME");
1527
+ pkTableName = caseConvertIdentifierForRails(metaData, pkTableName);
1528
+
1529
+ final String onDelete = extractForeignKeyRule( fkInfoSet.getInt("DELETE_RULE") );
1530
+ if ( onDelete != null ) options.op_aset(context, runtime.newSymbol("on_delete"), runtime.newSymbol(onDelete));
1531
+
1532
+ final String onUpdate = extractForeignKeyRule( fkInfoSet.getInt("UPDATE_RULE") );
1533
+ if ( onUpdate != null ) options.op_aset(context, runtime.newSymbol("on_update"), runtime.newSymbol(onUpdate));
1534
+
1535
+ IRubyObject from_table = cachedString(context, fkTableName);
1536
+ IRubyObject to_table = cachedString(context, pkTableName);
1537
+ fKeys.add( FKDefinition.newInstance(context, from_table, to_table, options, Block.NULL_BLOCK) ); // ForeignKeyDefinition.new
1538
+ }
1539
+
1540
+ return runtime.newArray(fKeys);
1541
+
1542
+ } finally { close(fkInfoSet); }
1543
+ }
1544
+ });
1545
+ }
1546
+
1547
+ protected String extractForeignKeyRule(final int rule) {
1548
+ switch (rule) {
1549
+ case DatabaseMetaData.importedKeyNoAction : return null ;
1550
+ case DatabaseMetaData.importedKeyCascade : return "cascade" ;
1551
+ case DatabaseMetaData.importedKeySetNull : return "nullify" ;
1552
+ case DatabaseMetaData.importedKeySetDefault: return "default" ;
1553
+ }
1554
+ return null;
1555
+ }
1556
+
1557
+ protected RubyClass getForeignKeyDefinition(final ThreadContext context) {
1558
+ final RubyClass adapterClass = getAdapter().getMetaClass();
1559
+ IRubyObject FKDef = adapterClass.getConstantAt("ForeignKeyDefinition");
1560
+ return FKDef != null ? (RubyClass) FKDef : getForeignKeyDefinition(context.runtime);
1561
+ }
1562
+
1563
+
1564
+ @JRubyMethod(name = "supports_foreign_keys?")
1565
+ public IRubyObject supports_foreign_keys_p(final ThreadContext context) throws SQLException {
1566
+ return withConnection(context, new Callable<IRubyObject>() {
1567
+ public IRubyObject call(final Connection connection) throws SQLException {
1568
+ final DatabaseMetaData metaData = connection.getMetaData();
1569
+ return context.runtime.newBoolean( metaData.supportsIntegrityEnhancementFacility() );
1570
+ }
1571
+ });
1572
+ }
1573
+
1574
+ @JRubyMethod(name = "supports_views?")
1575
+ public IRubyObject supports_views_p(final ThreadContext context) throws SQLException {
1576
+ return withConnection(context, new Callable<IRubyObject>() {
1577
+ public IRubyObject call(final Connection connection) throws SQLException {
1578
+ final DatabaseMetaData metaData = connection.getMetaData();
1579
+ final ResultSet tableTypes = metaData.getTableTypes();
1580
+ try {
1581
+ while ( tableTypes.next() ) {
1582
+ if ( "VIEW".equalsIgnoreCase( tableTypes.getString(1) ) ) {
1583
+ return context.runtime.newBoolean( true );
1584
+ }
1585
+ }
1586
+ }
1587
+ finally {
1588
+ close(tableTypes);
1589
+ }
1590
+ return context.runtime.newBoolean( false );
1591
+ }
1592
+ });
1593
+ }
1594
+
1595
+ @JRubyMethod(name = "with_jdbc_connection", alias = "with_connection_retry_guard", frame = true)
1596
+ public IRubyObject with_jdbc_connection(final ThreadContext context, final Block block) {
1597
+ return withConnection(context, new Callable<IRubyObject>() {
1598
+ public IRubyObject call(final Connection connection) throws SQLException {
1599
+ return block.call(context, convertJavaToRuby(connection));
1600
+ }
1601
+ });
1602
+ }
1603
+
1604
+ /*
1605
+ * (binary?, column_name, table_name, id_key, id_value, value)
1606
+ */
1607
+ @Deprecated
1608
+ @JRubyMethod(name = "write_large_object", required = 6)
1609
+ public IRubyObject write_large_object(final ThreadContext context, final IRubyObject[] args)
1610
+ throws SQLException {
1611
+
1612
+ final boolean binary = args[0].isTrue();
1613
+ final String columnName = args[1].toString();
1614
+ final String tableName = args[2].toString();
1615
+ final String idKey = args[3].toString();
1616
+ final IRubyObject idVal = args[4];
1617
+ final IRubyObject lobValue = args[5];
1618
+
1619
+ int count = updateLobValue(context, tableName, columnName, null, idKey, idVal, null, lobValue, binary);
1620
+ return context.runtime.newFixnum(count);
1621
+ }
1622
+
1623
+ @JRubyMethod(name = "update_lob_value", required = 3)
1624
+ public IRubyObject update_lob_value(final ThreadContext context,
1625
+ final IRubyObject record, final IRubyObject column, final IRubyObject value)
1626
+ throws SQLException {
1627
+
1628
+ final boolean binary = // column.type == :binary
1629
+ column.callMethod(context, "type").toString() == (Object) "binary";
1630
+
1631
+ final IRubyObject recordClass = record.callMethod(context, "class");
1632
+ final IRubyObject adapter = recordClass.callMethod(context, "connection");
1633
+
1634
+ IRubyObject columnName = column.callMethod(context, "name");
1635
+ columnName = adapter.callMethod(context, "quote_column_name", columnName);
1636
+ IRubyObject tableName = recordClass.callMethod(context, "table_name");
1637
+ tableName = adapter.callMethod(context, "quote_table_name", tableName);
1638
+ final IRubyObject idKey = recordClass.callMethod(context, "primary_key"); // 'id'
1639
+ // callMethod(context, "quote", primaryKey);
1640
+ final IRubyObject idColumn = // record.class.columns_hash['id']
1641
+ recordClass.callMethod(context, "columns_hash").callMethod(context, "[]", idKey);
1642
+
1643
+ final IRubyObject id = record.callMethod(context, "id"); // record.id
1644
+
1645
+ final int count = updateLobValue(context,
1646
+ tableName.toString(), columnName.toString(), column,
1647
+ idKey.toString(), id, idColumn, value, binary
1648
+ );
1649
+ return context.runtime.newFixnum(count);
1650
+ }
1651
+
1652
+ private int updateLobValue(final ThreadContext context,
1653
+ final String tableName, final String columnName, final IRubyObject column,
1654
+ final String idKey, final IRubyObject idValue, final IRubyObject idColumn,
1655
+ final IRubyObject value, final boolean binary) {
1656
+
1657
+ final String sql = "UPDATE "+ tableName +" SET "+ columnName +" = ? WHERE "+ idKey +" = ?" ;
1658
+
1659
+ // TODO: Fix this, the columns don't have the info needed to handle this anymore
1660
+ // currently commented out so that it will compile
1661
+
1662
+ return withConnection(context, new Callable<Integer>() {
1663
+ public Integer call(final Connection connection) throws SQLException {
1664
+ PreparedStatement statement = null;
1665
+ try {
1666
+ statement = connection.prepareStatement(sql);
1667
+ /*
1668
+ if ( binary ) { // blob
1669
+ setBlobParameter(context, connection, statement, 1, value, column, Types.BLOB);
1670
+ }
1671
+ else { // clob
1672
+ setClobParameter(context, connection, statement, 1, value, column, Types.CLOB);
1673
+ }
1674
+ setStatementParameter(context, context.runtime, connection, statement, 2, idValue, idColumn);
1675
+ */
1676
+ return statement.executeUpdate();
1677
+ }
1678
+ finally { close(statement); }
1679
+ }
1680
+ });
1681
+ }
1682
+
1683
+ protected String caseConvertIdentifierForRails(final Connection connection, final String value)
1684
+ throws SQLException {
1685
+ if ( value == null ) return null;
1686
+ return caseConvertIdentifierForRails(connection.getMetaData(), value);
1687
+ }
1688
+
1689
+ /**
1690
+ * Convert an identifier coming back from the database to a case which Rails is expecting.
1691
+ *
1692
+ * Assumption: Rails identifiers will be quoted for mixed or will stay mixed
1693
+ * as identifier names in Rails itself. Otherwise, they expect identifiers to
1694
+ * be lower-case. Databases which store identifiers uppercase should be made
1695
+ * lower-case.
1696
+ *
1697
+ * Assumption 2: It is always safe to convert all upper case names since it appears that
1698
+ * some adapters do not report StoresUpper/Lower/Mixed correctly (am I right postgres/mysql?).
1699
+ */
1700
+ protected static String caseConvertIdentifierForRails(final DatabaseMetaData metaData, final String value)
1701
+ throws SQLException {
1702
+ if ( value == null ) return null;
1703
+ return metaData.storesUpperCaseIdentifiers() ? value.toLowerCase() : value;
1704
+ }
1705
+
1706
+ protected String caseConvertIdentifierForJdbc(final Connection connection, final String value)
1707
+ throws SQLException {
1708
+ if ( value == null ) return null;
1709
+ return caseConvertIdentifierForJdbc(connection.getMetaData(), value);
1710
+ }
1711
+
1712
+ /**
1713
+ * Convert an identifier destined for a method which cares about the databases internal
1714
+ * storage case. Methods like DatabaseMetaData.getPrimaryKeys() needs the table name to match
1715
+ * the internal storage name. Arbitrary queries and the like DO NOT need to do this.
1716
+ */
1717
+ protected static String caseConvertIdentifierForJdbc(final DatabaseMetaData metaData, final String value)
1718
+ throws SQLException {
1719
+ if ( value == null ) return null;
1720
+
1721
+ if ( metaData.storesUpperCaseIdentifiers() ) {
1722
+ return value.toUpperCase();
1723
+ }
1724
+ else if ( metaData.storesLowerCaseIdentifiers() ) {
1725
+ return value.toLowerCase();
1726
+ }
1727
+ return value;
1728
+ }
1729
+
1730
+ // internal helper exported on ArJdbc @JRubyMethod(meta = true)
1731
+ public static IRubyObject with_meta_data_from_data_source_if_any(final ThreadContext context,
1732
+ final IRubyObject self, final IRubyObject config, final Block block) {
1733
+ final IRubyObject ds_or_name = rawDataSourceOrName(context, config);
1734
+
1735
+ if ( ds_or_name == null ) return context.runtime.getFalse();
1736
+
1737
+ final javax.sql.DataSource dataSource;
1738
+ final Object dsOrName = ds_or_name.toJava(Object.class);
1739
+ if ( dsOrName instanceof javax.sql.DataSource ) {
1740
+ dataSource = (javax.sql.DataSource) dsOrName;
1741
+ }
1742
+ else {
1743
+ try {
1744
+ dataSource = (javax.sql.DataSource) getInitialContext().lookup( dsOrName.toString() );
1745
+ }
1746
+ catch (Exception e) { // javax.naming.NamingException
1747
+ throw wrapException(context, context.runtime.getRuntimeError(), e);
1748
+ }
1749
+ }
1750
+
1751
+ Connection connection = null;
1752
+ try {
1753
+ connection = dataSource.getConnection();
1754
+ final DatabaseMetaData metaData = connection.getMetaData();
1755
+ return block.call(context, JavaUtil.convertJavaToRuby(context.runtime, metaData));
1756
+ }
1757
+ catch (SQLException e) {
1758
+ throw wrapSQLException(context, getJDBCError(context.runtime), e, null);
1759
+ }
1760
+ finally { close(connection); }
1761
+ }
1762
+
1763
+ @JRubyMethod(name = "jndi_config?", meta = true)
1764
+ public static IRubyObject jndi_config_p(final ThreadContext context,
1765
+ final IRubyObject self, final IRubyObject config) {
1766
+ return context.runtime.newBoolean( isJndiConfig(context, config) );
1767
+ }
1768
+
1769
+ private static IRubyObject rawDataSourceOrName(final ThreadContext context, final IRubyObject config) {
1770
+ // config[:jndi] || config[:data_source]
1771
+
1772
+ final Ruby runtime = context.runtime;
1773
+
1774
+ IRubyObject configValue;
1775
+
1776
+ if ( config.getClass() == RubyHash.class ) { // "optimized" version
1777
+ final RubyHash configHash = ((RubyHash) config);
1778
+ configValue = configHash.fastARef(runtime.newSymbol("jndi"));
1779
+ if ( configValue == null ) {
1780
+ configValue = configHash.fastARef(runtime.newSymbol("data_source"));
1781
+ }
1782
+ }
1783
+ else {
1784
+ configValue = config.callMethod(context, "[]", runtime.newSymbol("jndi"));
1785
+ if ( configValue == context.nil ) configValue = null;
1786
+ if ( configValue == null ) {
1787
+ configValue = config.callMethod(context, "[]", runtime.newSymbol("data_source"));
1788
+ }
1789
+ }
1790
+
1791
+ if ( configValue == null || configValue == context.nil || configValue == runtime.getFalse() ) {
1792
+ return null;
1793
+ }
1794
+ return configValue;
1795
+ }
1796
+
1797
+ private static boolean isJndiConfig(final ThreadContext context, final IRubyObject config) {
1798
+ return rawDataSourceOrName(context, config) != null;
1799
+ }
1800
+
1801
+ @JRubyMethod(name = "jndi_lookup", meta = true)
1802
+ public static IRubyObject jndi_lookup(final ThreadContext context,
1803
+ final IRubyObject self, final IRubyObject name) {
1804
+ try {
1805
+ final Object bound = getInitialContext().lookup( name.toString() );
1806
+ return JavaUtil.convertJavaToRuby(context.runtime, bound);
1807
+ }
1808
+ catch (Exception e) { // javax.naming.NamingException
1809
+ if ( e instanceof RaiseException ) throw (RaiseException) e;
1810
+ throw wrapException(context, context.runtime.getNameError(), e);
1811
+ }
1812
+ }
1813
+
1814
+ @Deprecated
1815
+ @JRubyMethod(name = "setup_jdbc_factory", visibility = Visibility.PROTECTED)
1816
+ public IRubyObject set_driver_factory(final ThreadContext context) {
1817
+ setDriverFactory(context);
1818
+ return get_connection_factory(context.runtime);
1819
+ }
1820
+
1821
+ private ConnectionFactory setDriverFactory(final ThreadContext context) {
1822
+
1823
+ final IRubyObject url = getConfigValue(context, "url");
1824
+ final IRubyObject driver = getConfigValue(context, "driver");
1825
+ final IRubyObject username = getConfigValue(context, "username");
1826
+ final IRubyObject password = getConfigValue(context, "password");
1827
+
1828
+ final IRubyObject driver_instance = getConfigValue(context, "driver_instance");
1829
+
1830
+ if ( url.isNil() || ( driver.isNil() && driver_instance.isNil() ) ) {
1831
+ final Ruby runtime = context.runtime;
1832
+ final RubyClass errorClass = getConnectionNotEstablished( runtime );
1833
+ throw runtime.newRaiseException(errorClass, "adapter requires :driver class and jdbc :url");
1834
+ }
1835
+
1836
+ final String jdbcURL = buildURL(context, url);
1837
+ final ConnectionFactory factory;
1838
+
1839
+ if ( driver_instance != null && ! driver_instance.isNil() ) {
1840
+ final Object driverInstance = driver_instance.toJava(Object.class);
1841
+ if ( driverInstance instanceof DriverWrapper ) {
1842
+ setConnectionFactory(factory = new DriverConnectionFactory(
1843
+ (DriverWrapper) driverInstance, jdbcURL,
1844
+ ( username.isNil() ? null : username.toString() ),
1845
+ ( password.isNil() ? null : password.toString() )
1846
+ ));
1847
+ return factory;
1848
+ }
1849
+ else {
1850
+ setConnectionFactory(factory = new RubyConnectionFactory(
1851
+ driver_instance, context.runtime.newString(jdbcURL),
1852
+ ( username.isNil() ? username : username.asString() ),
1853
+ ( password.isNil() ? password : password.asString() )
1854
+ ));
1855
+ return factory;
1856
+ }
1857
+ }
1858
+
1859
+ final String user = username.isNil() ? null : username.toString();
1860
+ final String pass = password.isNil() ? null : password.toString();
1861
+
1862
+ final DriverWrapper driverWrapper = newDriverWrapper(context, driver.toString());
1863
+ setConnectionFactory(factory = new DriverConnectionFactory(driverWrapper, jdbcURL, user, pass));
1864
+ return factory;
1865
+ }
1866
+
1867
+ protected DriverWrapper newDriverWrapper(final ThreadContext context, final String driver) throws RaiseException {
1868
+ try {
1869
+ return new DriverWrapper(context.runtime, driver, resolveDriverProperties(context));
1870
+ }
1871
+ //catch (ClassNotFoundException e) {
1872
+ // throw wrapException(context, context.runtime.getNameError(), e, "cannot load driver class " + driver);
1873
+ //}
1874
+ catch (ExceptionInInitializerError e) {
1875
+ throw wrapException(context, context.runtime.getNameError(), e, "cannot initialize driver class " + driver);
1876
+ }
1877
+ catch (LinkageError e) {
1878
+ throw wrapException(context, context.runtime.getNameError(), e, "cannot link driver class " + driver);
1879
+ }
1880
+ catch (ClassCastException e) {
1881
+ throw wrapException(context, context.runtime.getNameError(), e);
1882
+ }
1883
+ catch (IllegalAccessException e) { throw wrapException(context, e); }
1884
+ catch (InstantiationException e) {
1885
+ throw wrapException(context, e.getCause() != null ? e.getCause() : e);
1886
+ }
1887
+ catch (SecurityException e) {
1888
+ throw wrapException(context, context.runtime.getSecurityError(), e);
1889
+ }
1890
+ }
1891
+
1892
+ @Deprecated // no longer used - only kept for API compatibility
1893
+ @JRubyMethod(visibility = Visibility.PRIVATE)
1894
+ public IRubyObject jdbc_url(final ThreadContext context) {
1895
+ final IRubyObject url = getConfigValue(context, "url");
1896
+ return context.runtime.newString( buildURL(context, url) );
1897
+ }
1898
+
1899
+ protected String buildURL(final ThreadContext context, final IRubyObject url) {
1900
+ IRubyObject options = getConfigValue(context, "options");
1901
+ if ( options == context.nil ) options = null;
1902
+ // NOTE: else should print a deprecation warning - should use properties: instead
1903
+ return DriverWrapper.buildURL(url, (Map) options);
1904
+ }
1905
+
1906
+ private Properties resolveDriverProperties(final ThreadContext context) {
1907
+ IRubyObject properties = getConfigValue(context, "properties");
1908
+ if ( properties == context.nil ) return null;
1909
+ Map<?, ?> propertiesJava = (Map) properties.toJava(Map.class);
1910
+ if ( propertiesJava instanceof Properties ) {
1911
+ return (Properties) propertiesJava;
1912
+ }
1913
+ final Properties props = new Properties();
1914
+ for ( Map.Entry entry : propertiesJava.entrySet() ) {
1915
+ props.setProperty(entry.getKey().toString(), entry.getValue().toString());
1916
+ }
1917
+ return props;
1918
+ }
1919
+
1920
+ @JRubyMethod(name = "setup_jndi_factory", visibility = Visibility.PROTECTED)
1921
+ public IRubyObject set_data_source_factory(final ThreadContext context) {
1922
+ setDataSourceFactory(context);
1923
+ return get_connection_factory(context.runtime);
1924
+ }
1925
+
1926
+ private ConnectionFactory setDataSourceFactory(final ThreadContext context) {
1927
+ final javax.sql.DataSource dataSource; final String lookupName;
1928
+ IRubyObject value = getConfigValue(context, "data_source");
1929
+ if ( value == context.nil ) {
1930
+ value = getConfigValue(context, "jndi");
1931
+ lookupName = value.toString();
1932
+ dataSource = DataSourceConnectionFactory.lookupDataSource(context, lookupName);
1933
+ }
1934
+ else {
1935
+ dataSource = (javax.sql.DataSource) value.toJava(javax.sql.DataSource.class);
1936
+ lookupName = null;
1937
+ }
1938
+ ConnectionFactory factory = new DataSourceConnectionFactory(dataSource, lookupName);
1939
+ setConnectionFactory(factory);
1940
+ return factory;
1941
+ }
1942
+
1943
+ private static transient IRubyObject defaultConfig;
1944
+ private static volatile boolean defaultConfigJndi;
1945
+ private static transient ConnectionFactory defaultConnectionFactory;
1946
+
1947
+ /**
1948
+ * Sets the connection factory from the available configuration.
1949
+ * @param context
1950
+ * @see #initialize
1951
+ */
1952
+ @Deprecated
1953
+ @JRubyMethod(name = "setup_connection_factory", visibility = Visibility.PROTECTED)
1954
+ public IRubyObject setup_connection_factory(final ThreadContext context) {
1955
+ setupConnectionFactory(context);
1956
+ return get_connection_factory(context.runtime);
1957
+ }
1958
+
1959
+ private IRubyObject get_connection_factory(final Ruby runtime) {
1960
+ return JavaUtil.convertJavaToRuby(runtime, connectionFactory);
1961
+ }
1962
+
1963
+ /**
1964
+ * @return whether the connection factory is JNDI based
1965
+ */
1966
+ private boolean setupConnectionFactory(final ThreadContext context) {
1967
+ final IRubyObject config = getConfig();
1968
+
1969
+ if ( defaultConfig == null ) {
1970
+ synchronized(RubyJdbcConnection.class) {
1971
+ if ( defaultConfig == null ) {
1972
+ final boolean jndi = isJndiConfig(context, config);
1973
+ if ( jndi ) {
1974
+ defaultConnectionFactory = setDataSourceFactory(context);
1975
+ }
1976
+ else {
1977
+ defaultConnectionFactory = setDriverFactory(context);
1978
+ }
1979
+ defaultConfigJndi = jndi; defaultConfig = config;
1980
+ return jndi;
1981
+ }
1982
+ }
1983
+ }
1984
+
1985
+ if ( defaultConfig != null && ( defaultConfig == config || defaultConfig.eql(config) ) ) {
1986
+ setConnectionFactory( defaultConnectionFactory );
1987
+ return defaultConfigJndi;
1988
+ }
1989
+
1990
+ if ( isJndiConfig(context, config) ) {
1991
+ setDataSourceFactory(context); return true;
1992
+ }
1993
+ else {
1994
+ setDriverFactory(context); return false;
1995
+ }
1996
+ }
1997
+
1998
+ @JRubyMethod(name = "jndi?", alias = "jndi_connection?")
1999
+ public RubyBoolean jndi_p(final ThreadContext context) {
2000
+ return context.runtime.newBoolean( isJndi() );
2001
+ }
2002
+
2003
+ protected boolean isJndi() { return this.jndi; }
2004
+
2005
+ @JRubyMethod(name = "config")
2006
+ public IRubyObject config() { return getConfig(); }
2007
+
2008
+ public IRubyObject getConfig() { return this.config; }
2009
+
2010
+ protected final IRubyObject getConfigValue(final ThreadContext context, final String key) {
2011
+ final IRubyObject config = getConfig();
2012
+ final RubySymbol keySym = context.runtime.newSymbol(key);
2013
+ if ( config instanceof RubyHash ) {
2014
+ final IRubyObject value = ((RubyHash) config).fastARef(keySym);
2015
+ return value == null ? context.nil : value;
2016
+ }
2017
+ return config.callMethod(context, "[]", keySym);
2018
+ }
2019
+
2020
+ protected final IRubyObject setConfigValue(final ThreadContext context,
2021
+ final String key, final IRubyObject value) {
2022
+ final IRubyObject config = getConfig();
2023
+ final RubySymbol keySym = context.runtime.newSymbol(key);
2024
+ if ( config instanceof RubyHash ) {
2025
+ return ((RubyHash) config).op_aset(context, keySym, value);
2026
+ }
2027
+ return config.callMethod(context, "[]=", new IRubyObject[] { keySym, value });
2028
+ }
2029
+
2030
+ protected final IRubyObject setConfigValueIfNotSet(final ThreadContext context,
2031
+ final String key, final IRubyObject value) {
2032
+ final IRubyObject config = getConfig();
2033
+ final RubySymbol keySym = context.runtime.newSymbol(key);
2034
+ if ( config instanceof RubyHash ) {
2035
+ final IRubyObject setValue = ((RubyHash) config).fastARef(keySym);
2036
+ if ( setValue != null ) return setValue;
2037
+ return ((RubyHash) config).op_aset(context, keySym, value);
2038
+ }
2039
+
2040
+ final IRubyObject setValue = config.callMethod(context, "[]", keySym);
2041
+ if ( setValue != context.nil ) return setValue;
2042
+ return config.callMethod(context, "[]=", new IRubyObject[] { keySym, value });
2043
+ }
2044
+
2045
+ private static String toStringOrNull(final IRubyObject arg) {
2046
+ return arg.isNil() ? null : arg.toString();
2047
+ }
2048
+
2049
+ protected final IRubyObject getAdapter() { return this.adapter; }
2050
+
2051
+ protected RubyClass getJdbcColumnClass(final ThreadContext context) {
2052
+ return (RubyClass) getAdapter().callMethod(context, "jdbc_column_class");
2053
+ }
2054
+
2055
+ protected ConnectionFactory getConnectionFactory() throws RaiseException {
2056
+ if ( connectionFactory == null ) {
2057
+ // NOTE: only for (backwards) compatibility (to be deleted) :
2058
+ IRubyObject connection_factory = getInstanceVariable("@connection_factory");
2059
+ if ( connection_factory == null ) {
2060
+ throw getRuntime().newRuntimeError("@connection_factory not set");
2061
+ }
2062
+ connectionFactory = (ConnectionFactory) connection_factory.toJava(ConnectionFactory.class);
2063
+ }
2064
+ return connectionFactory;
2065
+ }
2066
+
2067
+ public void setConnectionFactory(ConnectionFactory connectionFactory) {
2068
+ this.connectionFactory = connectionFactory;
2069
+ }
2070
+
2071
+ protected Connection newConnection() throws SQLException {
2072
+ return getConnectionFactory().newConnection();
2073
+ }
2074
+
2075
+ private static String[] getTypes(final IRubyObject typeArg) {
2076
+ if ( typeArg instanceof RubyArray ) {
2077
+ final RubyArray typesArr = (RubyArray) typeArg;
2078
+ final String[] types = new String[typesArr.size()];
2079
+ for ( int i = 0; i < types.length; i++ ) {
2080
+ types[i] = typesArr.eltInternal(i).toString();
2081
+ }
2082
+ return types;
2083
+ }
2084
+ return new String[] { typeArg.toString() }; // expect a RubyString
2085
+ }
2086
+
2087
+ /**
2088
+ * Maps a query result into a <code>ActiveRecord</code> result.
2089
+ * @param context
2090
+ * @param connection
2091
+ * @param resultSet
2092
+ * @param columns
2093
+ * @return expected to return a <code>ActiveRecord::Result</code>
2094
+ * @throws SQLException
2095
+ */
2096
+ protected IRubyObject mapToResult(final ThreadContext context, final Connection connection,
2097
+ final ResultSet resultSet, final ColumnData[] columns) throws SQLException {
2098
+ final Ruby runtime = context.runtime;
2099
+
2100
+ final RubyArray resultRows = runtime.newArray();
2101
+
2102
+ while (resultSet.next()) {
2103
+ resultRows.append(mapRow(context, runtime, columns, resultSet, this));
2104
+ }
2105
+
2106
+ return newResult(context, columns, resultRows);
2107
+ }
2108
+
2109
+ protected IRubyObject jdbcToRuby(
2110
+ final ThreadContext context, final Ruby runtime,
2111
+ final int column, final int type, final ResultSet resultSet)
2112
+ throws SQLException {
2113
+
2114
+ try {
2115
+ switch (type) {
2116
+ case Types.BLOB:
2117
+ case Types.BINARY:
2118
+ case Types.VARBINARY:
2119
+ case Types.LONGVARBINARY:
2120
+ return streamToRuby(context, runtime, resultSet, column);
2121
+ case Types.CLOB:
2122
+ case Types.NCLOB: // JDBC 4.0
2123
+ return readerToRuby(context, runtime, resultSet, column);
2124
+ case Types.LONGVARCHAR:
2125
+ case Types.LONGNVARCHAR: // JDBC 4.0
2126
+ return readerToRuby(context, runtime, resultSet, column);
2127
+ case Types.TINYINT:
2128
+ case Types.SMALLINT:
2129
+ case Types.INTEGER:
2130
+ return integerToRuby(context, runtime, resultSet, column);
2131
+ case Types.REAL:
2132
+ case Types.FLOAT:
2133
+ case Types.DOUBLE:
2134
+ return doubleToRuby(context, runtime, resultSet, column);
2135
+ case Types.BIGINT:
2136
+ return bigIntegerToRuby(context, runtime, resultSet, column);
2137
+ case Types.NUMERIC:
2138
+ case Types.DECIMAL:
2139
+ return decimalToRuby(context, runtime, resultSet, column);
2140
+ case Types.DATE:
2141
+ return dateToRuby(context, runtime, resultSet, column);
2142
+ case Types.TIME:
2143
+ return timeToRuby(context, runtime, resultSet, column);
2144
+ case Types.TIMESTAMP:
2145
+ return timestampToRuby(context, runtime, resultSet, column);
2146
+ case Types.BIT:
2147
+ return bitToRuby(context, runtime, resultSet, column);
2148
+ case Types.BOOLEAN:
2149
+ return booleanToRuby(context, runtime, resultSet, column);
2150
+ case Types.SQLXML: // JDBC 4.0
2151
+ return xmlToRuby(context, runtime, resultSet, column);
2152
+ case Types.ARRAY: // we handle JDBC Array into (Ruby) []
2153
+ return arrayToRuby(context, runtime, resultSet, column);
2154
+ case Types.NULL:
2155
+ return context.nil;
2156
+ // NOTE: (JDBC) exotic stuff just cause it's so easy with JRuby :)
2157
+ case Types.JAVA_OBJECT:
2158
+ case Types.OTHER:
2159
+ return objectToRuby(context, runtime, resultSet, column);
2160
+ // (default) String
2161
+ case Types.CHAR:
2162
+ case Types.VARCHAR:
2163
+ case Types.NCHAR: // JDBC 4.0
2164
+ case Types.NVARCHAR: // JDBC 4.0
2165
+ default:
2166
+ return stringToRuby(context, runtime, resultSet, column);
2167
+ }
2168
+ // NOTE: not mapped types :
2169
+ //case Types.DISTINCT:
2170
+ //case Types.STRUCT:
2171
+ //case Types.REF:
2172
+ //case Types.DATALINK:
2173
+ }
2174
+ catch (IOException e) {
2175
+ throw new SQLException(e.getMessage(), e);
2176
+ }
2177
+ }
2178
+
2179
+ /**
2180
+ * Converts an integer column into a Ruby integer.
2181
+ * @param context current thread context
2182
+ * @param runtime current thread context
2183
+ * @param resultSet the jdbc result set to pull the value from
2184
+ * @param column the index of the column to convert
2185
+ * @return RubyNil if NULL or RubyInteger if there is a value
2186
+ * @throws SQLException if it failes to retrieve the value from the result set
2187
+ */
2188
+ protected IRubyObject integerToRuby(final ThreadContext context,
2189
+ final Ruby runtime, final ResultSet resultSet, final int column)
2190
+ throws SQLException {
2191
+ final long value = resultSet.getLong(column);
2192
+ if ( value == 0 && resultSet.wasNull() ) return context.nil;
2193
+ return runtime.newFixnum(value);
2194
+ }
2195
+
2196
+ /**
2197
+ * Converts an double column into a Ruby integer.
2198
+ * @param context current thread context
2199
+ * @param runtime the ruby runtime
2200
+ * @param resultSet the jdbc result set to pull the value from
2201
+ * @param column the index of the column to convert
2202
+ * @return RubyNil if NULL or RubyInteger if there is a value
2203
+ * @throws SQLException if it failes to retrieve the value from the result set
2204
+ */
2205
+ protected IRubyObject doubleToRuby(final ThreadContext context,
2206
+ final Ruby runtime, final ResultSet resultSet, final int column)
2207
+ throws SQLException {
2208
+ final double value = resultSet.getDouble(column);
2209
+ if ( value == 0 && resultSet.wasNull() ) return context.nil;
2210
+ return runtime.newFloat(value);
2211
+ }
2212
+
2213
+ /**
2214
+ * Converts a string column into a Ruby string
2215
+ * @param context current thread context
2216
+ * @param runtime the ruby runtime
2217
+ * @param resultSet the jdbc result set to pull the value from
2218
+ * @param column the index of the column to convert
2219
+ * @return RubyNil if NULL or RubyString if there is a value
2220
+ * @throws SQLException if it failes to retrieve the value from the result set
2221
+ */
2222
+ protected IRubyObject stringToRuby(final ThreadContext context,
2223
+ final Ruby runtime, final ResultSet resultSet, final int column) throws SQLException {
2224
+ final String value = resultSet.getString(column);
2225
+ if ( value == null ) return context.nil;
2226
+ return newDefaultInternalString(runtime, value);
2227
+ }
2228
+
2229
+ protected static IRubyObject bytesToRubyString(final ThreadContext context,
2230
+ final Ruby runtime, final ResultSet resultSet, final int column)
2231
+ throws SQLException { // optimized String -> byte[]
2232
+ final byte[] value = resultSet.getBytes(column);
2233
+ if ( value == null ) return context.nil;
2234
+ return newDefaultInternalString(runtime, value);
2235
+ }
2236
+
2237
+ protected IRubyObject bigIntegerToRuby(final ThreadContext context,
2238
+ final Ruby runtime, final ResultSet resultSet, final int column) throws SQLException {
2239
+ final String value = resultSet.getString(column);
2240
+ if ( value == null ) return context.nil;
2241
+ return RubyBignum.bignorm(runtime, new BigInteger(value));
2242
+ }
2243
+
2244
+ protected IRubyObject decimalToRuby(final ThreadContext context,
2245
+ final Ruby runtime, final ResultSet resultSet, final int column) throws SQLException {
2246
+ final BigDecimal value = resultSet.getBigDecimal(column);
2247
+ if ( value == null ) return context.nil;
2248
+ return new org.jruby.ext.bigdecimal.RubyBigDecimal(runtime, value);
2249
+ }
2250
+
2251
+ protected static Boolean rawDateTime;
2252
+ static {
2253
+ final String dateTimeRaw = SafePropertyAccessor.getProperty("arjdbc.datetime.raw");
2254
+ if ( dateTimeRaw != null ) {
2255
+ rawDateTime = Boolean.parseBoolean(dateTimeRaw);
2256
+ }
2257
+ // NOTE: we do this since it will have a different value depending on
2258
+ // AR version - since 4.0 false by default otherwise will be true ...
2259
+ }
2260
+
2261
+ @JRubyMethod(name = "raw_date_time?", meta = true)
2262
+ public static IRubyObject useRawDateTime(final ThreadContext context, final IRubyObject self) {
2263
+ if ( rawDateTime == null ) return context.nil;
2264
+ return context.runtime.newBoolean( rawDateTime.booleanValue() );
2265
+ }
2266
+
2267
+ @JRubyMethod(name = "raw_date_time=", meta = true)
2268
+ public static IRubyObject setRawDateTime(final IRubyObject self, final IRubyObject value) {
2269
+ if ( value instanceof RubyBoolean ) {
2270
+ rawDateTime = ((RubyBoolean) value).isTrue();
2271
+ }
2272
+ else {
2273
+ rawDateTime = value.isNil() ? null : Boolean.TRUE;
2274
+ }
2275
+ return value;
2276
+ }
2277
+
2278
+ /**
2279
+ * @return AR::Type-casted value
2280
+ * @since 1.3.18
2281
+ */
2282
+ @Deprecated
2283
+ protected static IRubyObject typeCastFromDatabase(final ThreadContext context,
2284
+ final IRubyObject adapter, final RubySymbol typeName, final RubyString value) {
2285
+ final IRubyObject type = adapter.callMethod(context, "lookup_cast_type", typeName);
2286
+ return type.callMethod(context, "deserialize", value);
2287
+ }
2288
+
2289
+ protected IRubyObject dateToRuby(final ThreadContext context,
2290
+ final Ruby runtime, final ResultSet resultSet, final int column)
2291
+ throws SQLException {
2292
+
2293
+ final Date value = resultSet.getDate(column);
2294
+ if ( value == null ) {
2295
+ // FIXME: Do we really need this wasNull check here?
2296
+ return resultSet.wasNull() ? context.nil : RubyString.newEmptyString(runtime);
2297
+ }
2298
+
2299
+ if ( rawDateTime != null && rawDateTime.booleanValue() ) {
2300
+ return RubyString.newString(runtime, DateTimeUtils.dateToString(value));
2301
+ }
2302
+
2303
+ return DateTimeUtils.newDateAsTime(context, value, null).callMethod(context, "to_date");
2304
+ }
2305
+
2306
+ protected IRubyObject timeToRuby(final ThreadContext context,
2307
+ final Ruby runtime, final ResultSet resultSet, final int column)
2308
+ throws SQLException {
2309
+
2310
+ final Time value = resultSet.getTime(column);
2311
+ if ( value == null ) {
2312
+ return resultSet.wasNull() ? context.nil : RubyString.newEmptyString(runtime);
2313
+ }
2314
+
2315
+ if ( rawDateTime != null && rawDateTime.booleanValue() ) {
2316
+ return RubyString.newString(runtime, DateTimeUtils.timeToString(value));
2317
+ }
2318
+
2319
+ return DateTimeUtils.newDummyTime(context, value, getDefaultTimeZone(context));
2320
+ }
2321
+
2322
+ protected IRubyObject timestampToRuby(final ThreadContext context,
2323
+ final Ruby runtime, final ResultSet resultSet, final int column)
2324
+ throws SQLException {
2325
+
2326
+ final Timestamp value = resultSet.getTimestamp(column);
2327
+ if ( value == null ) {
2328
+ return resultSet.wasNull() ? context.nil : RubyString.newEmptyString(runtime);
2329
+ }
2330
+
2331
+ if ( rawDateTime != null && rawDateTime.booleanValue() ) {
2332
+ return RubyString.newString(runtime, DateTimeUtils.timestampToString(value));
2333
+ }
2334
+
2335
+ // NOTE: with 'raw' String AR's Type::DateTime does put the time in proper time-zone
2336
+ // while when returning a Time object it just adjusts usec (apply_seconds_precision)
2337
+ // yet for custom SELECTs to work (SELECT created_at ... ) and for compatibility we
2338
+ // should be returning Time (by default) - AR does this by adjusting mysql2/pg returns
2339
+
2340
+ return DateTimeUtils.newTime(context, value, getDefaultTimeZone(context));
2341
+ }
2342
+
2343
+ @Deprecated
2344
+ protected static RubyString timestampToRubyString(final Ruby runtime, String value) {
2345
+ // Timestamp's format: yyyy-mm-dd hh:mm:ss.fffffffff
2346
+ String suffix; // assumes java.sql.Timestamp internals :
2347
+ if ( value.endsWith( suffix = " 00:00:00.0" ) ) {
2348
+ value = value.substring( 0, value.length() - suffix.length() );
2349
+ }
2350
+ else if ( value.endsWith( suffix = ".0" ) ) {
2351
+ value = value.substring( 0, value.length() - suffix.length() );
2352
+ }
2353
+ return RubyString.newUnicodeString(runtime, value);
2354
+ }
2355
+
2356
+ protected static Boolean rawBoolean;
2357
+ static {
2358
+ final String booleanRaw = SafePropertyAccessor.getProperty("arjdbc.boolean.raw");
2359
+ if ( booleanRaw != null ) {
2360
+ rawBoolean = Boolean.parseBoolean(booleanRaw);
2361
+ }
2362
+ }
2363
+
2364
+ @JRubyMethod(name = "raw_boolean?", meta = true)
2365
+ public static IRubyObject useRawBoolean(final ThreadContext context, final IRubyObject self) {
2366
+ if ( rawBoolean == null ) return context.nil;
2367
+ return context.runtime.newBoolean( rawBoolean.booleanValue() );
2368
+ }
2369
+
2370
+ @JRubyMethod(name = "raw_boolean=", meta = true)
2371
+ public static IRubyObject setRawBoolean(final IRubyObject self, final IRubyObject value) {
2372
+ if ( value instanceof RubyBoolean ) {
2373
+ rawBoolean = ((RubyBoolean) value).isTrue();
2374
+ }
2375
+ else {
2376
+ rawBoolean = value.isNil() ? null : Boolean.TRUE;
2377
+ }
2378
+ return value;
2379
+ }
2380
+
2381
+ /**
2382
+ * Converts a bit column to its Ruby equivalent.
2383
+ * Defaults to treating it as a boolean value.
2384
+ * @param context current thread context
2385
+ * @param runtime current instance of Ruby.
2386
+ * @param resultSet the jdbc result set to pull the value from
2387
+ * @param column the index of the column to convert
2388
+ * @return RubyNil if NULL or RubyBoolean if there is a value
2389
+ * @throws SQLException if it failes to retrieve the value from the result set
2390
+ */
2391
+ protected IRubyObject bitToRuby(final ThreadContext context, final Ruby runtime, final ResultSet resultSet,
2392
+ int column) throws SQLException {
2393
+ return booleanToRuby(context, runtime, resultSet, column);
2394
+ }
2395
+
2396
+ protected IRubyObject booleanToRuby(final ThreadContext context,
2397
+ final Ruby runtime, final ResultSet resultSet, final int column)
2398
+ throws SQLException {
2399
+ if ( rawBoolean != null && rawBoolean.booleanValue() ) {
2400
+ final String value = resultSet.getString(column);
2401
+ if ( value == null /* && resultSet.wasNull() */ ) return context.nil;
2402
+ return RubyString.newUnicodeString(runtime, value);
2403
+ }
2404
+ final boolean value = resultSet.getBoolean(column);
2405
+ if ( value == false && resultSet.wasNull() ) return context.nil;
2406
+ return runtime.newBoolean(value);
2407
+ }
2408
+
2409
+ protected static int streamBufferSize = 1024;
2410
+
2411
+ protected IRubyObject streamToRuby(final ThreadContext context,
2412
+ final Ruby runtime, final ResultSet resultSet, final int column)
2413
+ throws SQLException, IOException {
2414
+ final InputStream stream = resultSet.getBinaryStream(column);
2415
+
2416
+ if (stream == null) return context.nil;
2417
+
2418
+ try {
2419
+
2420
+ final int buffSize = streamBufferSize;
2421
+ final ByteList bytes = new ByteList(buffSize);
2422
+
2423
+ readBytes(bytes, stream, buffSize);
2424
+
2425
+ return runtime.newString(bytes);
2426
+
2427
+ } finally {
2428
+ stream.close();
2429
+ }
2430
+ }
2431
+
2432
+ /**
2433
+ * Converts a column that is handled as a Reader object into a Ruby string
2434
+ * @param context current thread context
2435
+ * @param runtime the ruby runtime
2436
+ * @param resultSet the jdbc result set to pull the value from
2437
+ * @param column the index of the column to convert
2438
+ * @return RubyNil if NULL or RubyString if there is a value
2439
+ * @throws SQLException if it failes to retrieve the value from the result set
2440
+ */
2441
+ protected IRubyObject readerToRuby(final ThreadContext context,
2442
+ final Ruby runtime, final ResultSet resultSet, final int column)
2443
+ throws SQLException, IOException {
2444
+ final Reader reader = resultSet.getCharacterStream(column);
2445
+ try {
2446
+ if ( reader == null ) return context.nil;
2447
+
2448
+ final int bufSize = streamBufferSize;
2449
+ final StringBuilder string = new StringBuilder(bufSize);
2450
+
2451
+ final char[] buf = new char[bufSize];
2452
+ for (int len = reader.read(buf); len != -1; len = reader.read(buf)) {
2453
+ string.append(buf, 0, len);
2454
+ }
2455
+
2456
+ return newDefaultInternalString(runtime, string);
2457
+ }
2458
+ finally { if ( reader != null ) reader.close(); }
2459
+ }
2460
+
2461
+
2462
+ /**
2463
+ * Converts the column into a RubyObject
2464
+ * @param context current thread context
2465
+ * @param runtime the ruby runtime
2466
+ * @param resultSet the jdbc result set to pull the value from
2467
+ * @param column the index of the column to convert
2468
+ * @return RubyNil if NULL or RubyObject if there is a value
2469
+ * @throws SQLException if it failes to retrieve the value from the result set
2470
+ */
2471
+ protected IRubyObject objectToRuby(final ThreadContext context,
2472
+ final Ruby runtime, final ResultSet resultSet, final int column)
2473
+ throws SQLException {
2474
+ final Object value = resultSet.getObject(column);
2475
+
2476
+ if ( value == null ) return context.nil;
2477
+
2478
+ return JavaUtil.convertJavaToRuby(runtime, value);
2479
+ }
2480
+
2481
+ protected IRubyObject arrayToRuby(final ThreadContext context,
2482
+ final Ruby runtime, final ResultSet resultSet, final int column)
2483
+ throws SQLException {
2484
+ final Array value = resultSet.getArray(column);
2485
+ try {
2486
+ if ( value == null ) return context.nil;
2487
+
2488
+ final RubyArray array = runtime.newArray();
2489
+
2490
+ final ResultSet arrayResult = value.getResultSet(); // 1: index, 2: value
2491
+ final int baseType = value.getBaseType();
2492
+
2493
+ if (baseType == Types.OTHER) {
2494
+ /*
2495
+ * If the base type is other, we may not have enough
2496
+ * information to correctly convert it so return it
2497
+ * as a string so it can be parsed on the Ruby side.
2498
+ * If we send it back as an array, AR assumes it has already
2499
+ * been parsed and doesn't try to cast the values inside the array.
2500
+ * This is primarly due to not being able to recognize json
2501
+ * strings in postgres but would apply to any custom type that couldn't be converted.
2502
+ * This won't work for multi-dimensional arrays of type other, but since
2503
+ * we currently don't support them that shouldn't be a problem.
2504
+ */
2505
+ return stringToRuby(context, runtime, resultSet, column);
2506
+ }
2507
+
2508
+ while ( arrayResult.next() ) {
2509
+ array.append( jdbcToRuby(context, runtime, 2, baseType, arrayResult) );
2510
+ }
2511
+ arrayResult.close();
2512
+
2513
+ return array;
2514
+ }
2515
+ finally { if ( value != null ) value.free(); }
2516
+ }
2517
+
2518
+ /**
2519
+ * Converts an XML column into a Ruby string
2520
+ * @param context current thread context
2521
+ * @param runtime the ruby runtime
2522
+ * @param resultSet the jdbc result set to pull the value from
2523
+ * @param column the index of the column to convert
2524
+ * @return RubyNil if NULL or RubyString if there is a value
2525
+ * @throws SQLException if it failes to retrieve the value from the result set
2526
+ */
2527
+ protected IRubyObject xmlToRuby(final ThreadContext context,
2528
+ final Ruby runtime, final ResultSet resultSet, final int column)
2529
+ throws SQLException {
2530
+ final SQLXML xml = resultSet.getSQLXML(column);
2531
+ try {
2532
+ if ( xml == null ) return context.nil;
2533
+
2534
+ return RubyString.newInternalFromJavaExternal(runtime, xml.getString());
2535
+ }
2536
+ finally { if ( xml != null ) xml.free(); }
2537
+ }
2538
+
2539
+ protected void setStatementParameters(final ThreadContext context,
2540
+ final Connection connection, final PreparedStatement statement,
2541
+ final RubyArray binds) throws SQLException {
2542
+
2543
+ for ( int i = 0; i < binds.getLength(); i++ ) {
2544
+ setStatementParameter(context, connection, statement, i + 1, binds.eltInternal(i));
2545
+ }
2546
+ }
2547
+
2548
+ // Set the prepared statement attributes based on the passed in Attribute object
2549
+ protected void setStatementParameter(final ThreadContext context,
2550
+ final Connection connection, final PreparedStatement statement,
2551
+ final int index, IRubyObject attribute) throws SQLException {
2552
+
2553
+ //debugMessage(context, attribute);
2554
+ final int type = jdbcTypeForAttribute(context, attribute);
2555
+ IRubyObject value = valueForDatabase(context, attribute);
2556
+
2557
+ // All the set methods were calling this first so save a method call in the nil case
2558
+ if ( value == context.nil ) {
2559
+ statement.setNull(index, type);
2560
+ return;
2561
+ }
2562
+
2563
+ switch (type) {
2564
+ case Types.TINYINT:
2565
+ case Types.SMALLINT:
2566
+ case Types.INTEGER:
2567
+ setIntegerParameter(context, connection, statement, index, value, attribute, type);
2568
+ break;
2569
+ case Types.BIGINT:
2570
+ setBigIntegerParameter(context, connection, statement, index, value, attribute, type);
2571
+ break;
2572
+ case Types.REAL:
2573
+ case Types.FLOAT:
2574
+ case Types.DOUBLE:
2575
+ setDoubleParameter(context, connection, statement, index, value, attribute, type);
2576
+ break;
2577
+ case Types.NUMERIC:
2578
+ case Types.DECIMAL:
2579
+ setDecimalParameter(context, connection, statement, index, value, attribute, type);
2580
+ break;
2581
+ case Types.DATE:
2582
+ setDateParameter(context, connection, statement, index, value, attribute, type);
2583
+ break;
2584
+ case Types.TIME:
2585
+ setTimeParameter(context, connection, statement, index, value, attribute, type);
2586
+ break;
2587
+ case Types.TIMESTAMP:
2588
+ setTimestampParameter(context, connection, statement, index, value, attribute, type);
2589
+ break;
2590
+ case Types.BIT:
2591
+ case Types.BOOLEAN:
2592
+ setBooleanParameter(context, connection, statement, index, value, attribute, type);
2593
+ break;
2594
+ case Types.SQLXML:
2595
+ setXmlParameter(context, connection, statement, index, value, attribute, type);
2596
+ break;
2597
+ case Types.ARRAY:
2598
+ setArrayParameter(context, connection, statement, index, value, attribute, type);
2599
+ break;
2600
+ case Types.JAVA_OBJECT:
2601
+ case Types.OTHER:
2602
+ setObjectParameter(context, connection, statement, index, value, attribute, type);
2603
+ break;
2604
+ case Types.BINARY:
2605
+ case Types.VARBINARY:
2606
+ case Types.LONGVARBINARY:
2607
+ case Types.BLOB:
2608
+ setBlobParameter(context, connection, statement, index, value, attribute, type);
2609
+ break;
2610
+ case Types.CLOB:
2611
+ case Types.NCLOB: // JDBC 4.0
2612
+ setClobParameter(context, connection, statement, index, value, attribute, type);
2613
+ break;
2614
+ case Types.CHAR:
2615
+ case Types.VARCHAR:
2616
+ case Types.NCHAR: // JDBC 4.0
2617
+ case Types.NVARCHAR: // JDBC 4.0
2618
+ default:
2619
+ setStringParameter(context, connection, statement, index, value, attribute, type);
2620
+ }
2621
+ }
2622
+
2623
+ protected static final Map<String, Integer> JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
2624
+ static {
2625
+ JDBC_TYPE_FOR.put("string", Types.VARCHAR);
2626
+ JDBC_TYPE_FOR.put("text", Types.CLOB);
2627
+ JDBC_TYPE_FOR.put("integer", Types.INTEGER);
2628
+ JDBC_TYPE_FOR.put("float", Types.FLOAT);
2629
+ JDBC_TYPE_FOR.put("real", Types.REAL);
2630
+ JDBC_TYPE_FOR.put("decimal", Types.DECIMAL);
2631
+ JDBC_TYPE_FOR.put("date", Types.DATE);
2632
+ JDBC_TYPE_FOR.put("time", Types.TIME);
2633
+ JDBC_TYPE_FOR.put("datetime", Types.TIMESTAMP);
2634
+ JDBC_TYPE_FOR.put("timestamp", Types.TIMESTAMP);
2635
+ JDBC_TYPE_FOR.put("boolean", Types.BOOLEAN);
2636
+ JDBC_TYPE_FOR.put("array", Types.ARRAY);
2637
+ JDBC_TYPE_FOR.put("xml", Types.SQLXML);
2638
+
2639
+ // also mapping standard SQL names :
2640
+ JDBC_TYPE_FOR.put("bit", Types.BIT);
2641
+ JDBC_TYPE_FOR.put("tinyint", Types.TINYINT);
2642
+ JDBC_TYPE_FOR.put("smallint", Types.SMALLINT);
2643
+ JDBC_TYPE_FOR.put("bigint", Types.BIGINT);
2644
+ JDBC_TYPE_FOR.put("int", Types.INTEGER);
2645
+ JDBC_TYPE_FOR.put("double", Types.DOUBLE);
2646
+ JDBC_TYPE_FOR.put("numeric", Types.NUMERIC);
2647
+ JDBC_TYPE_FOR.put("char", Types.CHAR);
2648
+ JDBC_TYPE_FOR.put("varchar", Types.VARCHAR);
2649
+ JDBC_TYPE_FOR.put("binary", Types.BINARY);
2650
+ JDBC_TYPE_FOR.put("varbinary", Types.VARBINARY);
2651
+ //JDBC_TYPE_FOR.put("struct", Types.STRUCT);
2652
+ JDBC_TYPE_FOR.put("blob", Types.BLOB);
2653
+ JDBC_TYPE_FOR.put("clob", Types.CLOB);
2654
+ JDBC_TYPE_FOR.put("nchar", Types.NCHAR);
2655
+ JDBC_TYPE_FOR.put("nvarchar", Types.NVARCHAR);
2656
+ JDBC_TYPE_FOR.put("nclob", Types.NCLOB);
2657
+ }
2658
+
2659
+ protected int jdbcTypeForAttribute(final ThreadContext context,
2660
+ final IRubyObject attribute) throws SQLException {
2661
+
2662
+ final String internedType = internedTypeFor(context, attribute);
2663
+ final Integer sqlType = jdbcTypeFor(internedType);
2664
+ if ( sqlType != null ) {
2665
+ return sqlType.intValue();
2666
+ }
2667
+
2668
+ return Types.OTHER; // -1 as well as 0 are used in Types
2669
+ }
2670
+
2671
+ protected Integer jdbcTypeFor(final String type) {
2672
+ return JDBC_TYPE_FOR.get(type);
2673
+ }
2674
+
2675
+ // ActiveRecord::Attribute#type (mostly sub-classes e.g. ActiveRecord::Attribute::WithCastValue)
2676
+ protected static IRubyObject attributeType(final ThreadContext context, final IRubyObject attribute) {
2677
+ // NOTE: a piece of (premature) optimalization - cause we can and AR 5.x does not mind
2678
+ return ((RubyBasicObject) attribute).getInstanceVariable("@type"); // attribute.callMethod(context, "type");
2679
+ }
2680
+
2681
+ protected static IRubyObject attributeSQLType(final ThreadContext context, final IRubyObject attribute) {
2682
+ return attributeType(context, attribute).callMethod(context, "type");
2683
+ }
2684
+
2685
+ private final CachingCallSite value_site = new FunctionalCachingCallSite("value"); // AR::Attribute#value
2686
+
2687
+ protected String internedTypeFor(final ThreadContext context, final IRubyObject attribute) throws SQLException {
2688
+
2689
+ final IRubyObject type = attributeSQLType(context, attribute);
2690
+
2691
+ if ( type != context.nil ) {
2692
+ return mapTypeToString(type);
2693
+ }
2694
+
2695
+ final IRubyObject value = value_site.call(context, attribute, attribute);
2696
+
2697
+ if ( value instanceof RubyInteger ) {
2698
+ return "integer";
2699
+ }
2700
+
2701
+ if ( value instanceof RubyNumeric ) {
2702
+ return "float";
2703
+ }
2704
+
2705
+ if ( value instanceof RubyTime ) {
2706
+ return "timestamp";
2707
+ }
2708
+
2709
+ return "string";
2710
+ }
2711
+
2712
+ // to be overriden in child class for database specific types
2713
+ protected String mapTypeToString(final IRubyObject type) {
2714
+ final String typeStr = type.asJavaString();
2715
+
2716
+ return typeStr;
2717
+ }
2718
+
2719
+ protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final IRubyObject value) {
2720
+ return timeInDefaultTimeZone(context, DateTimeUtils.toTime(context, value));
2721
+ }
2722
+
2723
+ protected final RubyTime timeInDefaultTimeZone(final ThreadContext context, final RubyTime time) {
2724
+ final DateTimeZone defaultZone = getDefaultTimeZone(context);
2725
+ if (defaultZone == time.getDateTime().getZone()) return time;
2726
+ final DateTime adjustedDateTime = time.getDateTime().withZone(defaultZone);
2727
+ final RubyTime timeInDefaultTZ = new RubyTime(context.runtime, context.runtime.getTime(), adjustedDateTime);
2728
+ timeInDefaultTZ.setNSec(time.getNSec());
2729
+ return timeInDefaultTZ;
2730
+ }
2731
+
2732
+ protected final DateTime dateTimeInDefaultTimeZone(final ThreadContext context, final DateTime dateTime) {
2733
+ final DateTimeZone defaultZone = getDefaultTimeZone(context);
2734
+ if (defaultZone == dateTime.getZone()) return dateTime;
2735
+ return dateTime.withZone(defaultZone);
2736
+ }
2737
+
2738
+ public static RubyTime toTime(final ThreadContext context, final IRubyObject value) {
2739
+ return DateTimeUtils.toTime(context, value);
2740
+ }
2741
+
2742
+ protected boolean isDefaultTimeZoneUTC(final ThreadContext context) {
2743
+ return "utc".equalsIgnoreCase( default_timezone(context) );
2744
+ }
2745
+
2746
+ protected DateTimeZone getDefaultTimeZone(final ThreadContext context) {
2747
+ return isDefaultTimeZoneUTC(context) ? DateTimeZone.UTC : getLocalTimeZone(context.runtime); // handles ENV['TZ']
2748
+ }
2749
+
2750
+ private String default_timezone(final ThreadContext context) {
2751
+ final RubyClass base = getBase(context.runtime);
2752
+ return default_timezone.call(context, base, base).asJavaString(); // :utc (or :local)
2753
+ }
2754
+
2755
+ // ActiveRecord::Base.default_timezone
2756
+ private final CachingCallSite default_timezone = new FunctionalCachingCallSite("default_timezone");
2757
+
2758
+ protected void setIntegerParameter(final ThreadContext context,
2759
+ final Connection connection, final PreparedStatement statement,
2760
+ final int index, final IRubyObject value,
2761
+ final IRubyObject attribute, final int type) throws SQLException {
2762
+
2763
+ if ( value instanceof RubyBignum ) { // e.g. HSQLDB / H2 report JDBC type 4
2764
+ setBigIntegerParameter(context, connection, statement, index, (RubyBignum) value, attribute, type);
2765
+ }
2766
+ else if ( value instanceof RubyNumeric ) {
2767
+ statement.setLong(index, RubyNumeric.num2long(value));
2768
+ }
2769
+ else {
2770
+ statement.setLong(index, value.convertToInteger("to_i").getLongValue());
2771
+ }
2772
+ }
2773
+
2774
+ protected void setBigIntegerParameter(final ThreadContext context,
2775
+ final Connection connection, final PreparedStatement statement,
2776
+ final int index, final IRubyObject value,
2777
+ final IRubyObject attribute, final int type) throws SQLException {
2778
+
2779
+ if ( value instanceof RubyBignum ) {
2780
+ setLongOrDecimalParameter(statement, index, ((RubyBignum) value).getValue());
2781
+ }
2782
+ else if ( value instanceof RubyFixnum ) {
2783
+ statement.setLong(index, ((RubyFixnum) value).getLongValue());
2784
+ }
2785
+ else {
2786
+ setLongOrDecimalParameter(statement, index, value.convertToInteger("to_i").getBigIntegerValue());
2787
+ }
2788
+ }
2789
+
2790
+ protected static void setLongOrDecimalParameter(final PreparedStatement statement,
2791
+ final int index, final BigInteger value) throws SQLException {
2792
+
2793
+ if ( value.bitLength() <= 63 ) {
2794
+ statement.setLong(index, value.longValue());
2795
+ }
2796
+ else {
2797
+ statement.setBigDecimal(index, new BigDecimal(value));
2798
+ }
2799
+ }
2800
+
2801
+ protected void setDoubleParameter(final ThreadContext context,
2802
+ final Connection connection, final PreparedStatement statement,
2803
+ final int index, final IRubyObject value,
2804
+ final IRubyObject attribute, final int type) throws SQLException {
2805
+
2806
+ if ( value instanceof RubyNumeric ) {
2807
+ statement.setDouble(index, ((RubyNumeric) value).getDoubleValue());
2808
+ }
2809
+ else {
2810
+ statement.setDouble(index, value.convertToFloat().getDoubleValue());
2811
+ }
2812
+ }
2813
+
2814
+ protected void setDecimalParameter(final ThreadContext context,
2815
+ final Connection connection, final PreparedStatement statement,
2816
+ final int index, final IRubyObject value,
2817
+ final IRubyObject attribute, final int type) throws SQLException {
2818
+
2819
+ if (value instanceof RubyBigDecimal) {
2820
+ statement.setBigDecimal(index, ((RubyBigDecimal) value).getValue());
2821
+ }
2822
+ else if ( value instanceof RubyInteger ) {
2823
+ statement.setBigDecimal(index, new BigDecimal(((RubyInteger) value).getBigIntegerValue()));
2824
+ }
2825
+ else if ( value instanceof RubyNumeric ) {
2826
+ statement.setDouble(index, ((RubyNumeric) value).getDoubleValue());
2827
+ }
2828
+ else { // e.g. `BigDecimal '42.00000000000000000001'`
2829
+ statement.setBigDecimal(index,
2830
+ RubyBigDecimal.newInstance(context, context.runtime.getModule("BigDecimal"), value).getValue());
2831
+ }
2832
+ }
2833
+
2834
+ protected void setTimestampParameter(final ThreadContext context,
2835
+ final Connection connection, final PreparedStatement statement,
2836
+ final int index, IRubyObject value,
2837
+ final IRubyObject attribute, final int type) throws SQLException {
2838
+
2839
+ final RubyTime timeValue = DateTimeUtils.toTime(context, value);
2840
+ final DateTime dateTime = dateTimeInDefaultTimeZone(context, timeValue.getDateTime());
2841
+ final Timestamp timestamp = new Timestamp(dateTime.getMillis());
2842
+ // 1942-11-30T01:02:03.123_456
2843
+ if (timeValue.getNSec() > 0) timestamp.setNanos((int) (timestamp.getNanos() + timeValue.getNSec()));
2844
+
2845
+ statement.setTimestamp(index, timestamp, getCalendar(dateTime.getZone()));
2846
+ }
2847
+
2848
+ @Deprecated
2849
+ protected static Timestamp convertToTimestamp(final RubyFloat value) {
2850
+ return DateTimeUtils.convertToTimestamp(value);
2851
+ }
2852
+
2853
+ protected static Calendar getCalendar(final DateTimeZone zone) { // final java.util.Date hint
2854
+ if (DateTimeZone.UTC == zone) return getCalendarUTC();
2855
+ if (DateTimeZone.getDefault() == zone) return new GregorianCalendar();
2856
+ return getCalendarInstance( zone.getID() );
2857
+ }
2858
+
2859
+ private static Calendar getCalendarInstance(final String ID) {
2860
+ return Calendar.getInstance( TimeZone.getTimeZone(ID) );
2861
+ }
2862
+
2863
+ private static Calendar getCalendarUTC() {
2864
+ return getCalendarInstance("GMT");
2865
+ }
2866
+
2867
+ protected void setTimeParameter(final ThreadContext context,
2868
+ final Connection connection, final PreparedStatement statement,
2869
+ final int index, IRubyObject value,
2870
+ final IRubyObject attribute, final int type) throws SQLException {
2871
+
2872
+ final RubyTime timeValue = timeInDefaultTimeZone(context, value);
2873
+ final DateTime dateTime = timeValue.getDateTime();
2874
+ final Time time = new Time(dateTime.getMillis()); // has millis precision
2875
+
2876
+ statement.setTime(index, time, getCalendar(dateTime.getZone()));
2877
+
2878
+ //if ( value instanceof RubyString ) {
2879
+ // statement.setString(index, value.toString()); // assume local time-zone
2880
+ //}
2881
+ //else { // DateTime ( ActiveSupport::TimeWithZone.to_time )
2882
+ // final RubyFloat timeValue = value.convertToFloat(); // to_f
2883
+ // final Time time = new Time((long) timeValue.getDoubleValue() * 1000);
2884
+ // // java.sql.Time is expected to be only up to (milli) second precision
2885
+ // statement.setTime(index, time, getCalendarUTC());
2886
+ //}
2887
+ }
2888
+
2889
+ protected void setDateParameter(final ThreadContext context,
2890
+ final Connection connection, final PreparedStatement statement,
2891
+ final int index, IRubyObject value,
2892
+ final IRubyObject attribute, final int type) throws SQLException {
2893
+
2894
+ if ( ! "Date".equals(value.getMetaClass().getName()) && value.respondsTo("to_date") ) {
2895
+ value = value.callMethod(context, "to_date");
2896
+ }
2897
+
2898
+ // NOTE: assuming Date#to_s does right ...
2899
+ statement.setDate(index, Date.valueOf(value.toString()));
2900
+ }
2901
+
2902
+ protected void setBooleanParameter(final ThreadContext context,
2903
+ final Connection connection, final PreparedStatement statement,
2904
+ final int index, final IRubyObject value,
2905
+ final IRubyObject attribute, final int type) throws SQLException {
2906
+ statement.setBoolean(index, value.isTrue());
2907
+ }
2908
+
2909
+ protected void setStringParameter(final ThreadContext context,
2910
+ final Connection connection, final PreparedStatement statement,
2911
+ final int index, final IRubyObject value,
2912
+ final IRubyObject attribute, final int type) throws SQLException {
2913
+
2914
+ statement.setString(index, value.asString().toString());
2915
+ }
2916
+
2917
+ protected void setArrayParameter(final ThreadContext context,
2918
+ final Connection connection, final PreparedStatement statement,
2919
+ final int index, final IRubyObject value,
2920
+ final IRubyObject attribute, final int type) throws SQLException {
2921
+
2922
+ final String typeName = resolveArrayBaseTypeName(context, attribute);
2923
+ final IRubyObject valueForDB = value.callMethod(context, "values");
2924
+ Array array = connection.createArrayOf(typeName, ((RubyArray) valueForDB).toArray());
2925
+ statement.setArray(index, array);
2926
+ }
2927
+
2928
+ protected String resolveArrayBaseTypeName(final ThreadContext context, final IRubyObject attribute) throws SQLException {
2929
+
2930
+ // This shouldn't return nil at this point because we know we have an array typed attribute
2931
+ final RubySymbol type = (RubySymbol) attributeSQLType(context, attribute);
2932
+
2933
+ // For some reason the driver doesn't like "character varying" as a type
2934
+ if ( type.eql(context.runtime.newSymbol("string")) ) return "varchar";
2935
+
2936
+ final RubyHash nativeTypes = (RubyHash) getAdapter().callMethod(context, "native_database_types");
2937
+ // e.g. `integer: { name: 'integer' }`
2938
+ final RubyHash typeInfo = (RubyHash) nativeTypes.op_aref(context, type);
2939
+
2940
+ return typeInfo.op_aref(context, context.runtime.newSymbol("name")).toString();
2941
+ }
2942
+
2943
+ protected void setXmlParameter(final ThreadContext context,
2944
+ final Connection connection, final PreparedStatement statement,
2945
+ final int index, final IRubyObject value,
2946
+ final IRubyObject attribute, final int type) throws SQLException {
2947
+
2948
+ SQLXML xml = connection.createSQLXML();
2949
+ xml.setString(value.asString().toString());
2950
+ statement.setSQLXML(index, xml);
2951
+ }
2952
+
2953
+ protected void setBlobParameter(final ThreadContext context,
2954
+ final Connection connection, final PreparedStatement statement,
2955
+ final int index, final IRubyObject value,
2956
+ final IRubyObject attribute, final int type) throws SQLException {
2957
+
2958
+ if ( value instanceof RubyIO ) { // IO/File
2959
+ // JDBC 4.0: statement.setBlob(index, ((RubyIO) value).getInStream());
2960
+ statement.setBinaryStream(index, ((RubyIO) value).getInStream());
2961
+ }
2962
+ else { // should be a RubyString
2963
+ final ByteList blob = value.asString().getByteList();
2964
+ statement.setBytes(index, blob.bytes());
2965
+
2966
+ // JDBC 4.0 :
2967
+ //statement.setBlob(index,
2968
+ // new ByteArrayInputStream(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize())
2969
+ //);
2970
+ }
2971
+ }
2972
+
2973
+ protected void setClobParameter(final ThreadContext context,
2974
+ final Connection connection, final PreparedStatement statement,
2975
+ final int index, final IRubyObject value,
2976
+ final IRubyObject attribute, final int type) throws SQLException {
2977
+ if ( value instanceof RubyIO ) { // IO/File
2978
+ statement.setClob(index, new InputStreamReader(((RubyIO) value).getInStream()));
2979
+ }
2980
+ else { // should be a RubyString
2981
+ final String clob = value.asString().decodeString();
2982
+ statement.setCharacterStream(index, new StringReader(clob), clob.length());
2983
+ // JDBC 4.0 :
2984
+ //statement.setClob(index, new StringReader(clob));
2985
+ }
2986
+ }
2987
+
2988
+ protected void setObjectParameter(final ThreadContext context,
2989
+ final Connection connection, final PreparedStatement statement,
2990
+ final int index, IRubyObject value,
2991
+ final IRubyObject attribute, final int type) throws SQLException {
2992
+
2993
+ statement.setObject(index, value.toJava(Object.class));
2994
+ }
2995
+
2996
+ /**
2997
+ * Always returns a connection (might cause a reconnect if there's none).
2998
+ * @return connection
2999
+ * @throws <code>ActiveRecord::ConnectionNotEstablished</code>, <code>ActiveRecord::JDBCError</code>
3000
+ */
3001
+ protected Connection getConnection() throws RaiseException {
3002
+ return getConnection(false);
3003
+ }
3004
+
3005
+ /**
3006
+ * @see #getConnection()
3007
+ * @param required set to true if a connection is required to exists (e.g. on commit)
3008
+ * @return connection
3009
+ * @throws <code>ActiveRecord::ConnectionNotEstablished</code> if disconnected
3010
+ * @throws <code>ActiveRecord::JDBCError</code> if not connected and connecting fails with a SQL exception
3011
+ */
3012
+ protected Connection getConnection(final boolean required) throws RaiseException {
3013
+ try {
3014
+ return getConnectionInternal(required);
3015
+ }
3016
+ catch (SQLException e) {
3017
+ throw wrapException(getRuntime().getCurrentContext(), e);
3018
+ }
3019
+ }
3020
+
3021
+ private Connection getConnectionInternal(final boolean required) throws SQLException {
3022
+ Connection connection = getConnectionImpl();
3023
+ if ( connection == null ) {
3024
+ if ( required ) {
3025
+ if ( ! connected ) handleNotConnected(); // raise ConnectionNotEstablished
3026
+ synchronized (this) {
3027
+ connection = getConnectionImpl();
3028
+ if ( connection == null ) {
3029
+ connectImpl( true ); // throws SQLException
3030
+ connection = getConnectionImpl();
3031
+ }
3032
+ }
3033
+ }
3034
+ }
3035
+ return connection;
3036
+ }
3037
+
3038
+ private void handleNotConnected() {
3039
+ final Ruby runtime = getRuntime();
3040
+ final RubyClass errorClass = getConnectionNotEstablished( runtime );
3041
+ throw runtime.newRaiseException(errorClass, "no connection available");
3042
+ }
3043
+
3044
+ /**
3045
+ * @note might return null if connection is lazy
3046
+ * @return current JDBC connection
3047
+ */
3048
+ protected final Connection getConnectionImpl() {
3049
+ return (Connection) dataGetStruct(); // synchronized
3050
+ }
3051
+
3052
+ private void setConnection(final Connection connection) {
3053
+ close( getConnectionImpl() ); // close previously open connection if there is one
3054
+ dataWrapStruct(connection);
3055
+ if ( connection != null ) logDriverUsed(connection);
3056
+ }
3057
+
3058
+ protected boolean isConnectionValid(final ThreadContext context, final Connection connection) {
3059
+ if ( connection == null ) return false;
3060
+ Statement statement = null;
3061
+ try {
3062
+ final String aliveSQL = getAliveSQL(context);
3063
+ final int aliveTimeout = getAliveTimeout(context);
3064
+ if ( aliveSQL != null ) { // expect a SELECT/CALL SQL statement
3065
+ statement = createStatement(context, connection);
3066
+ statement.setQueryTimeout(aliveTimeout); // 0 - no timeout
3067
+ statement.execute(aliveSQL);
3068
+ return true; // connection alive
3069
+ }
3070
+ return connection.isValid(aliveTimeout); // isValid(0) (default) means no timeout applied
3071
+ }
3072
+ catch (Exception e) {
3073
+ debugMessage(context.runtime, "connection considered not valid due: ", e);
3074
+ return false;
3075
+ }
3076
+ catch (AbstractMethodError e) { // non-JDBC 4.0 driver
3077
+ warn( context,
3078
+ "driver does not support checking if connection isValid()" +
3079
+ " please make sure you're using a JDBC 4.0 compilant driver or" +
3080
+ " set `connection_alive_sql: ...` in your database configuration" );
3081
+ debugStackTrace(context, e);
3082
+ throw e;
3083
+ }
3084
+ finally { close(statement); }
3085
+ }
3086
+
3087
+ private static final String NIL_ALIVE_SQL = new String(); // no set value marker
3088
+
3089
+ private transient String aliveSQL = null;
3090
+
3091
+ private String getAliveSQL(final ThreadContext context) {
3092
+ if ( aliveSQL == null ) {
3093
+ final IRubyObject alive_sql = getConfigValue(context, "connection_alive_sql");
3094
+ aliveSQL = ( alive_sql == context.nil ) ? NIL_ALIVE_SQL : alive_sql.asString().toString();
3095
+ }
3096
+ return aliveSQL == NIL_ALIVE_SQL ? null : aliveSQL;
3097
+ }
3098
+
3099
+ private transient int aliveTimeout = Integer.MIN_VALUE;
3100
+
3101
+ /**
3102
+ * internal API do not depend on it
3103
+ */
3104
+ protected int getAliveTimeout(final ThreadContext context) {
3105
+ if ( aliveTimeout == Integer.MIN_VALUE ) {
3106
+ final IRubyObject timeout = getConfigValue(context, "connection_alive_timeout");
3107
+ return aliveTimeout = timeout == context.nil ? 0 : RubyInteger.fix2int(timeout);
3108
+ }
3109
+ return aliveTimeout;
3110
+ }
3111
+
3112
+ private boolean tableExists(final ThreadContext context,
3113
+ final Connection connection, final TableName tableName) throws SQLException {
3114
+ final IRubyObject matchedTables =
3115
+ matchTables(context, connection, tableName.catalog, tableName.schema, tableName.name, getTableTypes(), true);
3116
+ // NOTE: allow implementers to ignore checkExistsOnly paramater - empty array means does not exists
3117
+ return matchedTables != null && ! matchedTables.isNil() &&
3118
+ ( ! (matchedTables instanceof RubyArray) || ! ((RubyArray) matchedTables).isEmpty() );
3119
+ }
3120
+
3121
+ @Override
3122
+ @JRubyMethod
3123
+ @SuppressWarnings("unchecked")
3124
+ public IRubyObject inspect() {
3125
+ final ArrayList<Variable<String>> varList = new ArrayList<>(2);
3126
+ final Connection connection = getConnectionImpl();
3127
+ varList.add(new VariableEntry<>( "connection", connection == null ? "null" : connection.toString() ));
3128
+ //varList.add(new VariableEntry<>( "connectionFactory", connectionFactory == null ? "null" : connectionFactory.toString() ));
3129
+ return ObjectSupport.inspect(this, (List) varList);
3130
+ }
3131
+
3132
+ /**
3133
+ * Match table names for given table name (pattern).
3134
+ * @param context
3135
+ * @param connection
3136
+ * @param catalog
3137
+ * @param schemaPattern
3138
+ * @param tablePattern
3139
+ * @param types table types
3140
+ * @param checkExistsOnly an optimization flag (that might be ignored by sub-classes)
3141
+ * whether the result really matters if true no need to map table names and a truth-y
3142
+ * value is sufficient (except for an empty array which is considered that the table
3143
+ * did not exists).
3144
+ * @return matched (and Ruby mapped) table names
3145
+ * @see #mapTables(ThreadContext, Connection, String, String, String, ResultSet)
3146
+ * @throws SQLException
3147
+ */
3148
+ protected IRubyObject matchTables(final ThreadContext context,
3149
+ final Connection connection,
3150
+ final String catalog, final String schemaPattern,
3151
+ final String tablePattern, final String[] types,
3152
+ final boolean checkExistsOnly) throws SQLException {
3153
+
3154
+ final String _tablePattern = caseConvertIdentifierForJdbc(connection, tablePattern);
3155
+ final String _schemaPattern = caseConvertIdentifierForJdbc(connection, schemaPattern);
3156
+ final DatabaseMetaData metaData = connection.getMetaData();
3157
+
3158
+ ResultSet tablesSet = null;
3159
+ try {
3160
+ tablesSet = metaData.getTables(catalog, _schemaPattern, _tablePattern, types);
3161
+ if ( checkExistsOnly ) { // only check if given table exists
3162
+ return tablesSet.next() ? context.runtime.getTrue() : null;
3163
+ }
3164
+ else {
3165
+ return mapTables(context, connection, catalog, _schemaPattern, _tablePattern, tablesSet);
3166
+ }
3167
+ }
3168
+ finally { close(tablesSet); }
3169
+ }
3170
+
3171
+ @Deprecated
3172
+ protected IRubyObject matchTables(final Ruby runtime,
3173
+ final Connection connection,
3174
+ final String catalog, final String schemaPattern,
3175
+ final String tablePattern, final String[] types,
3176
+ final boolean checkExistsOnly) throws SQLException {
3177
+ return matchTables(runtime.getCurrentContext(), connection, catalog, schemaPattern, tablePattern, types, checkExistsOnly);
3178
+ }
3179
+
3180
+ // NOTE java.sql.DatabaseMetaData.getTables :
3181
+ protected final static int TABLES_TABLE_CAT = 1;
3182
+ protected final static int TABLES_TABLE_SCHEM = 2;
3183
+ protected final static int TABLES_TABLE_NAME = 3;
3184
+ protected final static int TABLES_TABLE_TYPE = 4;
3185
+
3186
+ protected RubyArray mapTables(final ThreadContext context, final Connection connection,
3187
+ final String catalog, final String schemaPattern, final String tablePattern,
3188
+ final ResultSet tablesSet) throws SQLException {
3189
+ final RubyArray tables = RubyArray.newArray(context.runtime);
3190
+ while ( tablesSet.next() ) {
3191
+ String name = tablesSet.getString(TABLES_TABLE_NAME);
3192
+ tables.append( cachedString(context, caseConvertIdentifierForRails(connection, name)) );
3193
+ }
3194
+ return tables;
3195
+ }
3196
+
3197
+ protected static final int TABLE_NAME = 3;
3198
+ protected static final int COLUMN_NAME = 4;
3199
+ protected static final int DATA_TYPE = 5;
3200
+ protected static final int TYPE_NAME = 6;
3201
+ protected static final int COLUMN_SIZE = 7;
3202
+ protected static final int DECIMAL_DIGITS = 9;
3203
+ protected static final int COLUMN_DEF = 13;
3204
+ protected static final int IS_NULLABLE = 18;
3205
+ protected static final int BUFFER_LENGTH = 8;
3206
+
3207
+ /**
3208
+ * Create a string which represents a SQL type usable by Rails from the
3209
+ * resultSet column meta-data
3210
+ * @param resultSet
3211
+ */
3212
+ protected String typeFromResultSet(final ResultSet resultSet) throws SQLException {
3213
+ final int precision = intFromResultSet(resultSet, COLUMN_SIZE);
3214
+ final int scale = intFromResultSet(resultSet, DECIMAL_DIGITS);
3215
+
3216
+ final String type = resultSet.getString(TYPE_NAME);
3217
+ return formatTypeWithPrecisionAndScale(type, precision, scale);
3218
+ }
3219
+
3220
+ protected static int intFromResultSet(
3221
+ final ResultSet resultSet, final int column) throws SQLException {
3222
+ final int precision = resultSet.getInt(column);
3223
+ return ( precision == 0 && resultSet.wasNull() ) ? -1 : precision;
3224
+ }
3225
+
3226
+ protected static String formatTypeWithPrecisionAndScale(
3227
+ final String type, final int precision, final int scale) {
3228
+
3229
+ if ( precision <= 0 ) return type;
3230
+
3231
+ final StringBuilder typeStr = new StringBuilder().append(type);
3232
+ typeStr.append('(').append(precision); // type += "(" + precision;
3233
+ if ( scale > 0 ) typeStr.append(',').append(scale); // type += "," + scale;
3234
+ return typeStr.append(')').toString(); // type += ")";
3235
+ }
3236
+
3237
+ private static IRubyObject defaultValueFromResultSet(final Ruby runtime, final ResultSet resultSet)
3238
+ throws SQLException {
3239
+ final String defaultValue = resultSet.getString(COLUMN_DEF);
3240
+ return defaultValue == null ? runtime.getNil() : RubyString.newInternalFromJavaExternal(runtime, defaultValue);
3241
+ }
3242
+
3243
+ protected RubyArray mapColumnsResult(final ThreadContext context,
3244
+ final DatabaseMetaData metaData, final TableName components, final ResultSet results)
3245
+ throws SQLException {
3246
+
3247
+ return mapColumnsResult(context, metaData, components, results, getJdbcColumnClass(context));
3248
+ }
3249
+
3250
+ protected RubyArray mapColumnsResult(final ThreadContext context,
3251
+ final DatabaseMetaData metaData, final TableName components, final ResultSet results,
3252
+ final RubyClass Column)
3253
+ throws SQLException {
3254
+
3255
+ final Ruby runtime = context.runtime;
3256
+
3257
+ final RubyArray columns = RubyArray.newArray(runtime);
3258
+ while ( results.next() ) {
3259
+ final String colName = results.getString(COLUMN_NAME);
3260
+ final RubyString columnName = cachedString(context, caseConvertIdentifierForRails(metaData, colName));
3261
+ final IRubyObject defaultValue = defaultValueFromResultSet( runtime, results );
3262
+ final RubyString sqlType = cachedString(context, typeFromResultSet(results));
3263
+ final RubyBoolean nullable = runtime.newBoolean( ! results.getString(IS_NULLABLE).trim().equals("NO") );
3264
+
3265
+ final String tabName = results.getString(TABLE_NAME);
3266
+ final RubyString tableName = cachedString(context, caseConvertIdentifierForRails(metaData, tabName));
3267
+
3268
+ final IRubyObject type_metadata = getAdapter().callMethod(context, "fetch_type_metadata", sqlType);
3269
+
3270
+ // (name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
3271
+ final IRubyObject[] args = new IRubyObject[] {
3272
+ columnName, defaultValue, type_metadata, nullable, tableName
3273
+ };
3274
+ columns.append( Column.newInstance(context, args, Block.NULL_BLOCK) );
3275
+ }
3276
+ return columns;
3277
+ }
3278
+
3279
+ private static Collection<String> getPrimaryKeyNames(final DatabaseMetaData metaData,
3280
+ final TableName components) throws SQLException {
3281
+ ResultSet primaryKeys = null;
3282
+ try {
3283
+ primaryKeys = metaData.getPrimaryKeys(components.catalog, components.schema, components.name);
3284
+ final List<String> primaryKeyNames = new ArrayList<String>(4);
3285
+ while ( primaryKeys.next() ) {
3286
+ primaryKeyNames.add( primaryKeys.getString(COLUMN_NAME) );
3287
+ }
3288
+ return primaryKeyNames;
3289
+ }
3290
+ finally {
3291
+ close(primaryKeys);
3292
+ }
3293
+ }
3294
+
3295
+ protected IRubyObject mapGeneratedKeys(final ThreadContext context,
3296
+ final Connection connection, final Statement statement) throws SQLException {
3297
+ if (supportsGeneratedKeys(connection)) {
3298
+ return mapQueryResult(context, connection, statement.getGeneratedKeys());
3299
+ }
3300
+ return context.nil; // Adapters should know they don't support it and override this or Adapter#last_inserted_id
3301
+ }
3302
+
3303
+ protected IRubyObject mapGeneratedKeys(
3304
+ final Ruby runtime, final Connection connection,
3305
+ final Statement statement, final Boolean singleResult)
3306
+ throws SQLException {
3307
+ if ( supportsGeneratedKeys(connection) ) {
3308
+ ResultSet genKeys = null;
3309
+ try {
3310
+ genKeys = statement.getGeneratedKeys();
3311
+ // drivers might report a non-result statement without keys
3312
+ // e.g. on derby with SQL: 'SET ISOLATION = SERIALIZABLE'
3313
+ if ( genKeys == null ) return runtime.getNil();
3314
+ return doMapGeneratedKeys(runtime, genKeys, singleResult);
3315
+ }
3316
+ catch (SQLFeatureNotSupportedException e) {
3317
+ return null; // statement.getGeneratedKeys()
3318
+ }
3319
+ finally { close(genKeys); }
3320
+ }
3321
+ return null; // not supported
3322
+ }
3323
+
3324
+ protected final IRubyObject doMapGeneratedKeys(final Ruby runtime,
3325
+ final ResultSet genKeys, final Boolean singleResult)
3326
+ throws SQLException {
3327
+
3328
+ IRubyObject firstKey = null;
3329
+ // no generated keys - e.g. INSERT statement for a table that does
3330
+ // not have and auto-generated ID column :
3331
+ boolean next = genKeys.next() && genKeys.getMetaData().getColumnCount() > 0;
3332
+ // singleResult == null - guess if only single key returned
3333
+ if ( singleResult == null || singleResult.booleanValue() ) {
3334
+ if ( next ) {
3335
+ firstKey = mapGeneratedKey(runtime, genKeys);
3336
+ if ( singleResult != null || ! genKeys.next() ) {
3337
+ return firstKey;
3338
+ }
3339
+ next = true; // 2nd genKeys.next() returned true
3340
+ }
3341
+ else {
3342
+ /* if ( singleResult != null ) */ return runtime.getNil();
3343
+ }
3344
+ }
3345
+
3346
+ final RubyArray keys = runtime.newArray();
3347
+ if ( firstKey != null ) keys.append(firstKey); // singleResult == null
3348
+ while ( next ) {
3349
+ keys.append( mapGeneratedKey(runtime, genKeys) );
3350
+ next = genKeys.next();
3351
+ }
3352
+ return keys;
3353
+ }
3354
+
3355
+ protected IRubyObject mapGeneratedKey(final Ruby runtime, final ResultSet genKeys) throws SQLException {
3356
+ return runtime.newFixnum(genKeys.getLong(1));
3357
+ }
3358
+
3359
+ private transient Boolean supportsGeneratedKeys;
3360
+
3361
+ protected boolean supportsGeneratedKeys(final Connection connection) throws SQLException {
3362
+ Boolean supportsGeneratedKeys = this.supportsGeneratedKeys;
3363
+ if (supportsGeneratedKeys == null) {
3364
+ supportsGeneratedKeys = this.supportsGeneratedKeys = connection.getMetaData().supportsGetGeneratedKeys();
3365
+ }
3366
+ return supportsGeneratedKeys.booleanValue();
3367
+ }
3368
+
3369
+ /**
3370
+ * Converts a JDBC result set into an array (rows) of hashes (row).
3371
+ *
3372
+ * @param downCase should column names only be in lower case?
3373
+ */
3374
+ @SuppressWarnings("unchecked")
3375
+ private IRubyObject mapToRawResult(final ThreadContext context,
3376
+ final Connection connection, final ResultSet resultSet,
3377
+ final boolean downCase) throws SQLException {
3378
+
3379
+ final ColumnData[] columns = extractColumns(context, connection, resultSet, downCase);
3380
+
3381
+ final Ruby runtime = context.runtime;
3382
+ final RubyArray results = runtime.newArray();
3383
+ // [ { 'col1': 1, 'col2': 2 }, { 'col1': 3, 'col2': 4 } ]
3384
+
3385
+ while ( resultSet.next() ) {
3386
+ results.append(mapRawRow(context, runtime, columns, resultSet, this));
3387
+ }
3388
+ return results;
3389
+ }
3390
+
3391
+ private IRubyObject yieldResultRows(final ThreadContext context,
3392
+ final Connection connection, final ResultSet resultSet,
3393
+ final Block block) throws SQLException {
3394
+
3395
+ final ColumnData[] columns = extractColumns(context, connection, resultSet, false);
3396
+
3397
+ final Ruby runtime = context.runtime;
3398
+ final IRubyObject[] blockArgs = new IRubyObject[columns.length];
3399
+ while ( resultSet.next() ) {
3400
+ for ( int i = 0; i < columns.length; i++ ) {
3401
+ final ColumnData column = columns[i];
3402
+ blockArgs[i] = jdbcToRuby(context, runtime, column.index, column.type, resultSet);
3403
+ }
3404
+ block.call( context, blockArgs );
3405
+ }
3406
+
3407
+ return context.nil; // yielded result rows
3408
+ }
3409
+
3410
+ /**
3411
+ * Extract columns from result set.
3412
+ * @param context
3413
+ * @param connection
3414
+ * @param resultSet
3415
+ * @param downCase
3416
+ * @return columns data
3417
+ * @throws SQLException
3418
+ */
3419
+ protected ColumnData[] extractColumns(final ThreadContext context,
3420
+ final Connection connection, final ResultSet resultSet,
3421
+ final boolean downCase) throws SQLException {
3422
+ return setupColumns(context, connection, resultSet.getMetaData(), downCase);
3423
+ }
3424
+
3425
+ /**
3426
+ * @deprecated use {@link #extractColumns(ThreadContext, Connection, ResultSet, boolean)}
3427
+ */
3428
+ @Deprecated
3429
+ protected ColumnData[] extractColumns(final Ruby runtime,
3430
+ final Connection connection, final ResultSet resultSet,
3431
+ final boolean downCase) throws SQLException {
3432
+ return extractColumns(runtime.getCurrentContext(), connection, resultSet, downCase);
3433
+ }
3434
+
3435
+ protected <T> T withConnection(final ThreadContext context, final Callable<T> block)
3436
+ throws RaiseException {
3437
+ try {
3438
+ return withConnection(context, true, block);
3439
+ }
3440
+ catch (final SQLException e) {
3441
+ return handleException(context, e); // should never happen
3442
+ }
3443
+ }
3444
+
3445
+ private <T> T withConnection(final ThreadContext context, final boolean handleException,
3446
+ final Callable<T> block) throws RaiseException, SQLException {
3447
+
3448
+ Exception exception; int retry = 0; int i = 0;
3449
+
3450
+ boolean reconnectOnRetry = true; boolean gotConnection = false;
3451
+ do {
3452
+ boolean autoCommit = true; // retry in-case getAutoCommit throws
3453
+ try {
3454
+ if ( retry > 0 ) { // we're retrying running the block
3455
+ if ( reconnectOnRetry ) {
3456
+ gotConnection = false;
3457
+ debugMessage(context.runtime, "trying to re-connect using a new connection ...");
3458
+ connectImpl(true); // force a new connection to be created
3459
+ }
3460
+ else {
3461
+ debugMessage(context.runtime, "re-trying transient failure on same connection ...");
3462
+ }
3463
+ }
3464
+
3465
+ final Connection connection = getConnectionInternal(false); // getConnection()
3466
+ if ( connection == null ) {
3467
+ if ( ! connected ) handleNotConnected(); // raise ConnectionNotEstablished
3468
+ throw new NoConnectionException();
3469
+ }
3470
+ gotConnection = true;
3471
+ autoCommit = connection.getAutoCommit();
3472
+ return block.call(connection);
3473
+ }
3474
+ catch (final Exception e) { // SQLException or RuntimeException
3475
+ exception = e;
3476
+
3477
+ if ( i == 0 ) retry = 1;
3478
+
3479
+ if ( ! gotConnection ) { // SQLException from driver/data-source
3480
+ reconnectOnRetry = connected;
3481
+ }
3482
+ else if (!autoCommit) {
3483
+ // never retry inside a transaction
3484
+ break;
3485
+ }
3486
+ else if ( isTransient(exception) ) {
3487
+ reconnectOnRetry = false; // continue;
3488
+ }
3489
+ else {
3490
+ if ( isConnectionValid(context, getConnectionImpl()) ) {
3491
+ break; // connection not broken yet failed (do not retry)
3492
+ }
3493
+
3494
+ if ( ! isRecoverable(exception) ) break;
3495
+
3496
+ reconnectOnRetry = true; // retry calling block again
3497
+ }
3498
+ }
3499
+ } while ( i++ < retry ); // i == 0, retry == 1 means we should retry once
3500
+
3501
+ // (retry) loop ended and we did not return ... exception != null
3502
+ return withConnectionError(context, exception, handleException, gotConnection);
3503
+ }
3504
+
3505
+ // NOTE: this is meant to be internal - seeing this from the outside is a sign smt is not right!
3506
+ private static class NoConnectionException extends RuntimeException {
3507
+
3508
+ @Override
3509
+ public Throwable fillInStackTrace() { return this; }
3510
+
3511
+ }
3512
+
3513
+ private <T> T withConnectionError(final ThreadContext context, final Exception exception,
3514
+ final boolean handleException, final boolean gotConnection) throws SQLException {
3515
+ if ( handleException ) {
3516
+ if ( exception instanceof RaiseException ) {
3517
+ throw (RaiseException) exception;
3518
+ }
3519
+ if ( exception instanceof SQLException ) {
3520
+ if ( ! gotConnection && exception.getCause() != null ) {
3521
+ return handleException(context, exception.getCause()); // throws
3522
+ }
3523
+ return handleException(context, exception); // throws
3524
+ }
3525
+ return handleException(context, getCause(exception)); // throws
3526
+ }
3527
+ else {
3528
+ if ( exception instanceof SQLException ) {
3529
+ throw (SQLException) exception;
3530
+ }
3531
+ if ( exception instanceof RuntimeException ) {
3532
+ throw (RuntimeException) exception;
3533
+ }
3534
+ // won't happen - our try block only throws SQL or Runtime exceptions
3535
+ throw new RuntimeException(exception);
3536
+ }
3537
+ }
3538
+
3539
+ protected boolean isTransient(final Exception exception) {
3540
+ if ( exception instanceof SQLTransientException ) return true;
3541
+ return false;
3542
+ }
3543
+
3544
+ protected boolean isRecoverable(final Exception exception) {
3545
+ if ( exception instanceof SQLRecoverableException) return true;
3546
+ return false; // exception instanceof SQLException; // pre JDBC 4.0 drivers?
3547
+ }
3548
+
3549
+ private static Throwable getCause(Throwable exception) {
3550
+ Throwable cause = exception.getCause();
3551
+ while (cause != null && cause != exception) {
3552
+ // SQLException's cause might be DB specific (checked/unchecked) :
3553
+ if ( exception instanceof SQLException ) break;
3554
+ exception = cause; cause = exception.getCause();
3555
+ }
3556
+ return exception;
3557
+ }
3558
+
3559
+ protected <T> T handleException(final ThreadContext context, Throwable exception) throws RaiseException {
3560
+ // NOTE: we shall not wrap unchecked (runtime) exceptions into AR::Error
3561
+ // if it's really a misbehavior of the driver throwing a RuntimeExcepion
3562
+ // instead of SQLException than this should be overriden for the adapter
3563
+ if ( exception instanceof RuntimeException ) {
3564
+ throw (RuntimeException) exception;
3565
+ }
3566
+ debugStackTrace(context, exception);
3567
+ throw wrapException(context, exception);
3568
+ }
3569
+
3570
+ protected RaiseException wrapException(final ThreadContext context, final Throwable exception) {
3571
+ final Ruby runtime = context.runtime;
3572
+ if ( exception instanceof SQLException ) {
3573
+ return wrapException(context, (SQLException) exception, null);
3574
+ }
3575
+ if ( exception instanceof RaiseException ) {
3576
+ return (RaiseException) exception;
3577
+ }
3578
+ if ( exception instanceof RuntimeException ) {
3579
+ return wrapException(context, context.runtime.getRuntimeError(), exception);
3580
+ }
3581
+ // NOTE: compat - maybe makes sense or maybe not (e.g. IOException) :
3582
+ return wrapException(context, getJDBCError(runtime), exception);
3583
+ }
3584
+
3585
+ public static RaiseException wrapException(final ThreadContext context,
3586
+ final RubyClass errorClass, final Throwable exception) {
3587
+ return wrapException(context, errorClass, exception, exception.toString());
3588
+ }
3589
+
3590
+ public static RaiseException wrapException(final ThreadContext context,
3591
+ final RubyClass errorClass, final Throwable exception, final String message) {
3592
+ final RaiseException error = context.runtime.newRaiseException(errorClass, message);
3593
+ error.initCause(exception);
3594
+ return error;
3595
+ }
3596
+
3597
+ protected RaiseException wrapException(final ThreadContext context, final SQLException exception, String message) {
3598
+ return wrapSQLException(context, getJDBCError(context.runtime), exception, message);
3599
+ }
3600
+
3601
+ protected RaiseException wrapException(final ThreadContext context, final RubyClass errorClass, final SQLException exception) {
3602
+ return wrapSQLException(context, errorClass, exception, null);
3603
+ }
3604
+
3605
+ private static RaiseException wrapSQLException(final ThreadContext context, final RubyClass errorClass,
3606
+ final SQLException exception, String message) {
3607
+ final Ruby runtime = context.runtime;
3608
+ if ( message == null ) {
3609
+ message = SQLException.class == exception.getClass() ?
3610
+ exception.getMessage() : exception.toString(); // useful to easily see type on Ruby side
3611
+ }
3612
+ final RaiseException raise = wrapException(context, errorClass, exception, message);
3613
+ final RubyException error = raise.getException(); // assuming JDBCError internals :
3614
+ error.setInstanceVariable("@jdbc_exception", JavaEmbedUtils.javaToRuby(runtime, exception));
3615
+ return raise;
3616
+ }
3617
+
3618
+ protected final RaiseException newNoDatabaseError(final SQLException ex) {
3619
+ final Ruby runtime = getRuntime();
3620
+ return wrapException(runtime.getCurrentContext(), getNoDatabaseError(runtime), ex);
3621
+ }
3622
+
3623
+ private IRubyObject convertJavaToRuby(final Object object) {
3624
+ return JavaUtil.convertJavaToRuby( getRuntime(), object );
3625
+ }
3626
+
3627
+ /**
3628
+ * Some databases support schemas and others do not.
3629
+ * For ones which do this method should return true, aiding in decisions regarding schema vs database determination.
3630
+ */
3631
+ protected boolean databaseSupportsSchemas() {
3632
+ return false;
3633
+ }
3634
+
3635
+ private static final byte[] SELECT = new byte[] { 's','e','l','e','c','t' };
3636
+ private static final byte[] WITH = new byte[] { 'w','i','t','h' };
3637
+ private static final byte[] SHOW = new byte[] { 's','h','o','w' };
3638
+ private static final byte[] CALL = new byte[]{ 'c','a','l','l' };
3639
+
3640
+ @JRubyMethod(name = "select?", required = 1, meta = true, frame = false)
3641
+ public static RubyBoolean select_p(final ThreadContext context,
3642
+ final IRubyObject self, final IRubyObject sql) {
3643
+ return context.runtime.newBoolean( isSelect(sql.asString()) );
3644
+ }
3645
+
3646
+ private static boolean isSelect(final RubyString sql) {
3647
+ final ByteList sqlBytes = sql.getByteList();
3648
+ return StringHelper.startsWithIgnoreCase(sqlBytes, SELECT) ||
3649
+ StringHelper.startsWithIgnoreCase(sqlBytes, WITH) ||
3650
+ StringHelper.startsWithIgnoreCase(sqlBytes, SHOW) ||
3651
+ StringHelper.startsWithIgnoreCase(sqlBytes, CALL);
3652
+ }
3653
+
3654
+ private static final byte[] INSERT = new byte[] { 'i','n','s','e','r','t' };
3655
+
3656
+ @JRubyMethod(name = "insert?", required = 1, meta = true, frame = false)
3657
+ public static RubyBoolean insert_p(final ThreadContext context,
3658
+ final IRubyObject self, final IRubyObject sql) {
3659
+ final ByteList sqlBytes = sql.asString().getByteList();
3660
+ return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, INSERT) );
3661
+ }
3662
+
3663
+ protected static boolean startsWithIgnoreCase(final ByteList bytes, final byte[] start) {
3664
+ return StringHelper.startsWithIgnoreCase(bytes, start);
3665
+ }
3666
+
3667
+ // maps a AR::Result row
3668
+ protected static IRubyObject mapRow(final ThreadContext context, final Ruby runtime,
3669
+ final ColumnData[] columns, final ResultSet resultSet,
3670
+ final RubyJdbcConnection connection) throws SQLException {
3671
+
3672
+ final IRubyObject[] row = new IRubyObject[columns.length];
3673
+
3674
+ for (int i = 0; i < columns.length; i++) {
3675
+ final ColumnData column = columns[i];
3676
+ row[i] = connection.jdbcToRuby(context, runtime, column.index, column.type, resultSet);
3677
+ }
3678
+
3679
+ return RubyArray.newArrayNoCopy(context.runtime, row);
3680
+ }
3681
+
3682
+ private static IRubyObject mapRawRow(final ThreadContext context, final Ruby runtime,
3683
+ final ColumnData[] columns, final ResultSet resultSet,
3684
+ final RubyJdbcConnection connection) throws SQLException {
3685
+
3686
+ final RubyHash row = new RubyHash(runtime, columns.length);
3687
+
3688
+ for ( int i = 0; i < columns.length; i++ ) {
3689
+ final ColumnData column = columns[i];
3690
+ // NOTE: we know keys are always String so maybe we could take it even further ?!
3691
+ row.fastASetCheckString(runtime, column.getName(context),
3692
+ connection.jdbcToRuby(context, runtime, column.index, column.type, resultSet)
3693
+ );
3694
+ }
3695
+
3696
+ return row;
3697
+ }
3698
+
3699
+ protected static IRubyObject newResult(final ThreadContext context, ColumnData[] columns, IRubyObject rows) {
3700
+ final RubyClass Result = getResult(context.runtime);
3701
+ return Result.newInstance(context, columnsToArray(context, columns), rows, Block.NULL_BLOCK); // Result.new
3702
+ }
3703
+
3704
+ private static RubyArray columnsToArray(ThreadContext context, ColumnData[] columns) {
3705
+ final IRubyObject[] cols = new IRubyObject[columns.length];
3706
+
3707
+ for ( int i = 0; i < columns.length; i++ ) cols[i] = columns[i].getName(context);
3708
+
3709
+ return RubyArray.newArrayNoCopy(context.runtime, cols);
3710
+ }
3711
+
3712
+ protected static final class TableName {
3713
+
3714
+ public final String catalog, schema, name;
3715
+
3716
+ public TableName(String catalog, String schema, String table) {
3717
+ this.catalog = catalog;
3718
+ this.schema = schema;
3719
+ this.name = table;
3720
+ }
3721
+
3722
+ @Override
3723
+ public String toString() {
3724
+ return getClass().getName() + "{catalog=" + catalog + ",schema=" + schema + ",name=" + name + "}";
3725
+ }
3726
+
3727
+ }
3728
+
3729
+ /**
3730
+ * Extract the table name components for the given name e.g. "mycat.sys.entries"
3731
+ *
3732
+ * @param connection
3733
+ * @param catalog (optional) catalog to use if table name does not contain
3734
+ * the catalog prefix
3735
+ * @param schema (optional) schema to use if table name does not have one
3736
+ * @param tableName the table name
3737
+ * @return (parsed) table name
3738
+ *
3739
+ * @throws IllegalArgumentException for invalid table name format
3740
+ * @throws SQLException
3741
+ */
3742
+ protected TableName extractTableName(
3743
+ final Connection connection, String catalog, String schema,
3744
+ final String tableName) throws IllegalArgumentException, SQLException {
3745
+
3746
+ final List<String> nameParts = split(tableName, '.');
3747
+ final int len = nameParts.size();
3748
+ if ( len > 3 ) {
3749
+ throw new IllegalArgumentException("table name: " + tableName + " should not contain more than 2 '.'");
3750
+ }
3751
+
3752
+ String name = tableName;
3753
+
3754
+ if ( len == 2 ) {
3755
+ schema = nameParts.get(0); name = nameParts.get(1);
3756
+ }
3757
+ else if ( len == 3 ) {
3758
+ catalog = nameParts.get(0);
3759
+ schema = nameParts.get(1);
3760
+ name = nameParts.get(2);
3761
+ }
3762
+
3763
+ if ( schema != null ) {
3764
+ schema = caseConvertIdentifierForJdbc(connection, schema);
3765
+ }
3766
+ name = caseConvertIdentifierForJdbc(connection, name);
3767
+
3768
+ if ( schema != null && ! databaseSupportsSchemas() ) {
3769
+ catalog = schema;
3770
+ }
3771
+ if ( catalog == null ) catalog = connection.getCatalog();
3772
+
3773
+ return new TableName(catalog, schema, name);
3774
+ }
3775
+
3776
+ protected static List<String> split(final String str, final char sep) {
3777
+ ArrayList<String> split = new ArrayList<>(4);
3778
+ int s = 0;
3779
+ for ( int i = 0; i < str.length(); i++ ) {
3780
+ if ( str.charAt(i) == sep ) {
3781
+ split.add( str.substring(s, i) ); s = i + 1;
3782
+ }
3783
+ }
3784
+ if ( s < str.length() ) split.add( str.substring(s) );
3785
+ return split;
3786
+ }
3787
+
3788
+ protected final TableName extractTableName(
3789
+ final Connection connection, final String schema,
3790
+ final String tableName) throws IllegalArgumentException, SQLException {
3791
+ return extractTableName(connection, null, schema, tableName);
3792
+ }
3793
+
3794
+ protected IRubyObject valueForDatabase(final ThreadContext context, final IRubyObject attribute) {
3795
+ return attribute.callMethod(context, "value_for_database");
3796
+ }
3797
+
3798
+ // FIXME: This should not be static and will be exposed via api in connection as instance method.
3799
+ public static final StringCache STRING_CACHE = new StringCache();
3800
+
3801
+ protected static RubyString cachedString(final ThreadContext context, final String str) {
3802
+ return STRING_CACHE.get(context, str);
3803
+ }
3804
+
3805
+ protected static final class ColumnData {
3806
+
3807
+ @Deprecated
3808
+ public RubyString name;
3809
+ public final int index;
3810
+ public final int type;
3811
+
3812
+ private final String label;
3813
+
3814
+ @Deprecated
3815
+ public ColumnData(RubyString name, int type, int idx) {
3816
+ this.name = name;
3817
+ this.type = type;
3818
+ this.index = idx;
3819
+
3820
+ this.label = name.toString();
3821
+ }
3822
+
3823
+ public ColumnData(String label, int type, int idx) {
3824
+ this.label = label;
3825
+ this.type = type;
3826
+ this.index = idx;
3827
+ }
3828
+
3829
+ // NOTE: meant temporary for others to update from accesing name
3830
+ ColumnData(ThreadContext context, String label, int type, int idx) {
3831
+ this(label, type, idx);
3832
+ name = cachedString(context, label);
3833
+ }
3834
+
3835
+ public String getName() {
3836
+ return label;
3837
+ }
3838
+
3839
+ RubyString getName(final ThreadContext context) {
3840
+ if ( name != null ) return name;
3841
+ return name = cachedString(context, label);
3842
+ }
3843
+
3844
+ @Override
3845
+ public String toString() {
3846
+ return "'" + label + "'i" + index + "t" + type + "";
3847
+ }
3848
+
3849
+ }
3850
+
3851
+ private ColumnData[] setupColumns(
3852
+ final ThreadContext context,
3853
+ final Connection connection,
3854
+ final ResultSetMetaData resultMetaData,
3855
+ final boolean downCase) throws SQLException {
3856
+
3857
+ final int columnCount = resultMetaData.getColumnCount();
3858
+ final ColumnData[] columns = new ColumnData[columnCount];
3859
+
3860
+ for ( int i = 1; i <= columnCount; i++ ) { // metadata is one-based
3861
+ String name = resultMetaData.getColumnLabel(i);
3862
+ if ( downCase ) {
3863
+ name = name.toLowerCase();
3864
+ } else {
3865
+ name = caseConvertIdentifierForRails(connection, name);
3866
+ }
3867
+
3868
+ final int columnType = resultMetaData.getColumnType(i);
3869
+ columns[i - 1] = new ColumnData(context, name, columnType, i);
3870
+ }
3871
+
3872
+ return columns;
3873
+ }
3874
+
3875
+ // JDBC API Helpers :
3876
+
3877
+ protected static void close(final Connection connection) {
3878
+ if ( connection != null ) {
3879
+ try { connection.close(); }
3880
+ catch (final Exception e) { /* NOOP */ }
3881
+ }
3882
+ }
3883
+
3884
+ public static void close(final ResultSet resultSet) {
3885
+ if (resultSet != null) {
3886
+ try { resultSet.close(); }
3887
+ catch (final Exception e) { /* NOOP */ }
3888
+ }
3889
+ }
3890
+
3891
+ public static void close(final Statement statement) {
3892
+ if (statement != null) {
3893
+ try { statement.close(); }
3894
+ catch (final Exception e) { /* NOOP */ }
3895
+ }
3896
+ }
3897
+
3898
+ // DEBUG-ing helpers :
3899
+
3900
+ private static boolean debug = Boolean.parseBoolean( SafePropertyAccessor.getProperty("arjdbc.debug") );
3901
+
3902
+ public static boolean isDebug() { return debug; }
3903
+
3904
+ public static boolean isDebug(final Ruby runtime) {
3905
+ return debug || ( runtime != null && runtime.isDebug() );
3906
+ }
3907
+
3908
+ public static void setDebug(boolean debug) {
3909
+ RubyJdbcConnection.debug = debug;
3910
+ }
3911
+
3912
+ //public static void debugMessage(final ThreadContext context, final String msg) {
3913
+ // if ( debug || ( context != null && context.runtime.isDebug() ) ) {
3914
+ // final PrintStream out = context != null ? context.runtime.getOut() : System.out;
3915
+ // out.println(msg);
3916
+ // }
3917
+ //}
3918
+
3919
+ public static void debugMessage(final Ruby runtime, final Object msg) {
3920
+ if ( isDebug(runtime) ) {
3921
+ final PrintStream out = runtime != null ? runtime.getOut() : System.out;
3922
+ out.print("ArJdbc: "); out.println(msg);
3923
+ }
3924
+ }
3925
+
3926
+ public static void debugMessage(final Ruby runtime, final String msg, final Object e) {
3927
+ if ( isDebug(runtime) ) {
3928
+ final PrintStream out = runtime != null ? runtime.getOut() : System.out;
3929
+ out.print("ArJdbc: "); out.print(msg); out.println(e);
3930
+ }
3931
+ }
3932
+
3933
+ protected static void debugErrorSQL(final ThreadContext context, final String sql) {
3934
+ if ( debug || ( context != null && context.runtime.isDebug() ) ) {
3935
+ final PrintStream out = context != null ? context.runtime.getOut() : System.out;
3936
+ out.print("ArJdbc: (error) SQL = "); out.println(sql);
3937
+ }
3938
+ }
3939
+
3940
+ // disables full (Java) traces to be printed while DEBUG is on
3941
+ private static final Boolean debugStackTrace;
3942
+ static {
3943
+ String debugTrace = SafePropertyAccessor.getProperty("arjdbc.debug.trace");
3944
+ debugStackTrace = debugTrace == null ? null : Boolean.parseBoolean(debugTrace);
3945
+ }
3946
+
3947
+ public static void debugStackTrace(final ThreadContext context, final Throwable e) {
3948
+ if ( debug || ( context != null && context.runtime.isDebug() ) ) {
3949
+ final PrintStream out = context != null ? context.runtime.getOut() : System.out;
3950
+ if ( debugStackTrace == null || debugStackTrace.booleanValue() ) {
3951
+ e.printStackTrace(out);
3952
+ }
3953
+ else {
3954
+ out.println(e);
3955
+ }
3956
+ }
3957
+ }
3958
+
3959
+ protected void warn(final ThreadContext context, final String message) {
3960
+ arjdbc.ArJdbcModule.warn(context, message);
3961
+ }
3962
+
3963
+ private static boolean driverUsedLogged;
3964
+
3965
+ private void logDriverUsed(final Connection connection) {
3966
+ if ( isDebug() ) {
3967
+ if ( driverUsedLogged ) return;
3968
+ driverUsedLogged = true;
3969
+ try {
3970
+ final DatabaseMetaData meta = connection.getMetaData();
3971
+ debugMessage(getRuntime(), "using driver " + meta.getDriverVersion());
3972
+ }
3973
+ catch (Exception e) {
3974
+ debugMessage(getRuntime(), "failed to log driver ", e);
3975
+ }
3976
+ }
3977
+ }
3978
+
3979
+ }