neo-activerecord-jdbc-adapter 5.0.pre2

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