activerecord-jdbc-adapter-onsite 1.2.2
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.
- data/.gitignore +22 -0
- data/.travis.yml +14 -0
- data/Appraisals +16 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +45 -0
- data/History.txt +488 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +214 -0
- data/Rakefile +62 -0
- data/activerecord-jdbc-adapter.gemspec +23 -0
- data/bench/bench_attributes.rb +13 -0
- data/bench/bench_attributes_new.rb +14 -0
- data/bench/bench_create.rb +12 -0
- data/bench/bench_find_all.rb +12 -0
- data/bench/bench_find_all_mt.rb +25 -0
- data/bench/bench_model.rb +85 -0
- data/bench/bench_new.rb +12 -0
- data/bench/bench_new_valid.rb +12 -0
- data/bench/bench_valid.rb +13 -0
- data/gemfiles/rails23.gemfile +10 -0
- data/gemfiles/rails23.gemfile.lock +38 -0
- data/gemfiles/rails30.gemfile +9 -0
- data/gemfiles/rails30.gemfile.lock +33 -0
- data/gemfiles/rails31.gemfile +9 -0
- data/gemfiles/rails31.gemfile.lock +35 -0
- data/gemfiles/rails32.gemfile +9 -0
- data/gemfiles/rails32.gemfile.lock +35 -0
- data/lib/active_record/connection_adapters/derby_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/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/activerecord-jdbc-adapter.rb +8 -0
- data/lib/arel/engines/sql/compilers/db2_compiler.rb +9 -0
- data/lib/arel/engines/sql/compilers/derby_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/h2_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +15 -0
- data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +6 -0
- data/lib/arel/engines/sql/compilers/mssql_compiler.rb +46 -0
- data/lib/arel/visitors/compat.rb +13 -0
- data/lib/arel/visitors/db2.rb +17 -0
- data/lib/arel/visitors/derby.rb +32 -0
- data/lib/arel/visitors/firebird.rb +24 -0
- data/lib/arel/visitors/hsqldb.rb +26 -0
- data/lib/arel/visitors/sql_server.rb +46 -0
- data/lib/arjdbc.rb +24 -0
- data/lib/arjdbc/db2.rb +2 -0
- data/lib/arjdbc/db2/adapter.rb +541 -0
- data/lib/arjdbc/derby.rb +7 -0
- data/lib/arjdbc/derby/adapter.rb +358 -0
- data/lib/arjdbc/derby/connection_methods.rb +19 -0
- data/lib/arjdbc/discover.rb +92 -0
- data/lib/arjdbc/firebird.rb +2 -0
- data/lib/arjdbc/firebird/adapter.rb +140 -0
- data/lib/arjdbc/h2.rb +4 -0
- data/lib/arjdbc/h2/adapter.rb +54 -0
- data/lib/arjdbc/h2/connection_methods.rb +13 -0
- data/lib/arjdbc/hsqldb.rb +4 -0
- data/lib/arjdbc/hsqldb/adapter.rb +184 -0
- data/lib/arjdbc/hsqldb/connection_methods.rb +15 -0
- data/lib/arjdbc/informix.rb +3 -0
- data/lib/arjdbc/informix/adapter.rb +142 -0
- data/lib/arjdbc/informix/connection_methods.rb +11 -0
- data/lib/arjdbc/jdbc.rb +2 -0
- data/lib/arjdbc/jdbc/adapter.rb +356 -0
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/base_ext.rb +15 -0
- data/lib/arjdbc/jdbc/callbacks.rb +44 -0
- data/lib/arjdbc/jdbc/column.rb +47 -0
- data/lib/arjdbc/jdbc/compatibility.rb +51 -0
- data/lib/arjdbc/jdbc/connection.rb +134 -0
- data/lib/arjdbc/jdbc/connection_methods.rb +16 -0
- data/lib/arjdbc/jdbc/core_ext.rb +24 -0
- data/lib/arjdbc/jdbc/discover.rb +18 -0
- data/lib/arjdbc/jdbc/driver.rb +35 -0
- data/lib/arjdbc/jdbc/extension.rb +47 -0
- data/lib/arjdbc/jdbc/java.rb +14 -0
- data/lib/arjdbc/jdbc/jdbc.rake +131 -0
- data/lib/arjdbc/jdbc/missing_functionality_helper.rb +88 -0
- data/lib/arjdbc/jdbc/quoted_primary_key.rb +28 -0
- data/lib/arjdbc/jdbc/railtie.rb +9 -0
- data/lib/arjdbc/jdbc/rake_tasks.rb +10 -0
- data/lib/arjdbc/jdbc/require_driver.rb +16 -0
- data/lib/arjdbc/jdbc/type_converter.rb +126 -0
- data/lib/arjdbc/mimer.rb +2 -0
- data/lib/arjdbc/mimer/adapter.rb +142 -0
- data/lib/arjdbc/mssql.rb +4 -0
- data/lib/arjdbc/mssql/adapter.rb +477 -0
- data/lib/arjdbc/mssql/connection_methods.rb +31 -0
- data/lib/arjdbc/mssql/limit_helpers.rb +101 -0
- data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
- data/lib/arjdbc/mssql/tsql_helper.rb +61 -0
- data/lib/arjdbc/mysql.rb +4 -0
- data/lib/arjdbc/mysql/adapter.rb +505 -0
- data/lib/arjdbc/mysql/connection_methods.rb +28 -0
- data/lib/arjdbc/oracle.rb +3 -0
- data/lib/arjdbc/oracle/adapter.rb +432 -0
- data/lib/arjdbc/oracle/connection_methods.rb +12 -0
- data/lib/arjdbc/postgresql.rb +4 -0
- data/lib/arjdbc/postgresql/adapter.rb +861 -0
- data/lib/arjdbc/postgresql/connection_methods.rb +23 -0
- data/lib/arjdbc/sqlite3.rb +4 -0
- data/lib/arjdbc/sqlite3/adapter.rb +389 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +35 -0
- data/lib/arjdbc/sybase.rb +2 -0
- data/lib/arjdbc/sybase/adapter.rb +46 -0
- data/lib/arjdbc/version.rb +8 -0
- data/lib/generators/jdbc/USAGE +10 -0
- data/lib/generators/jdbc/jdbc_generator.rb +9 -0
- data/lib/jdbc_adapter.rb +2 -0
- data/lib/jdbc_adapter/rake_tasks.rb +3 -0
- data/lib/jdbc_adapter/version.rb +3 -0
- data/lib/pg.rb +26 -0
- data/pom.xml +57 -0
- data/rails_generators/jdbc_generator.rb +15 -0
- data/rails_generators/templates/config/initializers/jdbc.rb +7 -0
- data/rails_generators/templates/lib/tasks/jdbc.rake +8 -0
- data/rakelib/bundler_ext.rb +11 -0
- data/rakelib/compile.rake +23 -0
- data/rakelib/db.rake +39 -0
- data/rakelib/rails.rake +41 -0
- data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +69 -0
- data/src/java/arjdbc/derby/DerbyModule.java +324 -0
- data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +70 -0
- data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +74 -0
- data/src/java/arjdbc/jdbc/AdapterJavaService.java +68 -0
- data/src/java/arjdbc/jdbc/JdbcConnectionFactory.java +36 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +1346 -0
- data/src/java/arjdbc/jdbc/SQLBlock.java +48 -0
- data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +127 -0
- data/src/java/arjdbc/mysql/MySQLModule.java +134 -0
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +161 -0
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +85 -0
- data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +82 -0
- data/src/java/arjdbc/sqlite3/Sqlite3RubyJdbcConnection.java +126 -0
- data/test/abstract_db_create.rb +135 -0
- data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
- data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
- data/test/activerecord/jall.sh +7 -0
- data/test/activerecord/jtest.sh +3 -0
- data/test/db/db2.rb +11 -0
- data/test/db/derby.rb +12 -0
- data/test/db/h2.rb +11 -0
- data/test/db/hsqldb.rb +13 -0
- data/test/db/informix.rb +11 -0
- data/test/db/jdbc.rb +12 -0
- data/test/db/jndi_config.rb +40 -0
- data/test/db/logger.rb +3 -0
- data/test/db/mssql.rb +9 -0
- data/test/db/mysql.rb +10 -0
- data/test/db/oracle.rb +34 -0
- data/test/db/postgres.rb +18 -0
- data/test/db/sqlite3.rb +11 -0
- data/test/db2_reset_column_information_test.rb +8 -0
- data/test/db2_simple_test.rb +66 -0
- data/test/derby_migration_test.rb +68 -0
- data/test/derby_multibyte_test.rb +12 -0
- data/test/derby_reset_column_information_test.rb +8 -0
- data/test/derby_row_locking_test.rb +9 -0
- data/test/derby_simple_test.rb +139 -0
- data/test/generic_jdbc_connection_test.rb +29 -0
- data/test/h2_change_column_test.rb +68 -0
- data/test/h2_simple_test.rb +41 -0
- data/test/has_many_through.rb +79 -0
- data/test/helper.rb +108 -0
- data/test/hsqldb_simple_test.rb +6 -0
- data/test/informix_simple_test.rb +48 -0
- data/test/jdbc_common.rb +28 -0
- data/test/jndi_callbacks_test.rb +36 -0
- data/test/jndi_test.rb +25 -0
- data/test/manualTestDatabase.rb +191 -0
- data/test/models/add_not_null_column_to_table.rb +9 -0
- data/test/models/auto_id.rb +15 -0
- data/test/models/custom_pk_name.rb +14 -0
- data/test/models/data_types.rb +30 -0
- data/test/models/entry.rb +40 -0
- data/test/models/mixed_case.rb +22 -0
- data/test/models/reserved_word.rb +15 -0
- data/test/models/string_id.rb +17 -0
- data/test/models/thing.rb +16 -0
- data/test/models/validates_uniqueness_of_string.rb +19 -0
- data/test/mssql_db_create_test.rb +26 -0
- data/test/mssql_identity_insert_test.rb +19 -0
- data/test/mssql_ignore_system_views_test.rb +27 -0
- data/test/mssql_legacy_types_test.rb +58 -0
- data/test/mssql_limit_offset_test.rb +136 -0
- data/test/mssql_multibyte_test.rb +18 -0
- data/test/mssql_null_test.rb +14 -0
- data/test/mssql_reset_column_information_test.rb +8 -0
- data/test/mssql_row_locking_sql_test.rb +159 -0
- data/test/mssql_row_locking_test.rb +9 -0
- data/test/mssql_simple_test.rb +55 -0
- data/test/mysql_db_create_test.rb +27 -0
- data/test/mysql_index_length_test.rb +58 -0
- data/test/mysql_info_test.rb +123 -0
- data/test/mysql_multibyte_test.rb +10 -0
- data/test/mysql_nonstandard_primary_key_test.rb +42 -0
- data/test/mysql_reset_column_information_test.rb +8 -0
- data/test/mysql_simple_test.rb +125 -0
- data/test/oracle_reset_column_information_test.rb +8 -0
- data/test/oracle_simple_test.rb +18 -0
- data/test/oracle_specific_test.rb +83 -0
- data/test/postgres_db_create_test.rb +32 -0
- data/test/postgres_drop_db_test.rb +16 -0
- data/test/postgres_information_schema_leak_test.rb +29 -0
- data/test/postgres_mixed_case_test.rb +29 -0
- data/test/postgres_native_type_mapping_test.rb +93 -0
- data/test/postgres_nonseq_pkey_test.rb +38 -0
- data/test/postgres_reserved_test.rb +22 -0
- data/test/postgres_reset_column_information_test.rb +8 -0
- data/test/postgres_schema_search_path_test.rb +48 -0
- data/test/postgres_simple_test.rb +168 -0
- data/test/postgres_table_alias_length_test.rb +15 -0
- data/test/postgres_type_conversion_test.rb +34 -0
- data/test/row_locking.rb +90 -0
- data/test/simple.rb +731 -0
- data/test/sqlite3_reset_column_information_test.rb +8 -0
- data/test/sqlite3_simple_test.rb +316 -0
- data/test/sybase_jtds_simple_test.rb +28 -0
- data/test/sybase_reset_column_information_test.rb +8 -0
- metadata +288 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
**** BEGIN LICENSE BLOCK *****
|
|
3
|
+
* Copyright (c) 2006-2010 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
|
+
|
|
27
|
+
package arjdbc.h2;
|
|
28
|
+
|
|
29
|
+
import java.sql.ResultSet;
|
|
30
|
+
import java.sql.SQLException;
|
|
31
|
+
import java.sql.Types;
|
|
32
|
+
|
|
33
|
+
import arjdbc.jdbc.RubyJdbcConnection;
|
|
34
|
+
|
|
35
|
+
import org.jruby.Ruby;
|
|
36
|
+
import org.jruby.RubyClass;
|
|
37
|
+
import org.jruby.runtime.ObjectAllocator;
|
|
38
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @author nicksieger
|
|
43
|
+
*/
|
|
44
|
+
public class H2RubyJdbcConnection extends RubyJdbcConnection {
|
|
45
|
+
protected H2RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
|
|
46
|
+
super(runtime, metaClass);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static RubyClass createH2JdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
|
|
50
|
+
RubyClass clazz = RubyJdbcConnection.getConnectionAdapters(runtime).defineClassUnder("H2JdbcConnection",
|
|
51
|
+
jdbcConnection, H2_JDBCCONNECTION_ALLOCATOR);
|
|
52
|
+
clazz.defineAnnotatedMethods(H2RubyJdbcConnection.class);
|
|
53
|
+
|
|
54
|
+
return clazz;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private static ObjectAllocator H2_JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
|
|
58
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
|
59
|
+
return new H2RubyJdbcConnection(runtime, klass);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* H2 supports schemas.
|
|
65
|
+
*/
|
|
66
|
+
@Override
|
|
67
|
+
protected boolean databaseSupportsSchemas() {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
**** BEGIN LICENSE BLOCK *****
|
|
3
|
+
* Copyright (c) 2006-2010 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
|
+
|
|
27
|
+
package arjdbc.informix;
|
|
28
|
+
|
|
29
|
+
import java.sql.ResultSet;
|
|
30
|
+
import java.sql.SQLException;
|
|
31
|
+
import java.sql.Types;
|
|
32
|
+
|
|
33
|
+
import arjdbc.jdbc.RubyJdbcConnection;
|
|
34
|
+
|
|
35
|
+
import org.jruby.Ruby;
|
|
36
|
+
import org.jruby.RubyClass;
|
|
37
|
+
import org.jruby.runtime.ObjectAllocator;
|
|
38
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @author nicksieger
|
|
43
|
+
*/
|
|
44
|
+
public class InformixRubyJdbcConnection extends RubyJdbcConnection {
|
|
45
|
+
protected InformixRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
|
|
46
|
+
super(runtime, metaClass);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static RubyClass createInformixJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
|
|
50
|
+
RubyClass clazz = RubyJdbcConnection.getConnectionAdapters(runtime).defineClassUnder("InformixJdbcConnection",
|
|
51
|
+
jdbcConnection, INFORMIX_JDBCCONNECTION_ALLOCATOR);
|
|
52
|
+
clazz.defineAnnotatedMethods(InformixRubyJdbcConnection.class);
|
|
53
|
+
|
|
54
|
+
return clazz;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private static ObjectAllocator INFORMIX_JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
|
|
58
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
|
59
|
+
return new InformixRubyJdbcConnection(runtime, klass);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Treat LONGVARCHAR as CLOB on Informix for purposes of converting a JDBC value to Ruby.
|
|
65
|
+
*/
|
|
66
|
+
@Override
|
|
67
|
+
protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
|
|
68
|
+
throws SQLException {
|
|
69
|
+
if (type == Types.LONGVARCHAR) {
|
|
70
|
+
type = Types.CLOB;
|
|
71
|
+
}
|
|
72
|
+
return super.jdbcToRuby(runtime, column, type, resultSet);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
**** BEGIN LICENSE BLOCK *****
|
|
3
|
+
* Copyright (c) 2006-2010 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
|
+
|
|
27
|
+
package arjdbc.jdbc;
|
|
28
|
+
|
|
29
|
+
import java.io.IOException;
|
|
30
|
+
|
|
31
|
+
import arjdbc.db2.DB2RubyJdbcConnection;
|
|
32
|
+
import arjdbc.derby.DerbyModule;
|
|
33
|
+
import arjdbc.h2.H2RubyJdbcConnection;
|
|
34
|
+
import arjdbc.informix.InformixRubyJdbcConnection;
|
|
35
|
+
import arjdbc.mssql.MssqlRubyJdbcConnection;
|
|
36
|
+
import arjdbc.mysql.MySQLModule;
|
|
37
|
+
import arjdbc.mysql.MySQLRubyJdbcConnection;
|
|
38
|
+
import arjdbc.oracle.OracleRubyJdbcConnection;
|
|
39
|
+
import arjdbc.postgresql.PostgresqlRubyJdbcConnection;
|
|
40
|
+
import arjdbc.sqlite3.Sqlite3RubyJdbcConnection;
|
|
41
|
+
|
|
42
|
+
import org.jruby.Ruby;
|
|
43
|
+
import org.jruby.RubyClass;
|
|
44
|
+
import org.jruby.RubyModule;
|
|
45
|
+
import org.jruby.RubyObjectAdapter;
|
|
46
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
|
47
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
|
48
|
+
|
|
49
|
+
public class AdapterJavaService implements BasicLibraryService {
|
|
50
|
+
private static RubyObjectAdapter rubyApi;
|
|
51
|
+
|
|
52
|
+
public boolean basicLoad(final Ruby runtime) throws IOException {
|
|
53
|
+
RubyClass jdbcConnection = RubyJdbcConnection.createJdbcConnectionClass(runtime);
|
|
54
|
+
PostgresqlRubyJdbcConnection.createPostgresqlJdbcConnectionClass(runtime, jdbcConnection);
|
|
55
|
+
MssqlRubyJdbcConnection.createMssqlJdbcConnectionClass(runtime, jdbcConnection);
|
|
56
|
+
InformixRubyJdbcConnection.createInformixJdbcConnectionClass(runtime, jdbcConnection);
|
|
57
|
+
OracleRubyJdbcConnection.createOracleJdbcConnectionClass(runtime, jdbcConnection);
|
|
58
|
+
Sqlite3RubyJdbcConnection.createSqlite3JdbcConnectionClass(runtime, jdbcConnection);
|
|
59
|
+
H2RubyJdbcConnection.createH2JdbcConnectionClass(runtime, jdbcConnection);
|
|
60
|
+
MySQLRubyJdbcConnection.createMySQLJdbcConnectionClass(runtime, jdbcConnection);
|
|
61
|
+
DB2RubyJdbcConnection.createDB2JdbcConnectionClass(runtime, jdbcConnection);
|
|
62
|
+
RubyModule arJdbc = runtime.getOrCreateModule("ArJdbc");
|
|
63
|
+
rubyApi = JavaEmbedUtils.newObjectAdapter();
|
|
64
|
+
MySQLModule.load(arJdbc);
|
|
65
|
+
DerbyModule.load(arJdbc, rubyApi);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/***** BEGIN LICENSE BLOCK *****
|
|
2
|
+
* Copyright (c) 2006-2010 Nick Sieger <nick@nicksieger.com>
|
|
3
|
+
* Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
|
|
4
|
+
*
|
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
* a copy of this software and associated documentation files (the
|
|
7
|
+
* "Software"), to deal in the Software without restriction, including
|
|
8
|
+
* without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
* permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
* the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be
|
|
14
|
+
* included in all copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
***** END LICENSE BLOCK *****/
|
|
24
|
+
|
|
25
|
+
package arjdbc.jdbc;
|
|
26
|
+
|
|
27
|
+
import java.sql.Connection;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Interface to be implemented in Ruby for retrieving a new connection
|
|
31
|
+
*
|
|
32
|
+
* @author nicksieger
|
|
33
|
+
*/
|
|
34
|
+
public interface JdbcConnectionFactory {
|
|
35
|
+
Connection newConnection();
|
|
36
|
+
}
|
|
@@ -0,0 +1,1346 @@
|
|
|
1
|
+
/*
|
|
2
|
+
**** BEGIN LICENSE BLOCK *****
|
|
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.Reader;
|
|
32
|
+
import java.io.StringReader;
|
|
33
|
+
import java.math.BigInteger;
|
|
34
|
+
import java.sql.Connection;
|
|
35
|
+
import java.sql.DatabaseMetaData;
|
|
36
|
+
import java.sql.PreparedStatement;
|
|
37
|
+
import java.sql.ResultSet;
|
|
38
|
+
import java.sql.ResultSetMetaData;
|
|
39
|
+
import java.sql.SQLException;
|
|
40
|
+
import java.sql.Statement;
|
|
41
|
+
import java.sql.Timestamp;
|
|
42
|
+
import java.sql.Types;
|
|
43
|
+
import java.text.DateFormat;
|
|
44
|
+
import java.text.SimpleDateFormat;
|
|
45
|
+
import java.util.ArrayList;
|
|
46
|
+
import java.util.Calendar;
|
|
47
|
+
import java.util.Date;
|
|
48
|
+
import java.util.List;
|
|
49
|
+
|
|
50
|
+
import org.jruby.Ruby;
|
|
51
|
+
import org.jruby.RubyArray;
|
|
52
|
+
import org.jruby.RubyBignum;
|
|
53
|
+
import org.jruby.RubyClass;
|
|
54
|
+
import org.jruby.RubyHash;
|
|
55
|
+
import org.jruby.RubyModule;
|
|
56
|
+
import org.jruby.RubyNumeric;
|
|
57
|
+
import org.jruby.RubyObject;
|
|
58
|
+
import org.jruby.RubyObjectAdapter;
|
|
59
|
+
import org.jruby.RubyString;
|
|
60
|
+
import org.jruby.RubySymbol;
|
|
61
|
+
import org.jruby.RubyTime;
|
|
62
|
+
import org.jruby.anno.JRubyMethod;
|
|
63
|
+
import org.jruby.exceptions.RaiseException;
|
|
64
|
+
import org.jruby.javasupport.Java;
|
|
65
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
|
66
|
+
import org.jruby.javasupport.JavaObject;
|
|
67
|
+
import org.jruby.javasupport.util.RuntimeHelpers;
|
|
68
|
+
import org.jruby.runtime.Arity;
|
|
69
|
+
import org.jruby.runtime.Block;
|
|
70
|
+
import org.jruby.runtime.ObjectAllocator;
|
|
71
|
+
import org.jruby.runtime.ThreadContext;
|
|
72
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
|
73
|
+
import org.jruby.util.ByteList;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Part of our ActiveRecord::ConnectionAdapters::Connection impl.
|
|
77
|
+
*/
|
|
78
|
+
public class RubyJdbcConnection extends RubyObject {
|
|
79
|
+
private static final String[] TABLE_TYPE = new String[]{"TABLE"};
|
|
80
|
+
private static final String[] TABLE_TYPES = new String[]{"TABLE", "VIEW", "SYNONYM"};
|
|
81
|
+
|
|
82
|
+
private static RubyObjectAdapter rubyApi;
|
|
83
|
+
|
|
84
|
+
protected RubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
|
|
85
|
+
super(runtime, metaClass);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public static RubyClass createJdbcConnectionClass(Ruby runtime) {
|
|
89
|
+
RubyClass jdbcConnection = getConnectionAdapters(runtime).defineClassUnder("JdbcConnection",
|
|
90
|
+
runtime.getObject(), JDBCCONNECTION_ALLOCATOR);
|
|
91
|
+
jdbcConnection.defineAnnotatedMethods(RubyJdbcConnection.class);
|
|
92
|
+
|
|
93
|
+
rubyApi = JavaEmbedUtils.newObjectAdapter();
|
|
94
|
+
|
|
95
|
+
return jdbcConnection;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private static ObjectAllocator JDBCCONNECTION_ALLOCATOR = new ObjectAllocator() {
|
|
99
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
|
100
|
+
return new RubyJdbcConnection(runtime, klass);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
protected static RubyModule getConnectionAdapters(Ruby runtime) {
|
|
105
|
+
return (RubyModule) runtime.getModule("ActiveRecord").getConstant("ConnectionAdapters");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected String[] getTableTypes() {
|
|
109
|
+
return TABLE_TYPES;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@JRubyMethod(name = "begin")
|
|
113
|
+
public IRubyObject begin(ThreadContext context) throws SQLException {
|
|
114
|
+
final Ruby runtime = context.getRuntime();
|
|
115
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
116
|
+
public Object call(Connection c) throws SQLException {
|
|
117
|
+
getConnection(true).setAutoCommit(false);
|
|
118
|
+
return runtime.getNil();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@JRubyMethod(name = {"columns", "columns_internal"}, required = 1, optional = 2)
|
|
124
|
+
public IRubyObject columns_internal(final ThreadContext context, final IRubyObject[] args)
|
|
125
|
+
throws SQLException, IOException {
|
|
126
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
127
|
+
public Object call(Connection c) throws SQLException {
|
|
128
|
+
ResultSet results = null, pkeys = null;
|
|
129
|
+
try {
|
|
130
|
+
String defaultSchema = args.length > 2 ? toStringOrNull(args[2]) : null;
|
|
131
|
+
String tableName = rubyApi.convertToRubyString(args[0]).getUnicodeValue();
|
|
132
|
+
TableNameComponents components = extractTableNameComponents(c, defaultSchema, tableName);
|
|
133
|
+
|
|
134
|
+
RubyArray matchingTables = (RubyArray) tableLookupBlock(context.getRuntime(),
|
|
135
|
+
components.catalog, components.schema, components.table, getTableTypes(), false).call(c);
|
|
136
|
+
if (matchingTables.isEmpty()) {
|
|
137
|
+
throw new SQLException("Table " + tableName + " does not exist");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
DatabaseMetaData metadata = c.getMetaData();
|
|
141
|
+
results = metadata.getColumns(components.catalog, components.schema, components.table, null);
|
|
142
|
+
pkeys = metadata.getPrimaryKeys(components.catalog, components.schema, components.table);
|
|
143
|
+
return unmarshal_columns(context, metadata, results, pkeys);
|
|
144
|
+
} finally {
|
|
145
|
+
close(results);
|
|
146
|
+
close(pkeys);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@JRubyMethod(name = "commit")
|
|
153
|
+
public IRubyObject commit(ThreadContext context) throws SQLException {
|
|
154
|
+
Connection connection = getConnection(true);
|
|
155
|
+
|
|
156
|
+
if (!connection.getAutoCommit()) {
|
|
157
|
+
try {
|
|
158
|
+
connection.commit();
|
|
159
|
+
} finally {
|
|
160
|
+
connection.setAutoCommit(true);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return context.getRuntime().getNil();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@JRubyMethod(name = "connection", frame = false)
|
|
168
|
+
public IRubyObject connection() {
|
|
169
|
+
if (getConnection() == null) reconnect();
|
|
170
|
+
|
|
171
|
+
return getInstanceVariable("@connection");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@JRubyMethod(name = "database_name", frame = false)
|
|
175
|
+
public IRubyObject database_name(ThreadContext context) throws SQLException {
|
|
176
|
+
Connection connection = getConnection(true);
|
|
177
|
+
String name = connection.getCatalog();
|
|
178
|
+
|
|
179
|
+
if (null == name) {
|
|
180
|
+
name = connection.getMetaData().getUserName();
|
|
181
|
+
|
|
182
|
+
if (null == name) name = "db1";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return context.getRuntime().newString(name);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@JRubyMethod(name = "disconnect!", frame = false)
|
|
189
|
+
public IRubyObject disconnect() {
|
|
190
|
+
return setConnection(null);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@JRubyMethod
|
|
194
|
+
public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
|
|
195
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
196
|
+
public Object call(Connection c) throws SQLException {
|
|
197
|
+
Statement stmt = null;
|
|
198
|
+
String query = rubyApi.convertToRubyString(sql).getUnicodeValue();
|
|
199
|
+
try {
|
|
200
|
+
stmt = c.createStatement();
|
|
201
|
+
if (genericExecute(stmt, query)) {
|
|
202
|
+
return unmarshalResults(context, c.getMetaData(), stmt, false);
|
|
203
|
+
} else {
|
|
204
|
+
return unmarshalKeysOrUpdateCount(context, c, stmt);
|
|
205
|
+
}
|
|
206
|
+
} catch (SQLException sqe) {
|
|
207
|
+
if (context.getRuntime().isDebug()) {
|
|
208
|
+
System.out.println("Error SQL: " + query);
|
|
209
|
+
}
|
|
210
|
+
throw sqe;
|
|
211
|
+
} finally {
|
|
212
|
+
close(stmt);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
protected boolean genericExecute(Statement stmt, String query) throws SQLException {
|
|
219
|
+
return stmt.execute(query);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
protected IRubyObject unmarshalKeysOrUpdateCount(ThreadContext context, Connection c, Statement stmt) throws SQLException {
|
|
223
|
+
IRubyObject key = context.getRuntime().getNil();
|
|
224
|
+
if (c.getMetaData().supportsGetGeneratedKeys()) {
|
|
225
|
+
key = unmarshal_id_result(context.getRuntime(), stmt.getGeneratedKeys());
|
|
226
|
+
}
|
|
227
|
+
if (key.isNil()) {
|
|
228
|
+
return context.getRuntime().newFixnum(stmt.getUpdateCount());
|
|
229
|
+
} else {
|
|
230
|
+
return key;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@JRubyMethod(name = "execute_id_insert", required = 2)
|
|
235
|
+
public IRubyObject execute_id_insert(final ThreadContext context, final IRubyObject sql,
|
|
236
|
+
final IRubyObject id) throws SQLException {
|
|
237
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
238
|
+
public Object call(Connection c) throws SQLException {
|
|
239
|
+
String insert = rubyApi.convertToRubyString(sql).getUnicodeValue();
|
|
240
|
+
PreparedStatement ps = c.prepareStatement(insert);
|
|
241
|
+
try {
|
|
242
|
+
ps.setLong(1, RubyNumeric.fix2long(id));
|
|
243
|
+
ps.executeUpdate();
|
|
244
|
+
} catch (SQLException sqe) {
|
|
245
|
+
if (context.getRuntime().isDebug()) {
|
|
246
|
+
System.out.println("Error SQL: " + insert);
|
|
247
|
+
}
|
|
248
|
+
throw sqe;
|
|
249
|
+
} finally {
|
|
250
|
+
close(ps);
|
|
251
|
+
}
|
|
252
|
+
return id;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@JRubyMethod(name = "execute_insert", required = 1)
|
|
258
|
+
public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql)
|
|
259
|
+
throws SQLException {
|
|
260
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
261
|
+
public Object call(Connection c) throws SQLException {
|
|
262
|
+
Statement stmt = null;
|
|
263
|
+
String insert = rubyApi.convertToRubyString(sql).getUnicodeValue();
|
|
264
|
+
try {
|
|
265
|
+
stmt = c.createStatement();
|
|
266
|
+
stmt.executeUpdate(insert, Statement.RETURN_GENERATED_KEYS);
|
|
267
|
+
return unmarshal_id_result(context.getRuntime(), stmt.getGeneratedKeys());
|
|
268
|
+
} catch (SQLException sqe) {
|
|
269
|
+
if (context.getRuntime().isDebug()) {
|
|
270
|
+
System.out.println("Error SQL: " + insert);
|
|
271
|
+
}
|
|
272
|
+
throw sqe;
|
|
273
|
+
} finally {
|
|
274
|
+
close(stmt);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@JRubyMethod(name = "execute_query", required = 1)
|
|
281
|
+
public IRubyObject execute_query(final ThreadContext context, IRubyObject _sql)
|
|
282
|
+
throws SQLException, IOException {
|
|
283
|
+
String sql = rubyApi.convertToRubyString(_sql).getUnicodeValue();
|
|
284
|
+
|
|
285
|
+
return executeQuery(context, sql, 0);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@JRubyMethod(name = "execute_query", required = 2)
|
|
289
|
+
public IRubyObject execute_query(final ThreadContext context, IRubyObject _sql,
|
|
290
|
+
IRubyObject _maxRows) throws SQLException, IOException {
|
|
291
|
+
String sql = rubyApi.convertToRubyString(_sql).getUnicodeValue();
|
|
292
|
+
int maxrows = RubyNumeric.fix2int(_maxRows);
|
|
293
|
+
|
|
294
|
+
return executeQuery(context, sql, maxrows);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
protected IRubyObject executeQuery(final ThreadContext context, final String query, final int maxRows) {
|
|
298
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
299
|
+
public Object call(Connection c) throws SQLException {
|
|
300
|
+
Statement stmt = null;
|
|
301
|
+
try {
|
|
302
|
+
DatabaseMetaData metadata = c.getMetaData();
|
|
303
|
+
stmt = c.createStatement();
|
|
304
|
+
stmt.setMaxRows(maxRows);
|
|
305
|
+
return unmarshalResult(context, metadata, stmt.executeQuery(query), false);
|
|
306
|
+
} catch (SQLException sqe) {
|
|
307
|
+
if (context.getRuntime().isDebug()) {
|
|
308
|
+
System.out.println("Error SQL: " + query);
|
|
309
|
+
}
|
|
310
|
+
throw sqe;
|
|
311
|
+
} finally {
|
|
312
|
+
close(stmt);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@JRubyMethod(name = "execute_update", required = 1)
|
|
319
|
+
public IRubyObject execute_update(final ThreadContext context, final IRubyObject sql)
|
|
320
|
+
throws SQLException {
|
|
321
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
322
|
+
public Object call(Connection c) throws SQLException {
|
|
323
|
+
Statement stmt = null;
|
|
324
|
+
String update = rubyApi.convertToRubyString(sql).getUnicodeValue();
|
|
325
|
+
try {
|
|
326
|
+
stmt = c.createStatement();
|
|
327
|
+
return context.getRuntime().newFixnum((long)stmt.executeUpdate(update));
|
|
328
|
+
} catch (SQLException sqe) {
|
|
329
|
+
if (context.getRuntime().isDebug()) {
|
|
330
|
+
System.out.println("Error SQL: " + update);
|
|
331
|
+
}
|
|
332
|
+
throw sqe;
|
|
333
|
+
} finally {
|
|
334
|
+
close(stmt);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
@JRubyMethod(name = "indexes")
|
|
341
|
+
public IRubyObject indexes(ThreadContext context, IRubyObject tableName, IRubyObject name, IRubyObject schemaName) {
|
|
342
|
+
return indexes(context, toStringOrNull(tableName), toStringOrNull(name), toStringOrNull(schemaName));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private static final int INDEX_TABLE_NAME = 3;
|
|
346
|
+
private static final int INDEX_NON_UNIQUE = 4;
|
|
347
|
+
private static final int INDEX_NAME = 6;
|
|
348
|
+
private static final int INDEX_COLUMN_NAME = 9;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Default JDBC introspection for index metadata on the JdbcConnection.
|
|
352
|
+
*
|
|
353
|
+
* JDBC index metadata is denormalized (multiple rows may be returned for
|
|
354
|
+
* one index, one row per column in the index), so a simple block-based
|
|
355
|
+
* filter like that used for tables doesn't really work here. Callers
|
|
356
|
+
* should filter the return from this method instead.
|
|
357
|
+
*/
|
|
358
|
+
protected IRubyObject indexes(final ThreadContext context, final String tableNameArg, final String name, final String schemaNameArg) {
|
|
359
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
360
|
+
public Object call(Connection c) throws SQLException {
|
|
361
|
+
Ruby runtime = context.getRuntime();
|
|
362
|
+
DatabaseMetaData metadata = c.getMetaData();
|
|
363
|
+
String tableName = caseConvertIdentifierForJdbc(metadata, tableNameArg);
|
|
364
|
+
String schemaName = caseConvertIdentifierForJdbc(metadata, schemaNameArg);
|
|
365
|
+
|
|
366
|
+
ResultSet resultSet = null;
|
|
367
|
+
List indexes = new ArrayList();
|
|
368
|
+
try {
|
|
369
|
+
resultSet = metadata.getIndexInfo(null, schemaName, tableName, false, true);
|
|
370
|
+
List primaryKeys = primaryKeys(context, tableName);
|
|
371
|
+
String currentIndex = null;
|
|
372
|
+
RubyModule indexDefinitionClass = getConnectionAdapters(runtime).getClass("IndexDefinition");
|
|
373
|
+
|
|
374
|
+
while (resultSet.next()) {
|
|
375
|
+
String indexName = resultSet.getString(INDEX_NAME);
|
|
376
|
+
|
|
377
|
+
if (indexName == null) continue;
|
|
378
|
+
|
|
379
|
+
indexName = caseConvertIdentifierForRails(metadata, indexName);
|
|
380
|
+
|
|
381
|
+
RubyString columnName = RubyString.newUnicodeString(runtime, caseConvertIdentifierForRails(metadata, resultSet.getString(INDEX_COLUMN_NAME)));
|
|
382
|
+
|
|
383
|
+
if (primaryKeys.contains(columnName)) continue;
|
|
384
|
+
|
|
385
|
+
// We are working on a new index
|
|
386
|
+
if (!indexName.equals(currentIndex)) {
|
|
387
|
+
currentIndex = indexName;
|
|
388
|
+
|
|
389
|
+
tableName = caseConvertIdentifierForRails(metadata, resultSet.getString(INDEX_TABLE_NAME));
|
|
390
|
+
boolean nonUnique = resultSet.getBoolean(INDEX_NON_UNIQUE);
|
|
391
|
+
|
|
392
|
+
IRubyObject indexDefinition = indexDefinitionClass.callMethod(context, "new",
|
|
393
|
+
new IRubyObject[] {
|
|
394
|
+
RubyString.newUnicodeString(runtime, tableName),
|
|
395
|
+
RubyString.newUnicodeString(runtime, indexName),
|
|
396
|
+
runtime.newBoolean(!nonUnique),
|
|
397
|
+
runtime.newArray()
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// empty list for column names, we'll add to that in just a bit
|
|
401
|
+
indexes.add(indexDefinition);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// One or more columns can be associated with an index
|
|
405
|
+
IRubyObject lastIndex = (IRubyObject) indexes.get(indexes.size() - 1);
|
|
406
|
+
|
|
407
|
+
if (lastIndex != null) {
|
|
408
|
+
lastIndex.callMethod(context, "columns").callMethod(context, "<<", columnName);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return runtime.newArray(indexes);
|
|
413
|
+
} finally {
|
|
414
|
+
close(resultSet);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
@JRubyMethod(name = "insert?", required = 1, meta = true, frame = false)
|
|
421
|
+
public static IRubyObject insert_p(ThreadContext context, IRubyObject recv, IRubyObject _sql) {
|
|
422
|
+
ByteList sql = rubyApi.convertToRubyString(_sql).getByteList();
|
|
423
|
+
|
|
424
|
+
return context.getRuntime().newBoolean(startsWithNoCaseCmp(sql, INSERT));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/*
|
|
428
|
+
* sql, values, types, name = nil, pk = nil, id_value = nil, sequence_name = nil
|
|
429
|
+
*/
|
|
430
|
+
@JRubyMethod(name = "insert_bind", required = 3, rest = true)
|
|
431
|
+
public IRubyObject insert_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
|
|
432
|
+
final Ruby runtime = context.getRuntime();
|
|
433
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
434
|
+
public Object call(Connection c) throws SQLException {
|
|
435
|
+
PreparedStatement ps = null;
|
|
436
|
+
try {
|
|
437
|
+
ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString(), Statement.RETURN_GENERATED_KEYS);
|
|
438
|
+
setValuesOnPS(ps, context, args[1], args[2]);
|
|
439
|
+
ps.executeUpdate();
|
|
440
|
+
return unmarshal_id_result(runtime, ps.getGeneratedKeys());
|
|
441
|
+
} finally {
|
|
442
|
+
close(ps);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@JRubyMethod(name = "native_database_types", frame = false)
|
|
449
|
+
public IRubyObject native_database_types() {
|
|
450
|
+
return getInstanceVariable("@native_database_types");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@JRubyMethod(name = "primary_keys", required = 1)
|
|
455
|
+
public IRubyObject primary_keys(ThreadContext context, IRubyObject tableName) throws SQLException {
|
|
456
|
+
return context.getRuntime().newArray(primaryKeys(context, tableName.toString()));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
protected List primaryKeys(final ThreadContext context, final String tableNameArg) {
|
|
460
|
+
return (List) withConnectionAndRetry(context, new SQLBlock() {
|
|
461
|
+
public Object call(Connection c) throws SQLException {
|
|
462
|
+
Ruby runtime = context.getRuntime();
|
|
463
|
+
DatabaseMetaData metadata = c.getMetaData();
|
|
464
|
+
String tableName = caseConvertIdentifierForJdbc(metadata, tableNameArg);
|
|
465
|
+
ResultSet resultSet = null;
|
|
466
|
+
List keyNames = new ArrayList();
|
|
467
|
+
try {
|
|
468
|
+
TableNameComponents components = extractTableNameComponents(c, null, tableName);
|
|
469
|
+
resultSet = metadata.getPrimaryKeys(components.catalog, components.schema, components.table);
|
|
470
|
+
|
|
471
|
+
while (resultSet.next()) {
|
|
472
|
+
keyNames.add(RubyString.newUnicodeString(runtime,
|
|
473
|
+
caseConvertIdentifierForRails(metadata, resultSet.getString(4))));
|
|
474
|
+
}
|
|
475
|
+
} finally {
|
|
476
|
+
close(resultSet);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return keyNames;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
@JRubyMethod(name = "reconnect!")
|
|
485
|
+
public IRubyObject reconnect() {
|
|
486
|
+
return setConnection(getConnectionFactory().newConnection());
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@JRubyMethod(name = "rollback")
|
|
491
|
+
public IRubyObject rollback(ThreadContext context) throws SQLException {
|
|
492
|
+
final Ruby runtime = context.getRuntime();
|
|
493
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
494
|
+
public Object call(Connection c) throws SQLException {
|
|
495
|
+
Connection connection = getConnection(true);
|
|
496
|
+
|
|
497
|
+
if (!connection.getAutoCommit()) {
|
|
498
|
+
try {
|
|
499
|
+
connection.rollback();
|
|
500
|
+
} finally {
|
|
501
|
+
connection.setAutoCommit(true);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return runtime.getNil();
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
@JRubyMethod(name = "select?", required = 1, meta = true, frame = false)
|
|
511
|
+
public static IRubyObject select_p(ThreadContext context, IRubyObject recv, IRubyObject _sql) {
|
|
512
|
+
ByteList sql = rubyApi.convertToRubyString(_sql).getByteList();
|
|
513
|
+
|
|
514
|
+
return context.getRuntime().newBoolean(startsWithNoCaseCmp(sql, SELECT) || startsWithNoCaseCmp(sql, WITH) ||
|
|
515
|
+
startsWithNoCaseCmp(sql, SHOW) || startsWithNoCaseCmp(sql, CALL));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
@JRubyMethod(name = "set_native_database_types")
|
|
519
|
+
public IRubyObject set_native_database_types(ThreadContext context) throws SQLException, IOException {
|
|
520
|
+
Ruby runtime = context.getRuntime();
|
|
521
|
+
DatabaseMetaData metadata = getConnection(true).getMetaData();
|
|
522
|
+
IRubyObject types = unmarshalResult(context, metadata, metadata.getTypeInfo(), true);
|
|
523
|
+
IRubyObject typeConverter = getConnectionAdapters(runtime).getConstant("JdbcTypeConverter");
|
|
524
|
+
IRubyObject value = rubyApi.callMethod(rubyApi.callMethod(typeConverter, "new", types), "choose_best_types");
|
|
525
|
+
setInstanceVariable("@native_types", value);
|
|
526
|
+
|
|
527
|
+
return runtime.getNil();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
@JRubyMethod(name = "tables")
|
|
531
|
+
public IRubyObject tables(ThreadContext context) {
|
|
532
|
+
return tables(context, null, null, null, TABLE_TYPE);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
@JRubyMethod(name = "tables")
|
|
536
|
+
public IRubyObject tables(ThreadContext context, IRubyObject catalog) {
|
|
537
|
+
return tables(context, toStringOrNull(catalog), null, null, TABLE_TYPE);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
@JRubyMethod(name = "tables")
|
|
541
|
+
public IRubyObject tables(ThreadContext context, IRubyObject catalog, IRubyObject schemaPattern) {
|
|
542
|
+
return tables(context, toStringOrNull(catalog), toStringOrNull(schemaPattern), null, TABLE_TYPE);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
@JRubyMethod(name = "tables")
|
|
546
|
+
public IRubyObject tables(ThreadContext context, IRubyObject catalog, IRubyObject schemaPattern, IRubyObject tablePattern) {
|
|
547
|
+
return tables(context, toStringOrNull(catalog), toStringOrNull(schemaPattern), toStringOrNull(tablePattern), TABLE_TYPE);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
@JRubyMethod(name = "tables", required = 4, rest = true)
|
|
551
|
+
public IRubyObject tables(ThreadContext context, IRubyObject[] args) {
|
|
552
|
+
return tables(context, toStringOrNull(args[0]), toStringOrNull(args[1]), toStringOrNull(args[2]), getTypes(args[3]));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
protected IRubyObject tables(ThreadContext context, String catalog, String schemaPattern, String tablePattern, String[] types) {
|
|
556
|
+
return (IRubyObject) withConnectionAndRetry(context, tableLookupBlock(context.getRuntime(), catalog, schemaPattern, tablePattern, types, false));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/*
|
|
560
|
+
* sql, values, types, name = nil
|
|
561
|
+
*/
|
|
562
|
+
@JRubyMethod(name = "update_bind", required = 3, rest = true)
|
|
563
|
+
public IRubyObject update_bind(final ThreadContext context, final IRubyObject[] args) throws SQLException {
|
|
564
|
+
final Ruby runtime = context.getRuntime();
|
|
565
|
+
Arity.checkArgumentCount(runtime, args, 3, 4);
|
|
566
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
567
|
+
public Object call(Connection c) throws SQLException {
|
|
568
|
+
PreparedStatement ps = null;
|
|
569
|
+
try {
|
|
570
|
+
ps = c.prepareStatement(rubyApi.convertToRubyString(args[0]).toString());
|
|
571
|
+
setValuesOnPS(ps, context, args[1], args[2]);
|
|
572
|
+
ps.executeUpdate();
|
|
573
|
+
} finally {
|
|
574
|
+
close(ps);
|
|
575
|
+
}
|
|
576
|
+
return runtime.getNil();
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@JRubyMethod(name = "with_connection_retry_guard", frame = true)
|
|
582
|
+
public IRubyObject with_connection_retry_guard(final ThreadContext context, final Block block) {
|
|
583
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
584
|
+
public Object call(Connection c) throws SQLException {
|
|
585
|
+
return block.call(context, new IRubyObject[] { wrappedConnection(c) });
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/*
|
|
591
|
+
* (is binary?, colname, tablename, primary key, id, value)
|
|
592
|
+
*/
|
|
593
|
+
@JRubyMethod(name = "write_large_object", required = 6)
|
|
594
|
+
public IRubyObject write_large_object(ThreadContext context, final IRubyObject[] args)
|
|
595
|
+
throws SQLException, IOException {
|
|
596
|
+
final Ruby runtime = context.getRuntime();
|
|
597
|
+
return (IRubyObject) withConnectionAndRetry(context, new SQLBlock() {
|
|
598
|
+
public Object call(Connection c) throws SQLException {
|
|
599
|
+
String sql = "UPDATE " + rubyApi.convertToRubyString(args[2])
|
|
600
|
+
+ " SET " + rubyApi.convertToRubyString(args[1])
|
|
601
|
+
+ " = ? WHERE " + rubyApi.convertToRubyString(args[3])
|
|
602
|
+
+ "=" + rubyApi.convertToRubyString(args[4]);
|
|
603
|
+
PreparedStatement ps = null;
|
|
604
|
+
try {
|
|
605
|
+
ps = c.prepareStatement(sql);
|
|
606
|
+
if (args[0].isTrue()) { // binary
|
|
607
|
+
ByteList outp = rubyApi.convertToRubyString(args[5]).getByteList();
|
|
608
|
+
ps.setBinaryStream(1, new ByteArrayInputStream(outp.bytes,
|
|
609
|
+
outp.begin, outp.realSize), outp.realSize);
|
|
610
|
+
} else { // clob
|
|
611
|
+
String ss = rubyApi.convertToRubyString(args[5]).getUnicodeValue();
|
|
612
|
+
ps.setCharacterStream(1, new StringReader(ss), ss.length());
|
|
613
|
+
}
|
|
614
|
+
ps.executeUpdate();
|
|
615
|
+
} finally {
|
|
616
|
+
close(ps);
|
|
617
|
+
}
|
|
618
|
+
return runtime.getNil();
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Convert an identifier coming back from the database to a case which Rails is expecting.
|
|
625
|
+
*
|
|
626
|
+
* Assumption: Rails identifiers will be quoted for mixed or will stay mixed
|
|
627
|
+
* as identifier names in Rails itself. Otherwise, they expect identifiers to
|
|
628
|
+
* be lower-case. Databases which store identifiers uppercase should be made
|
|
629
|
+
* lower-case.
|
|
630
|
+
*
|
|
631
|
+
* Assumption 2: It is always safe to convert all upper case names since it appears that
|
|
632
|
+
* some adapters do not report StoresUpper/Lower/Mixed correctly (am I right postgres/mysql?).
|
|
633
|
+
*/
|
|
634
|
+
public static String caseConvertIdentifierForRails(DatabaseMetaData metadata, String value)
|
|
635
|
+
throws SQLException {
|
|
636
|
+
if (value == null) return null;
|
|
637
|
+
|
|
638
|
+
return metadata.storesUpperCaseIdentifiers() ? value.toLowerCase() : value;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Convert an identifier destined for a method which cares about the databases internal
|
|
643
|
+
* storage case. Methods like DatabaseMetaData.getPrimaryKeys() needs the table name to match
|
|
644
|
+
* the internal storage name. Arbtrary queries and the like DO NOT need to do this.
|
|
645
|
+
*/
|
|
646
|
+
public static String caseConvertIdentifierForJdbc(DatabaseMetaData metadata, String value)
|
|
647
|
+
throws SQLException {
|
|
648
|
+
if (value == null) return null;
|
|
649
|
+
boolean isPostgres = metadata.getDatabaseProductName().equals("PostgreSQL");
|
|
650
|
+
|
|
651
|
+
if (metadata.storesUpperCaseIdentifiers()) {
|
|
652
|
+
return value.toUpperCase();
|
|
653
|
+
} else if (metadata.storesLowerCaseIdentifiers() && ! isPostgres) {
|
|
654
|
+
return value.toLowerCase();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return value;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// helpers
|
|
661
|
+
protected static void close(Connection connection) {
|
|
662
|
+
if (connection != null) {
|
|
663
|
+
try {
|
|
664
|
+
connection.close();
|
|
665
|
+
} catch(Exception e) {}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
public static void close(ResultSet resultSet) {
|
|
670
|
+
if (resultSet != null) {
|
|
671
|
+
try {
|
|
672
|
+
resultSet.close();
|
|
673
|
+
} catch(Exception e) {}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
public static void close(Statement statement) {
|
|
678
|
+
if (statement != null) {
|
|
679
|
+
try {
|
|
680
|
+
statement.close();
|
|
681
|
+
} catch(Exception e) {}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
protected IRubyObject config_value(ThreadContext context, String key) {
|
|
686
|
+
IRubyObject config_hash = getInstanceVariable("@config");
|
|
687
|
+
|
|
688
|
+
return config_hash.callMethod(context, "[]", context.getRuntime().newSymbol(key));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
private static String toStringOrNull(IRubyObject arg) {
|
|
692
|
+
return arg.isNil() ? null : arg.toString();
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
protected IRubyObject doubleToRuby(Ruby runtime, ResultSet resultSet, double doubleValue)
|
|
696
|
+
throws SQLException, IOException {
|
|
697
|
+
if (doubleValue == 0 && resultSet.wasNull()) return runtime.getNil();
|
|
698
|
+
return runtime.newFloat(doubleValue);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
protected Connection getConnection() {
|
|
702
|
+
return getConnection(false);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
protected Connection getConnection(boolean error) {
|
|
706
|
+
Connection conn = (Connection) dataGetStruct();
|
|
707
|
+
if(error && conn == null) {
|
|
708
|
+
RubyClass err = getRuntime().getModule("ActiveRecord").getClass("ConnectionNotEstablished");
|
|
709
|
+
throw new RaiseException(getRuntime(), err, "no connection available", false);
|
|
710
|
+
}
|
|
711
|
+
return conn;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
protected IRubyObject getAdapter(ThreadContext context) {
|
|
715
|
+
return callMethod(context, "adapter");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
protected IRubyObject getJdbcColumnClass(ThreadContext context) {
|
|
719
|
+
return getAdapter(context).callMethod(context, "jdbc_column_class");
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
protected JdbcConnectionFactory getConnectionFactory() throws RaiseException {
|
|
723
|
+
IRubyObject connection_factory = getInstanceVariable("@connection_factory");
|
|
724
|
+
JdbcConnectionFactory factory = null;
|
|
725
|
+
try {
|
|
726
|
+
factory = (JdbcConnectionFactory) JavaEmbedUtils.rubyToJava(
|
|
727
|
+
connection_factory.getRuntime(), connection_factory, JdbcConnectionFactory.class);
|
|
728
|
+
} catch (Exception e) {
|
|
729
|
+
factory = null;
|
|
730
|
+
}
|
|
731
|
+
if (factory == null) {
|
|
732
|
+
throw getRuntime().newRuntimeError("@connection_factory not set properly");
|
|
733
|
+
}
|
|
734
|
+
return factory;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private static String[] getTypes(IRubyObject typeArg) {
|
|
738
|
+
if (!(typeArg instanceof RubyArray)) return new String[] { typeArg.toString() };
|
|
739
|
+
|
|
740
|
+
IRubyObject[] arr = rubyApi.convertToJavaArray(typeArg);
|
|
741
|
+
String[] types = new String[arr.length];
|
|
742
|
+
for (int i = 0; i < types.length; i++) {
|
|
743
|
+
types[i] = arr[i].toString();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return types;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private static int getTypeValueFor(Ruby runtime, IRubyObject type) throws SQLException {
|
|
750
|
+
// How could this ever yield anything useful?
|
|
751
|
+
if (!(type instanceof RubySymbol)) type = rubyApi.callMethod(type, "class");
|
|
752
|
+
|
|
753
|
+
// Assumption; If this is a symbol then it will be backed by an interned string. (enebo)
|
|
754
|
+
String internedValue = type.asJavaString();
|
|
755
|
+
|
|
756
|
+
if(internedValue == "string") {
|
|
757
|
+
return Types.VARCHAR;
|
|
758
|
+
} else if(internedValue == "text") {
|
|
759
|
+
return Types.CLOB;
|
|
760
|
+
} else if(internedValue == "integer") {
|
|
761
|
+
return Types.INTEGER;
|
|
762
|
+
} else if(internedValue == "decimal") {
|
|
763
|
+
return Types.DECIMAL;
|
|
764
|
+
} else if(internedValue == "float") {
|
|
765
|
+
return Types.FLOAT;
|
|
766
|
+
} else if(internedValue == "datetime") {
|
|
767
|
+
return Types.TIMESTAMP;
|
|
768
|
+
} else if(internedValue == "timestamp") {
|
|
769
|
+
return Types.TIMESTAMP;
|
|
770
|
+
} else if(internedValue == "time") {
|
|
771
|
+
return Types.TIME;
|
|
772
|
+
} else if(internedValue == "date") {
|
|
773
|
+
return Types.DATE;
|
|
774
|
+
} else if(internedValue == "binary") {
|
|
775
|
+
return Types.BLOB;
|
|
776
|
+
} else if(internedValue == "boolean") {
|
|
777
|
+
return Types.BOOLEAN;
|
|
778
|
+
} else {
|
|
779
|
+
return -1;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private boolean isConnectionBroken(ThreadContext context, Connection c) {
|
|
784
|
+
try {
|
|
785
|
+
IRubyObject alive = config_value(context, "connection_alive_sql");
|
|
786
|
+
if (select_p(context, this, alive).isTrue()) {
|
|
787
|
+
String connectionSQL = rubyApi.convertToRubyString(alive).toString();
|
|
788
|
+
Statement s = c.createStatement();
|
|
789
|
+
try {
|
|
790
|
+
s.execute(connectionSQL);
|
|
791
|
+
} finally {
|
|
792
|
+
close(s);
|
|
793
|
+
}
|
|
794
|
+
return false;
|
|
795
|
+
} else {
|
|
796
|
+
return !c.isClosed();
|
|
797
|
+
}
|
|
798
|
+
} catch (Exception sx) {
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
protected IRubyObject integerToRuby(Ruby runtime, ResultSet resultSet, long longValue)
|
|
804
|
+
throws SQLException {
|
|
805
|
+
if (longValue == 0 && resultSet.wasNull()) return runtime.getNil();
|
|
806
|
+
|
|
807
|
+
return runtime.newFixnum(longValue);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
protected IRubyObject bigIntegerToRuby(Ruby runtime, ResultSet resultSet, String bigint) throws SQLException {
|
|
811
|
+
if (bigint == null && resultSet.wasNull()) return runtime.getNil();
|
|
812
|
+
|
|
813
|
+
return RubyBignum.bignorm(runtime, new BigInteger(bigint));
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
protected IRubyObject jdbcToRuby(Ruby runtime, int column, int type, ResultSet resultSet)
|
|
817
|
+
throws SQLException {
|
|
818
|
+
try {
|
|
819
|
+
switch (type) {
|
|
820
|
+
case Types.BINARY:
|
|
821
|
+
case Types.BLOB:
|
|
822
|
+
case Types.LONGVARBINARY:
|
|
823
|
+
case Types.VARBINARY:
|
|
824
|
+
return streamToRuby(runtime, resultSet, resultSet.getBinaryStream(column));
|
|
825
|
+
case Types.LONGVARCHAR:
|
|
826
|
+
return runtime.is1_9() ?
|
|
827
|
+
readerToRuby(runtime, resultSet, resultSet.getCharacterStream(column)) :
|
|
828
|
+
streamToRuby(runtime, resultSet, resultSet.getBinaryStream(column));
|
|
829
|
+
case Types.CLOB:
|
|
830
|
+
return readerToRuby(runtime, resultSet, resultSet.getCharacterStream(column));
|
|
831
|
+
case Types.TIMESTAMP:
|
|
832
|
+
return timestampToRuby(runtime, resultSet, resultSet.getTimestamp(column));
|
|
833
|
+
case Types.INTEGER:
|
|
834
|
+
case Types.SMALLINT:
|
|
835
|
+
case Types.TINYINT:
|
|
836
|
+
return integerToRuby(runtime, resultSet, resultSet.getLong(column));
|
|
837
|
+
case Types.REAL:
|
|
838
|
+
return doubleToRuby(runtime, resultSet, resultSet.getDouble(column));
|
|
839
|
+
case Types.BIGINT:
|
|
840
|
+
return bigIntegerToRuby(runtime, resultSet, resultSet.getString(column));
|
|
841
|
+
default:
|
|
842
|
+
return stringToRuby(runtime, resultSet, resultSet.getString(column));
|
|
843
|
+
}
|
|
844
|
+
} catch (IOException ioe) {
|
|
845
|
+
throw (SQLException) new SQLException(ioe.getMessage()).initCause(ioe);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
protected void populateFromResultSet(ThreadContext context, Ruby runtime, List results,
|
|
850
|
+
ResultSet resultSet, ColumnData[] columns) throws SQLException {
|
|
851
|
+
int columnCount = columns.length;
|
|
852
|
+
|
|
853
|
+
while (resultSet.next()) {
|
|
854
|
+
RubyHash row = RubyHash.newHash(runtime);
|
|
855
|
+
|
|
856
|
+
for (int i = 0; i < columnCount; i++) {
|
|
857
|
+
row.op_aset(context, columns[i].name, jdbcToRuby(runtime, columns[i].index, columns[i].type, resultSet));
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
results.add(row);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
protected IRubyObject readerToRuby(Ruby runtime, ResultSet resultSet, Reader reader)
|
|
866
|
+
throws SQLException, IOException {
|
|
867
|
+
if (reader == null && resultSet.wasNull()) return runtime.getNil();
|
|
868
|
+
|
|
869
|
+
StringBuffer str = new StringBuffer(2048);
|
|
870
|
+
try {
|
|
871
|
+
char[] buf = new char[2048];
|
|
872
|
+
|
|
873
|
+
for (int n = reader.read(buf); n != -1; n = reader.read(buf)) {
|
|
874
|
+
str.append(buf, 0, n);
|
|
875
|
+
}
|
|
876
|
+
} finally {
|
|
877
|
+
reader.close();
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return RubyString.newUnicodeString(runtime, str.toString());
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
private IRubyObject setConnection(Connection c) {
|
|
884
|
+
close(getConnection()); // Close previously open connection if there is one
|
|
885
|
+
|
|
886
|
+
IRubyObject rubyconn = c != null ? wrappedConnection(c) : getRuntime().getNil();
|
|
887
|
+
setInstanceVariable("@connection", rubyconn);
|
|
888
|
+
dataWrapStruct(c);
|
|
889
|
+
return this;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private final static DateFormat FORMAT = new SimpleDateFormat("%y-%M-%d %H:%m:%s");
|
|
893
|
+
|
|
894
|
+
private static void setValue(PreparedStatement ps, int index, ThreadContext context,
|
|
895
|
+
IRubyObject value, IRubyObject type) throws SQLException {
|
|
896
|
+
final int tp = getTypeValueFor(context.getRuntime(), type);
|
|
897
|
+
if(value.isNil()) {
|
|
898
|
+
ps.setNull(index, tp);
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
switch(tp) {
|
|
903
|
+
case Types.VARCHAR:
|
|
904
|
+
case Types.CLOB:
|
|
905
|
+
ps.setString(index, RubyString.objAsString(context, value).toString());
|
|
906
|
+
break;
|
|
907
|
+
case Types.INTEGER:
|
|
908
|
+
ps.setLong(index, RubyNumeric.fix2long(value));
|
|
909
|
+
break;
|
|
910
|
+
case Types.FLOAT:
|
|
911
|
+
ps.setDouble(index, ((RubyNumeric)value).getDoubleValue());
|
|
912
|
+
break;
|
|
913
|
+
case Types.TIMESTAMP:
|
|
914
|
+
case Types.TIME:
|
|
915
|
+
case Types.DATE:
|
|
916
|
+
if(!(value instanceof RubyTime)) {
|
|
917
|
+
try {
|
|
918
|
+
Date dd = FORMAT.parse(RubyString.objAsString(context, value).toString());
|
|
919
|
+
ps.setTimestamp(index, new java.sql.Timestamp(dd.getTime()), Calendar.getInstance());
|
|
920
|
+
} catch(Exception e) {
|
|
921
|
+
ps.setString(index, RubyString.objAsString(context, value).toString());
|
|
922
|
+
}
|
|
923
|
+
} else {
|
|
924
|
+
RubyTime rubyTime = (RubyTime) value;
|
|
925
|
+
java.util.Date date = rubyTime.getJavaDate();
|
|
926
|
+
long millis = date.getTime();
|
|
927
|
+
long micros = rubyTime.microseconds() - millis / 1000;
|
|
928
|
+
java.sql.Timestamp ts = new java.sql.Timestamp(millis);
|
|
929
|
+
java.util.Calendar cal = Calendar.getInstance();
|
|
930
|
+
cal.setTime(date);
|
|
931
|
+
ts.setNanos((int)(micros * 1000));
|
|
932
|
+
ps.setTimestamp(index, ts, cal);
|
|
933
|
+
}
|
|
934
|
+
break;
|
|
935
|
+
case Types.BOOLEAN:
|
|
936
|
+
ps.setBoolean(index, value.isTrue());
|
|
937
|
+
break;
|
|
938
|
+
default: throw new RuntimeException("type " + type + " not supported in _bind yet");
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private static void setValuesOnPS(PreparedStatement ps, ThreadContext context,
|
|
943
|
+
IRubyObject valuesArg, IRubyObject typesArg) throws SQLException {
|
|
944
|
+
RubyArray values = (RubyArray) valuesArg;
|
|
945
|
+
RubyArray types = (RubyArray) typesArg;
|
|
946
|
+
|
|
947
|
+
for(int i=0, j=values.getLength(); i<j; i++) {
|
|
948
|
+
setValue(ps, i+1, context, values.eltInternal(i), types.eltInternal(i));
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
protected IRubyObject streamToRuby(Ruby runtime, ResultSet resultSet, InputStream is)
|
|
953
|
+
throws SQLException, IOException {
|
|
954
|
+
if (is == null && resultSet.wasNull()) return runtime.getNil();
|
|
955
|
+
|
|
956
|
+
ByteList str = new ByteList(2048);
|
|
957
|
+
try {
|
|
958
|
+
byte[] buf = new byte[2048];
|
|
959
|
+
|
|
960
|
+
for (int n = is.read(buf); n != -1; n = is.read(buf)) {
|
|
961
|
+
str.append(buf, 0, n);
|
|
962
|
+
}
|
|
963
|
+
} finally {
|
|
964
|
+
is.close();
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
return runtime.newString(str);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
protected IRubyObject stringToRuby(Ruby runtime, ResultSet resultSet, String string)
|
|
971
|
+
throws SQLException, IOException {
|
|
972
|
+
if (string == null && resultSet.wasNull()) return runtime.getNil();
|
|
973
|
+
|
|
974
|
+
return RubyString.newUnicodeString(runtime, string);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
protected SQLBlock tableLookupBlock(final Ruby runtime,
|
|
979
|
+
final String catalog, final String schemapat,
|
|
980
|
+
final String tablepat, final String[] types, final boolean downCase) {
|
|
981
|
+
final int TABLE_SCHEM = 2;
|
|
982
|
+
final int TABLE_NAME = 3;
|
|
983
|
+
final int TABLE_TYPE = 4;
|
|
984
|
+
return new SQLBlock() {
|
|
985
|
+
public Object call(Connection c) throws SQLException {
|
|
986
|
+
ResultSet rs = null;
|
|
987
|
+
try {
|
|
988
|
+
DatabaseMetaData metadata = c.getMetaData();
|
|
989
|
+
String clzName = metadata.getClass().getName().toLowerCase();
|
|
990
|
+
boolean isOracle = clzName.indexOf("oracle") != -1 || clzName.indexOf("oci") != -1;
|
|
991
|
+
boolean isDerby = clzName.indexOf("derby") != -1;
|
|
992
|
+
boolean isMssql = clzName.indexOf("sqlserver") != -1 || clzName.indexOf("tds") != -1;
|
|
993
|
+
|
|
994
|
+
String realschema = schemapat;
|
|
995
|
+
String realtablepat = tablepat;
|
|
996
|
+
|
|
997
|
+
if (isDerby && realschema != null && realschema.equals("")) realschema = null; // Derby doesn't like empty-string schema name
|
|
998
|
+
if (realtablepat != null) realtablepat = caseConvertIdentifierForJdbc(metadata, realtablepat);
|
|
999
|
+
if (realschema != null) realschema = caseConvertIdentifierForJdbc(metadata, realschema);
|
|
1000
|
+
|
|
1001
|
+
rs = metadata.getTables(catalog, realschema, realtablepat, types);
|
|
1002
|
+
List arr = new ArrayList();
|
|
1003
|
+
while (rs.next()) {
|
|
1004
|
+
String name;
|
|
1005
|
+
String schema = rs.getString(TABLE_SCHEM) != null ? rs.getString(TABLE_SCHEM).toLowerCase() : null;
|
|
1006
|
+
|
|
1007
|
+
if (downCase) {
|
|
1008
|
+
name = rs.getString(TABLE_NAME).toLowerCase();
|
|
1009
|
+
} else {
|
|
1010
|
+
name = caseConvertIdentifierForRails(metadata, rs.getString(TABLE_NAME));
|
|
1011
|
+
}
|
|
1012
|
+
// Handle stupid Oracle 10g RecycleBin feature
|
|
1013
|
+
if (isOracle && name.startsWith("bin$")) {
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
// Under mssql, don't return system tables/views unless they're explicitly asked for.
|
|
1017
|
+
if (isMssql && realschema==null &&
|
|
1018
|
+
("sys".equals(schema) || "information_schema".equals(schema))) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
arr.add(RubyString.newUnicodeString(runtime, name));
|
|
1022
|
+
}
|
|
1023
|
+
return runtime.newArray(arr);
|
|
1024
|
+
} finally {
|
|
1025
|
+
close(rs);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
protected IRubyObject timestampToRuby(Ruby runtime, ResultSet resultSet, Timestamp time)
|
|
1032
|
+
throws SQLException {
|
|
1033
|
+
if (time == null && resultSet.wasNull()) return runtime.getNil();
|
|
1034
|
+
|
|
1035
|
+
String str = time.toString();
|
|
1036
|
+
if (str.endsWith(" 00:00:00.0")) {
|
|
1037
|
+
str = str.substring(0, str.length() - (" 00:00:00.0".length()));
|
|
1038
|
+
}
|
|
1039
|
+
if (str.endsWith(".0")) {
|
|
1040
|
+
str = str.substring(0, str.length() - (".0".length()));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
return RubyString.newUnicodeString(runtime, str);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
protected static final int COLUMN_NAME = 4;
|
|
1047
|
+
protected static final int DATA_TYPE = 5;
|
|
1048
|
+
protected static final int TYPE_NAME = 6;
|
|
1049
|
+
protected static final int COLUMN_SIZE = 7;
|
|
1050
|
+
protected static final int DECIMAL_DIGITS = 9;
|
|
1051
|
+
protected static final int COLUMN_DEF = 13;
|
|
1052
|
+
protected static final int IS_NULLABLE = 18;
|
|
1053
|
+
|
|
1054
|
+
protected int intFromResultSet(ResultSet resultSet, int column) throws SQLException {
|
|
1055
|
+
int precision = resultSet.getInt(column);
|
|
1056
|
+
|
|
1057
|
+
return precision == 0 && resultSet.wasNull() ? -1 : precision;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Create a string which represents a sql type usable by Rails from the resultSet column
|
|
1062
|
+
* metadata object.
|
|
1063
|
+
*/
|
|
1064
|
+
protected String typeFromResultSet(ResultSet resultSet) throws SQLException {
|
|
1065
|
+
int precision = intFromResultSet(resultSet, COLUMN_SIZE);
|
|
1066
|
+
int scale = intFromResultSet(resultSet, DECIMAL_DIGITS);
|
|
1067
|
+
|
|
1068
|
+
String type = resultSet.getString(TYPE_NAME);
|
|
1069
|
+
if (precision > 0) {
|
|
1070
|
+
type += "(" + precision;
|
|
1071
|
+
if(scale > 0) type += "," + scale;
|
|
1072
|
+
type += ")";
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
return type;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
private IRubyObject defaultValueFromResultSet(Ruby runtime, ResultSet resultSet)
|
|
1079
|
+
throws SQLException {
|
|
1080
|
+
String defaultValue = resultSet.getString(COLUMN_DEF);
|
|
1081
|
+
|
|
1082
|
+
return defaultValue == null ? runtime.getNil() : RubyString.newUnicodeString(runtime, defaultValue);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
private IRubyObject unmarshal_columns(ThreadContext context, DatabaseMetaData metadata,
|
|
1086
|
+
ResultSet rs, ResultSet pkeys) throws SQLException {
|
|
1087
|
+
try {
|
|
1088
|
+
Ruby runtime = context.getRuntime();
|
|
1089
|
+
List columns = new ArrayList();
|
|
1090
|
+
List pkeyNames = new ArrayList();
|
|
1091
|
+
String clzName = metadata.getClass().getName().toLowerCase();
|
|
1092
|
+
|
|
1093
|
+
RubyHash types = (RubyHash) native_database_types();
|
|
1094
|
+
IRubyObject jdbcCol = getJdbcColumnClass(context);
|
|
1095
|
+
|
|
1096
|
+
while (pkeys.next()) {
|
|
1097
|
+
pkeyNames.add(pkeys.getString(COLUMN_NAME));
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
while (rs.next()) {
|
|
1101
|
+
String colName = rs.getString(COLUMN_NAME);
|
|
1102
|
+
IRubyObject column = jdbcCol.callMethod(context, "new",
|
|
1103
|
+
new IRubyObject[] {
|
|
1104
|
+
getInstanceVariable("@config"),
|
|
1105
|
+
RubyString.newUnicodeString(runtime,
|
|
1106
|
+
caseConvertIdentifierForRails(metadata, colName)),
|
|
1107
|
+
defaultValueFromResultSet(runtime, rs),
|
|
1108
|
+
RubyString.newUnicodeString(runtime, typeFromResultSet(rs)),
|
|
1109
|
+
runtime.newBoolean(!rs.getString(IS_NULLABLE).trim().equals("NO"))
|
|
1110
|
+
});
|
|
1111
|
+
columns.add(column);
|
|
1112
|
+
|
|
1113
|
+
if (pkeyNames.contains(colName)) {
|
|
1114
|
+
column.callMethod(context, "primary=", runtime.getTrue());
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return runtime.newArray(columns);
|
|
1118
|
+
} finally {
|
|
1119
|
+
close(rs);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
public static IRubyObject unmarshal_id_result(Ruby runtime, ResultSet rs) throws SQLException {
|
|
1125
|
+
try {
|
|
1126
|
+
if (rs.next() && rs.getMetaData().getColumnCount() > 0) {
|
|
1127
|
+
return runtime.newFixnum(rs.getLong(1));
|
|
1128
|
+
}
|
|
1129
|
+
return runtime.getNil();
|
|
1130
|
+
} finally {
|
|
1131
|
+
close(rs);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
protected IRubyObject unmarshalResults(ThreadContext context, DatabaseMetaData metadata,
|
|
1136
|
+
Statement stmt, boolean downCase) throws SQLException {
|
|
1137
|
+
Ruby runtime = context.getRuntime();
|
|
1138
|
+
List<IRubyObject> sets = new ArrayList<IRubyObject>();
|
|
1139
|
+
|
|
1140
|
+
while (true) {
|
|
1141
|
+
sets.add(unmarshalResult(context, metadata, stmt.getResultSet(), downCase));
|
|
1142
|
+
if (!stmt.getMoreResults()) {
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (sets.size() > 1) {
|
|
1148
|
+
return runtime.newArray(sets);
|
|
1149
|
+
} else {
|
|
1150
|
+
return sets.get(0);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* Converts a jdbc resultset into an array (rows) of hashes (row) that AR expects.
|
|
1156
|
+
*
|
|
1157
|
+
* @param downCase should column names only be in lower case?
|
|
1158
|
+
*/
|
|
1159
|
+
protected IRubyObject unmarshalResult(ThreadContext context, DatabaseMetaData metadata,
|
|
1160
|
+
ResultSet resultSet, boolean downCase) throws SQLException {
|
|
1161
|
+
Ruby runtime = context.getRuntime();
|
|
1162
|
+
List results = new ArrayList();
|
|
1163
|
+
|
|
1164
|
+
try {
|
|
1165
|
+
ColumnData[] columns = ColumnData.setup(runtime, metadata, resultSet.getMetaData(), downCase);
|
|
1166
|
+
|
|
1167
|
+
populateFromResultSet(context, runtime, results, resultSet, columns);
|
|
1168
|
+
} finally {
|
|
1169
|
+
close(resultSet);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
return runtime.newArray(results);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
protected Object withConnectionAndRetry(ThreadContext context, SQLBlock block) {
|
|
1176
|
+
int tries = 1;
|
|
1177
|
+
int i = 0;
|
|
1178
|
+
Throwable toWrap = null;
|
|
1179
|
+
boolean autoCommit = false;
|
|
1180
|
+
while (i < tries) {
|
|
1181
|
+
Connection c = getConnection(true);
|
|
1182
|
+
try {
|
|
1183
|
+
autoCommit = c.getAutoCommit();
|
|
1184
|
+
return block.call(c);
|
|
1185
|
+
} catch (Exception e) {
|
|
1186
|
+
toWrap = e;
|
|
1187
|
+
while (toWrap.getCause() != null && toWrap.getCause() != toWrap) {
|
|
1188
|
+
toWrap = toWrap.getCause();
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
if (context.getRuntime().isDebug()) {
|
|
1192
|
+
toWrap.printStackTrace(System.out);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
i++;
|
|
1196
|
+
if (autoCommit) {
|
|
1197
|
+
if (i == 1) {
|
|
1198
|
+
tries = (int) rubyApi.convertToRubyInteger(config_value(context, "retry_count")).getLongValue();
|
|
1199
|
+
if (tries <= 0) {
|
|
1200
|
+
tries = 1;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (isConnectionBroken(context, c)) {
|
|
1204
|
+
reconnect();
|
|
1205
|
+
} else {
|
|
1206
|
+
throw wrap(context, toWrap);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
throw wrap(context, toWrap);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
protected RuntimeException wrap(ThreadContext context, Throwable exception) {
|
|
1215
|
+
Ruby runtime = context.getRuntime();
|
|
1216
|
+
RaiseException arError = new RaiseException(runtime, runtime.getModule("ActiveRecord").getClass("JDBCError"),
|
|
1217
|
+
exception.getMessage(), true);
|
|
1218
|
+
arError.initCause(exception);
|
|
1219
|
+
if (exception instanceof SQLException) {
|
|
1220
|
+
RuntimeHelpers.invoke(context, arError.getException(),
|
|
1221
|
+
"errno=", runtime.newFixnum(((SQLException) exception).getErrorCode()));
|
|
1222
|
+
RuntimeHelpers.invoke(context, arError.getException(),
|
|
1223
|
+
"sql_exception=", JavaEmbedUtils.javaToRuby(runtime, exception));
|
|
1224
|
+
}
|
|
1225
|
+
return (RuntimeException) arError;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
private IRubyObject wrappedConnection(Connection c) {
|
|
1229
|
+
return Java.java_to_ruby(this, JavaObject.wrap(getRuntime(), c), Block.NULL_BLOCK);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Some databases support schemas and others do not.
|
|
1234
|
+
* For ones which do this method should return true, aiding in decisions regarding schema vs database determination.
|
|
1235
|
+
*/
|
|
1236
|
+
protected boolean databaseSupportsSchemas() {
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private static int whitespace(int start, ByteList bl) {
|
|
1241
|
+
int end = bl.begin + bl.realSize;
|
|
1242
|
+
|
|
1243
|
+
for (int i = start; i < end; i++) {
|
|
1244
|
+
if (!Character.isWhitespace(bl.bytes[i])) return i;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return end;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
private static byte[] CALL = new byte[]{'c', 'a', 'l', 'l'};
|
|
1251
|
+
private static byte[] INSERT = new byte[] {'i', 'n', 's', 'e', 'r', 't'};
|
|
1252
|
+
private static byte[] SELECT = new byte[] {'s', 'e', 'l', 'e', 'c', 't'};
|
|
1253
|
+
private static byte[] WITH = new byte[] {'w', 'i', 't', 'h'};
|
|
1254
|
+
private static byte[] SHOW = new byte[] {'s', 'h', 'o', 'w'};
|
|
1255
|
+
|
|
1256
|
+
private static boolean startsWithNoCaseCmp(ByteList bytelist, byte[] compare) {
|
|
1257
|
+
int p = whitespace(bytelist.begin, bytelist);
|
|
1258
|
+
|
|
1259
|
+
// What the hell is this for?
|
|
1260
|
+
if (bytelist.bytes[p] == '(') p = whitespace(p, bytelist);
|
|
1261
|
+
|
|
1262
|
+
for (int i = 0; i < bytelist.realSize && i < compare.length; i++) {
|
|
1263
|
+
if (Character.toLowerCase(bytelist.bytes[p + i]) != compare[i]) return false;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
return true;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
private TableNameComponents extractTableNameComponents(Connection connection, String defaultSchema, String tableName) throws SQLException {
|
|
1270
|
+
String schemaName = null;
|
|
1271
|
+
|
|
1272
|
+
final String[] name_parts = tableName.split("\\.");
|
|
1273
|
+
if (name_parts.length > 3) {
|
|
1274
|
+
throw new SQLException("Table name '" + tableName + "' should not contain more than 2 '.'");
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
DatabaseMetaData metadata = connection.getMetaData();
|
|
1278
|
+
String clzName = metadata.getClass().getName().toLowerCase();
|
|
1279
|
+
boolean isPostgres = clzName.contains("postgresql");
|
|
1280
|
+
|
|
1281
|
+
String catalog = connection.getCatalog();
|
|
1282
|
+
if (name_parts.length == 2) {
|
|
1283
|
+
schemaName = name_parts[0];
|
|
1284
|
+
tableName = name_parts[1];
|
|
1285
|
+
} else if (name_parts.length == 3) {
|
|
1286
|
+
catalog = name_parts[0];
|
|
1287
|
+
schemaName = name_parts[1];
|
|
1288
|
+
tableName = name_parts[2];
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
if (schemaName == null && defaultSchema != null) schemaName = defaultSchema;
|
|
1292
|
+
|
|
1293
|
+
// The postgres JDBC driver will default to searching every schema if no
|
|
1294
|
+
// schema search path is given. Default to the public schema instead.
|
|
1295
|
+
if (schemaName == null && isPostgres) schemaName = "public";
|
|
1296
|
+
if (schemaName != null) schemaName = caseConvertIdentifierForJdbc(metadata, schemaName);
|
|
1297
|
+
tableName = caseConvertIdentifierForJdbc(metadata, tableName);
|
|
1298
|
+
|
|
1299
|
+
if (schemaName != null && !databaseSupportsSchemas()) { catalog = schemaName; }
|
|
1300
|
+
|
|
1301
|
+
return new TableNameComponents(catalog, schemaName, tableName);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
public static class ColumnData {
|
|
1305
|
+
public IRubyObject name;
|
|
1306
|
+
public int index;
|
|
1307
|
+
public int type;
|
|
1308
|
+
|
|
1309
|
+
public ColumnData(IRubyObject name, int type, int idx) {
|
|
1310
|
+
this.name = name;
|
|
1311
|
+
this.type = type;
|
|
1312
|
+
this.index = idx;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
public static ColumnData[] setup(Ruby runtime, DatabaseMetaData databaseMetadata,
|
|
1316
|
+
ResultSetMetaData metadata, boolean downCase) throws SQLException {
|
|
1317
|
+
int columnsCount = metadata.getColumnCount();
|
|
1318
|
+
ColumnData[] columns = new ColumnData[columnsCount];
|
|
1319
|
+
|
|
1320
|
+
for (int i = 1; i <= columnsCount; i++) { // metadata is one-based
|
|
1321
|
+
String name;
|
|
1322
|
+
if (downCase) {
|
|
1323
|
+
name = metadata.getColumnLabel(i).toLowerCase();
|
|
1324
|
+
} else {
|
|
1325
|
+
name = RubyJdbcConnection.caseConvertIdentifierForRails(databaseMetadata, metadata.getColumnLabel(i));
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
columns[i - 1] = new ColumnData(RubyString.newUnicodeString(runtime, name), metadata.getColumnType(i), i);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return columns;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
private static class TableNameComponents {
|
|
1336
|
+
private String catalog;
|
|
1337
|
+
private String schema;
|
|
1338
|
+
private String table;
|
|
1339
|
+
|
|
1340
|
+
private TableNameComponents(String catalog, String schema, String table) {
|
|
1341
|
+
this.catalog = catalog;
|
|
1342
|
+
this.schema = schema;
|
|
1343
|
+
this.table = table;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|