activerecord-jdbc-alt-adapter 50.3.0-java

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