activerecord-jdbc-adapter-ficoh 1.3.21-java

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