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