activerecord-jdbc-adapter-ficoh 1.3.21-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
+ }