activerecord-jdbc-adapter 1.3.17 → 1.3.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +24 -5
  3. data/History.md +54 -0
  4. data/lib/arel/visitors/compat.rb +30 -2
  5. data/lib/arel/visitors/db2.rb +118 -29
  6. data/lib/arel/visitors/derby.rb +84 -29
  7. data/lib/arel/visitors/firebird.rb +66 -9
  8. data/lib/arel/visitors/h2.rb +16 -0
  9. data/lib/arel/visitors/hsqldb.rb +6 -3
  10. data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
  11. data/lib/arel/visitors/sql_server.rb +121 -40
  12. data/lib/arel/visitors/sql_server/ng42.rb +293 -0
  13. data/lib/arjdbc.rb +1 -7
  14. data/lib/arjdbc/db2.rb +1 -0
  15. data/lib/arjdbc/db2/adapter.rb +118 -18
  16. data/lib/arjdbc/derby/adapter.rb +29 -8
  17. data/lib/arjdbc/firebird.rb +1 -0
  18. data/lib/arjdbc/firebird/adapter.rb +126 -11
  19. data/lib/arjdbc/hsqldb/adapter.rb +3 -0
  20. data/lib/arjdbc/informix.rb +1 -0
  21. data/lib/arjdbc/jdbc.rb +17 -0
  22. data/lib/arjdbc/jdbc/adapter.rb +28 -3
  23. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  24. data/lib/arjdbc/jdbc/column.rb +7 -3
  25. data/lib/arjdbc/jdbc/type_cast.rb +2 -0
  26. data/lib/arjdbc/jdbc/type_converter.rb +28 -15
  27. data/lib/arjdbc/mimer.rb +1 -0
  28. data/lib/arjdbc/mssql.rb +2 -1
  29. data/lib/arjdbc/mssql/adapter.rb +105 -30
  30. data/lib/arjdbc/mssql/column.rb +30 -7
  31. data/lib/arjdbc/mssql/limit_helpers.rb +22 -9
  32. data/lib/arjdbc/mssql/types.rb +343 -0
  33. data/lib/arjdbc/mssql/utils.rb +25 -2
  34. data/lib/arjdbc/mysql/adapter.rb +22 -21
  35. data/lib/arjdbc/oracle.rb +1 -0
  36. data/lib/arjdbc/oracle/adapter.rb +291 -19
  37. data/lib/arjdbc/oracle/column.rb +9 -5
  38. data/lib/arjdbc/oracle/connection_methods.rb +4 -1
  39. data/lib/arjdbc/postgresql/_bc_time_cast_patch.rb +21 -0
  40. data/lib/arjdbc/postgresql/adapter.rb +7 -1
  41. data/lib/arjdbc/postgresql/oid/bytea.rb +3 -0
  42. data/lib/arjdbc/postgresql/oid_types.rb +2 -1
  43. data/lib/arjdbc/tasks/database_tasks.rb +3 -0
  44. data/lib/arjdbc/util/quoted_cache.rb +2 -2
  45. data/lib/arjdbc/util/serialized_attributes.rb +11 -0
  46. data/lib/arjdbc/version.rb +1 -1
  47. data/rakelib/02-test.rake +1 -1
  48. data/rakelib/db.rake +3 -1
  49. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +190 -0
  50. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +259 -61
  51. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +13 -2
  52. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +192 -15
  53. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +10 -2
  54. metadata +9 -4
@@ -11,13 +11,13 @@ module ArJdbc
11
11
 
12
12
  def self.included(base)
13
13
  # NOTE: assumes a standalone OracleColumn class
14
- class << base; include Cast; end
14
+ class << base; include Cast; end # unless AR42
15
15
  end
16
16
 
17
17
  def primary=(value)
18
18
  super
19
19
  @type = :integer if value && @sql_type =~ /^NUMBER$/i
20
- end if ::ActiveRecord::VERSION::STRING < '4.2'
20
+ end unless AR42
21
21
 
22
22
  def type_cast(value)
23
23
  return nil if value.nil?
@@ -40,15 +40,19 @@ module ArJdbc
40
40
  end
41
41
  end
42
42
 
43
+ def sql_type
44
+ (@sql_type || '').start_with?('XMLTYPE') ? 'XMLTYPE' : @sql_type
45
+ end
46
+
43
47
  private
44
48
 
45
49
  def extract_limit(sql_type)
46
50
  case sql_type
47
51
  when /^(clob|date)/i then nil
48
- when /^xml/i then @sql_type = 'XMLTYPE'; nil
52
+ when /^xml/i then nil
49
53
  else super
50
54
  end
51
- end
55
+ end unless AR42
52
56
 
53
57
  def simplified_type(field_type)
54
58
  case field_type
@@ -67,7 +71,7 @@ module ArJdbc
67
71
  else
68
72
  super
69
73
  end
70
- end
74
+ end unless AR42
71
75
 
72
76
  # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
73
77
  def default_value(value)
@@ -9,9 +9,12 @@ ArJdbc::ConnectionMethods.module_eval do
9
9
  return jndi_connection(config) if jndi_config?(config)
10
10
 
11
11
  config[:port] ||= 1521
12
- config[:url] ||= "jdbc:oracle:thin:@#{config[:host]}:#{config[:port]}:#{config[:database]}"
12
+ config[:url] ||= "jdbc:oracle:thin:@#{config[:host]}:#{config[:port]}:#{config[:database] || 'XE'}"
13
13
  config[:driver] ||= "oracle.jdbc.driver.OracleDriver"
14
14
  config[:connection_alive_sql] ||= 'SELECT 1 FROM DUAL'
15
+ unless config.key?(:statement_escape_processing)
16
+ config[:statement_escape_processing] = true
17
+ end
15
18
  jdbc_connection(config)
16
19
  end
17
20
  alias_method :jdbcoracle_connection, :oracle_connection
@@ -0,0 +1,21 @@
1
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::DateTime.class_eval do
2
+ def cast_value(value)
3
+ if value.is_a?(::String)
4
+ case value
5
+ when 'infinity' then ::Float::INFINITY
6
+ when '-infinity' then -::Float::INFINITY
7
+ #when / BC$/
8
+ # astronomical_year = format("%04d", value[/^\d+/].to_i)
9
+ # super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year))
10
+ else
11
+ if value.end_with?(' BC')
12
+ DateTime.parse("-#{value}"[0...-3])
13
+ else
14
+ super
15
+ end
16
+ end
17
+ else
18
+ value
19
+ end
20
+ end
21
+ end
@@ -44,7 +44,9 @@ module ArJdbc
44
44
  end
45
45
  end
46
46
 
47
+ # @see ActiveRecord::ConnectionAdapters::Jdbc::ArelSupport
47
48
  def self.arel_visitor_type(config = nil)
49
+ require 'arel/visitors/postgresql_jdbc'
48
50
  ::Arel::Visitors::PostgreSQL
49
51
  end
50
52
 
@@ -916,7 +918,7 @@ module ArJdbc
916
918
  def _quote(value)
917
919
  case value
918
920
  when Type::Binary::Data
919
- "'#{escape_bytea(value.to_s)}'"
921
+ "E'#{escape_bytea(value.to_s)}'"
920
922
  when OID::Xml::Data
921
923
  "xml '#{quote_string(value.to_s)}'"
922
924
  when OID::Bit::Data
@@ -1182,6 +1184,7 @@ module ArJdbc
1182
1184
  execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
1183
1185
  end
1184
1186
 
1187
+ # @override
1185
1188
  def supports_foreign_keys?; true end
1186
1189
 
1187
1190
  def foreign_keys(table_name)
@@ -1477,6 +1480,9 @@ module ActiveRecord::ConnectionAdapters
1477
1480
 
1478
1481
  require 'arjdbc/postgresql/oid_types' if ::ArJdbc::AR40
1479
1482
  include ::ArJdbc::PostgreSQL::OIDTypes if ::ArJdbc::PostgreSQL.const_defined?(:OIDTypes)
1483
+
1484
+ load 'arjdbc/postgresql/_bc_time_cast_patch.rb' if ::ArJdbc::AR42
1485
+
1480
1486
  include ::ArJdbc::PostgreSQL::ColumnHelpers if ::ArJdbc::AR42
1481
1487
 
1482
1488
  include ::ArJdbc::Util::QuotedCache
@@ -0,0 +1,3 @@
1
+ class ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
2
+ remove_method :type_cast_from_database
3
+ end
@@ -5,6 +5,7 @@ module ArJdbc
5
5
 
6
6
  if AR42
7
7
  require 'active_record/connection_adapters/postgresql/oid'
8
+ require 'arjdbc/postgresql/oid/bytea.rb'
8
9
  else
9
10
  require 'arjdbc/postgresql/base/oid'
10
11
  end
@@ -264,4 +265,4 @@ module ArJdbc
264
265
 
265
266
  end
266
267
  end
267
- end
268
+ end
@@ -7,6 +7,9 @@ module ArJdbc
7
7
  ActiveRecord::Tasks::DatabaseTasks.register_task(pattern, task)
8
8
  end
9
9
 
10
+ # support adapter: mariadb (as if it were mysql)
11
+ register_tasks(/mariadb/, ActiveRecord::Tasks::MySQLDatabaseTasks)
12
+
10
13
  else
11
14
 
12
15
  @@tasks = {}
@@ -28,7 +28,7 @@ module ArJdbc
28
28
  # Caches quoted table names, the cache is stored in the class'
29
29
  # `QUOTED_TABLE_NAMES` constant.
30
30
  # @return [String]
31
- def quote_table_name(name)
31
+ def quote_table_name(name, *args)
32
32
  if cache = self.class::QUOTED_TABLE_NAMES
33
33
  unless quoted = cache[name]
34
34
  quoted = super
@@ -43,7 +43,7 @@ module ArJdbc
43
43
  # Caches quoted table names, the cache is stored in the class'
44
44
  # `QUOTED_COLUMN_NAMES` constant.
45
45
  # @return [String]
46
- def quote_column_name(name)
46
+ def quote_column_name(name, *args)
47
47
  if cache = self.class::QUOTED_COLUMN_NAMES
48
48
  unless quoted = cache[name]
49
49
  quoted = super
@@ -29,6 +29,15 @@ module ArJdbc
29
29
  SerializedAttributes.dump_column_value(self, column)
30
30
  end
31
31
 
32
+ if defined? ActiveRecord::Type::Serialized # ArJdbc::AR42
33
+
34
+ def self.dump_column_value(record, column)
35
+ value = record[ column.name.to_s ]
36
+ column.cast_type.type_cast_for_database(value)
37
+ end
38
+
39
+ else
40
+
32
41
  def self.dump_column_value(record, column)
33
42
  value = record[ name = column.name.to_s ]
34
43
  if record.class.respond_to?(:serialized_attributes)
@@ -45,6 +54,8 @@ module ArJdbc
45
54
  value
46
55
  end
47
56
 
57
+ end
58
+
48
59
  def self.setup(lob_type = nil, after_save_alias = nil)
49
60
  ActiveRecord::Base.send :include, self # include SerializedAttributes
50
61
  ActiveRecord::Base.lob_type = lob_type unless lob_type.nil?
@@ -1,5 +1,5 @@
1
1
  module ArJdbc
2
- VERSION = "1.3.17"
2
+ VERSION = "1.3.18"
3
3
  # @deprecated
4
4
  module Version
5
5
  # @private 1.2.x compatibility
@@ -94,7 +94,7 @@ test_task_for :AS400, :desc => "Run tests against AS400 (DB2) (ensure driver is
94
94
  :files => FileList["test/db2*_test.rb"] + FileList["test/db/db2/*_test.rb"]
95
95
 
96
96
  test_task_for 'JDBC', :desc => 'Run tests against plain JDBC adapter (uses MySQL and Derby)',
97
- :files => FileList['test/*jdbc_*test.rb'] do |test_task|
97
+ :prereqs => 'db:mysql', :files => FileList['test/*jdbc_*test.rb'] do |test_task|
98
98
  test_task.libs << 'jdbc-mysql/lib' << 'jdbc-derby/lib'
99
99
  end
100
100
 
@@ -32,7 +32,9 @@ SQL
32
32
  DROP DATABASE IF EXISTS #{POSTGRES_CONFIG[:database]};
33
33
  DROP USER IF EXISTS #{POSTGRES_CONFIG[:username]};
34
34
  CREATE USER #{POSTGRES_CONFIG[:username]} CREATEDB SUPERUSER LOGIN PASSWORD '#{POSTGRES_CONFIG[:password]}';
35
- CREATE DATABASE #{POSTGRES_CONFIG[:database]} OWNER #{POSTGRES_CONFIG[:username]};
35
+ CREATE DATABASE #{POSTGRES_CONFIG[:database]} OWNER #{POSTGRES_CONFIG[:username]}
36
+ TEMPLATE template0
37
+ ENCODING '#{POSTGRES_CONFIG[:encoding]}' LC_COLLATE '#{POSTGRES_CONFIG[:collate]}' LC_CTYPE '#{POSTGRES_CONFIG[:collate]}';
36
38
  SQL
37
39
  params = { '-U' => ENV['PSQL_USER'] || 'postgres' }
38
40
  params['-q'] = nil unless $VERBOSE
@@ -0,0 +1,190 @@
1
+ /*
2
+ * The MIT License
3
+ *
4
+ * Copyright 2015 Karol Bucek.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ * THE SOFTWARE.
23
+ */
24
+ package arjdbc.firebird;
25
+
26
+ import arjdbc.jdbc.RubyJdbcConnection;
27
+
28
+ import java.sql.Connection;
29
+ import java.sql.ResultSet;
30
+ import java.sql.SQLException;
31
+ import java.sql.PreparedStatement;
32
+ import java.sql.ResultSetMetaData;
33
+ import java.sql.Types;
34
+
35
+ import org.jruby.Ruby;
36
+ import org.jruby.RubyClass;
37
+ import org.jruby.RubyString;
38
+ import org.jruby.runtime.ObjectAllocator;
39
+ import org.jruby.runtime.ThreadContext;
40
+ import org.jruby.runtime.builtin.IRubyObject;
41
+
42
+ /**
43
+ * @author kares
44
+ */
45
+ public class FirebirdRubyJdbcConnection extends RubyJdbcConnection {
46
+
47
+ protected FirebirdRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
48
+ super(runtime, metaClass);
49
+ }
50
+
51
+ public static RubyClass createFirebirdJdbcConnectionClass(Ruby runtime, RubyClass jdbcConnection) {
52
+ final RubyClass clazz = RubyJdbcConnection.getConnectionAdapters(runtime).
53
+ defineClassUnder("FirebirdJdbcConnection", jdbcConnection, ALLOCATOR);
54
+ clazz.defineAnnotatedMethods(FirebirdRubyJdbcConnection.class);
55
+ return clazz;
56
+ }
57
+
58
+ private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
59
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
60
+ return new FirebirdRubyJdbcConnection(runtime, klass);
61
+ }
62
+ };
63
+
64
+ @Override // resultSet.wasNull() might be falsy for '' treated as null
65
+ protected IRubyObject stringToRuby(final ThreadContext context,
66
+ final Ruby runtime, final ResultSet resultSet, final int column)
67
+ throws SQLException {
68
+ final String value = resultSet.getString(column);
69
+ if ( value == null ) return runtime.getNil();
70
+ return RubyString.newUnicodeString(runtime, value);
71
+ }
72
+
73
+ @Override // booleans are emulated can not setNull(index, Types.BOOLEAN)
74
+ protected void setBooleanParameter(final ThreadContext context,
75
+ final Connection connection, final PreparedStatement statement,
76
+ final int index, final Object value,
77
+ final IRubyObject column, final int type) throws SQLException {
78
+ if ( value instanceof IRubyObject ) {
79
+ setBooleanParameter(context, connection, statement, index, (IRubyObject) value, column, type);
80
+ }
81
+ else {
82
+ if ( value == null ) statement.setNull(index, Types.CHAR);
83
+ else {
84
+ statement.setBoolean(index, ((Boolean) value).booleanValue());
85
+ }
86
+ }
87
+ }
88
+
89
+ @Override // booleans are emulated can not setNull(index, Types.BOOLEAN)
90
+ protected void setBooleanParameter(final ThreadContext context,
91
+ final Connection connection, final PreparedStatement statement,
92
+ final int index, final IRubyObject value,
93
+ final IRubyObject column, final int type) throws SQLException {
94
+ if ( value.isNil() ) statement.setNull(index, Types.CHAR);
95
+ else {
96
+ statement.setBoolean(index, value.isTrue());
97
+ }
98
+ }
99
+
100
+ protected IRubyObject jdbcToRuby(
101
+ final ThreadContext context, final Ruby runtime,
102
+ final int column, final int type, final ResultSet resultSet)
103
+ throws SQLException {
104
+
105
+ switch (type) {
106
+ case SMALL_CHAR_1:
107
+ return smallChar1ToRuby(runtime, resultSet, column);
108
+ case SMALL_CHAR_2:
109
+ return smallChar2ToRuby(runtime, resultSet, column);
110
+ }
111
+ return super.jdbcToRuby(context, runtime, column, type, resultSet);
112
+ }
113
+
114
+ private static IRubyObject smallChar1ToRuby(
115
+ final Ruby runtime, final ResultSet resultSet, final int column)
116
+ throws SQLException {
117
+ String value = resultSet.getString(column);
118
+ if ( value == null ) return runtime.getNil();
119
+ if ( value.length() > 1 && value.charAt(1) == ' ' ) {
120
+ value = value.substring(0, 1);
121
+ }
122
+ return RubyString.newUnicodeString(runtime, value);
123
+ }
124
+
125
+ private static IRubyObject smallChar2ToRuby(
126
+ final Ruby runtime, final ResultSet resultSet, final int column)
127
+ throws SQLException {
128
+ String value = resultSet.getString(column);
129
+ if ( value == null ) return runtime.getNil();
130
+ if ( value.length() > 2 && value.charAt(2) == ' ' ) {
131
+ value = value.substring(0, 2);
132
+ }
133
+ return RubyString.newUnicodeString(runtime, value);
134
+ }
135
+
136
+ private final static int SMALL_CHAR_1 = 31431001;
137
+ private final static int SMALL_CHAR_2 = 31431002;
138
+
139
+ @Override
140
+ protected ColumnData[] extractColumns(final Ruby runtime,
141
+ final Connection connection, final ResultSet resultSet,
142
+ final boolean downCase) throws SQLException {
143
+
144
+ final ResultSetMetaData resultMetaData = resultSet.getMetaData();
145
+
146
+ final int columnCount = resultMetaData.getColumnCount();
147
+ final ColumnData[] columns = new ColumnData[columnCount];
148
+
149
+ for ( int i = 1; i <= columnCount; i++ ) { // metadata is one-based
150
+ String name = resultMetaData.getColumnLabel(i);
151
+ if ( downCase ) {
152
+ name = name.toLowerCase();
153
+ } else {
154
+ name = caseConvertIdentifierForRails(connection, name);
155
+ }
156
+ final RubyString columnName = RubyString.newUnicodeString(runtime, name);
157
+
158
+ int columnType = resultMetaData.getColumnType(i);
159
+ if (columnType == Types.CHAR) {
160
+ // CHAR(1) 'aligned' by JayBird to "1 "
161
+ final int prec = resultMetaData.getPrecision(i);
162
+ if ( prec == 1 ) {
163
+ columnType = SMALL_CHAR_1;
164
+ }
165
+ else if ( prec == 2 ) {
166
+ columnType = SMALL_CHAR_2;
167
+ }
168
+ }
169
+
170
+ columns[i - 1] = new ColumnData(columnName, columnType, i);
171
+ }
172
+
173
+ return columns;
174
+ }
175
+
176
+ // storesMixedCaseIdentifiers() return false;
177
+ // storesLowerCaseIdentifiers() return false;
178
+ // storesUpperCaseIdentifiers() return true;
179
+
180
+ @Override
181
+ protected String caseConvertIdentifierForRails(final Connection connection, final String value) {
182
+ return value == null ? null : value.toLowerCase();
183
+ }
184
+
185
+ @Override
186
+ protected String caseConvertIdentifierForJdbc(final Connection connection, final String value) {
187
+ return value == null ? null : value.toUpperCase();
188
+ }
189
+
190
+ }
@@ -54,6 +54,7 @@ import java.sql.Types;
54
54
  import java.util.ArrayList;
55
55
  import java.util.Calendar;
56
56
  import java.util.Collection;
57
+ import java.util.HashMap;
57
58
  import java.util.LinkedHashMap;
58
59
  import java.util.List;
59
60
  import java.util.Map;
@@ -152,6 +153,15 @@ public class RubyJdbcConnection extends RubyObject {
152
153
  return getConnectionAdapters(runtime).getClass("IndexDefinition");
153
154
  }
154
155
 
156
+ /**
157
+ * @param runtime
158
+ * @return <code>ActiveRecord::ConnectionAdapters::ForeignKeyDefinition</code>
159
+ * @note only since AR 4.2
160
+ */
161
+ protected static RubyClass getForeignKeyDefinition(final Ruby runtime) {
162
+ return getConnectionAdapters(runtime).getClass("ForeignKeyDefinition");
163
+ }
164
+
155
165
  /**
156
166
  * @param runtime
157
167
  * @return <code>ActiveRecord::JDBCError</code>
@@ -1168,7 +1178,7 @@ public class RubyJdbcConnection extends RubyObject {
1168
1178
 
1169
1179
  final DatabaseMetaData metaData = connection.getMetaData();
1170
1180
  columns = metaData.getColumns(components.catalog, components.schema, components.name, null);
1171
- return unmarshalColumns(context, metaData, components, columns);
1181
+ return mapColumnsResult(context, metaData, components, columns);
1172
1182
  }
1173
1183
  finally {
1174
1184
  close(columns);
@@ -1205,7 +1215,7 @@ public class RubyJdbcConnection extends RubyObject {
1205
1215
  return withConnection(context, new Callable<IRubyObject>() {
1206
1216
  public IRubyObject call(final Connection connection) throws SQLException {
1207
1217
  final Ruby runtime = context.getRuntime();
1208
- final RubyClass indexDefinition = getIndexDefinition(runtime);
1218
+ final RubyClass IndexDefinition = getIndexDefinition(context);
1209
1219
 
1210
1220
  String _tableName = caseConvertIdentifierForJdbc(connection, tableName);
1211
1221
  String _schemaName = caseConvertIdentifierForJdbc(connection, schemaName);
@@ -1248,7 +1258,7 @@ public class RubyJdbcConnection extends RubyObject {
1248
1258
  // orders, (since AR 3.2) where, type, using (AR 4.0)
1249
1259
  };
1250
1260
 
1251
- indexes.add( indexDefinition.callMethod(context, "new", args) ); // IndexDefinition.new
1261
+ indexes.add( IndexDefinition.callMethod(context, "new", args) ); // IndexDefinition.new
1252
1262
  }
1253
1263
 
1254
1264
  // One or more columns can be associated with an index
@@ -1265,6 +1275,103 @@ public class RubyJdbcConnection extends RubyObject {
1265
1275
  });
1266
1276
  }
1267
1277
 
1278
+ protected RubyClass getIndexDefinition(final ThreadContext context) {
1279
+ final RubyClass adapterClass = getAdapter(context).getMetaClass();
1280
+ IRubyObject IDef = adapterClass.getConstantAt("IndexDefinition");
1281
+ return IDef != null ? (RubyClass) IDef : getIndexDefinition(context.runtime);
1282
+ }
1283
+
1284
+ @JRubyMethod
1285
+ public IRubyObject foreign_keys(final ThreadContext context, IRubyObject table_name) {
1286
+ return foreignKeys(context, table_name.toString(), null, null);
1287
+ }
1288
+
1289
+ protected IRubyObject foreignKeys(final ThreadContext context, final String tableName, final String schemaName, final String catalog) {
1290
+ return withConnection(context, new Callable<IRubyObject>() {
1291
+ public IRubyObject call(final Connection connection) throws SQLException {
1292
+ final Ruby runtime = context.getRuntime();
1293
+ final RubyClass FKDefinition = getForeignKeyDefinition(context);
1294
+
1295
+ String _tableName = caseConvertIdentifierForJdbc(connection, tableName);
1296
+ String _schemaName = caseConvertIdentifierForJdbc(connection, schemaName);
1297
+ final TableName table = extractTableName(connection, catalog, _schemaName, _tableName);
1298
+
1299
+ ResultSet fkInfoSet = null;
1300
+ final List<IRubyObject> fKeys = new ArrayList<IRubyObject>(8);
1301
+ try {
1302
+ final DatabaseMetaData metaData = connection.getMetaData();
1303
+ fkInfoSet = metaData.getImportedKeys(table.catalog, table.schema, table.name);
1304
+
1305
+ while ( fkInfoSet.next() ) {
1306
+ final RubyHash options = RubyHash.newHash(runtime);
1307
+
1308
+ String fkName = fkInfoSet.getString("FK_NAME");
1309
+ if (fkName != null) {
1310
+ fkName = caseConvertIdentifierForRails(metaData, fkName);
1311
+ options.put(runtime.newSymbol("name"), fkName);
1312
+ }
1313
+
1314
+ String columnName = fkInfoSet.getString("FKCOLUMN_NAME");
1315
+ options.put(runtime.newSymbol("column"), caseConvertIdentifierForRails(metaData, columnName));
1316
+
1317
+ columnName = fkInfoSet.getString("PKCOLUMN_NAME");
1318
+ options.put(runtime.newSymbol("primary_key"), caseConvertIdentifierForRails(metaData, columnName));
1319
+
1320
+ String fkTableName = fkInfoSet.getString("FKTABLE_NAME");
1321
+ fkTableName = caseConvertIdentifierForRails(metaData, fkTableName);
1322
+
1323
+ String pkTableName = fkInfoSet.getString("PKTABLE_NAME");
1324
+ pkTableName = caseConvertIdentifierForRails(metaData, pkTableName);
1325
+
1326
+ final String onDelete = extractForeignKeyRule( fkInfoSet.getInt("DELETE_RULE") );
1327
+ if ( onDelete != null ) options.op_aset(context, runtime.newSymbol("on_delete"), runtime.newSymbol(onDelete));
1328
+
1329
+ final String onUpdate = extractForeignKeyRule( fkInfoSet.getInt("UPDATE_RULE") );
1330
+ if ( onUpdate != null ) options.op_aset(context, runtime.newSymbol("on_update"), runtime.newSymbol(onUpdate));
1331
+
1332
+ IRubyObject[] args = new IRubyObject[] {
1333
+ RubyString.newUnicodeString(runtime, fkTableName), // from_table
1334
+ RubyString.newUnicodeString(runtime, pkTableName), // to_table
1335
+ options
1336
+ };
1337
+
1338
+ fKeys.add( FKDefinition.callMethod(context, "new", args) ); // ForeignKeyDefinition.new
1339
+ }
1340
+
1341
+ return runtime.newArray(fKeys);
1342
+
1343
+ } finally { close(fkInfoSet); }
1344
+ }
1345
+ });
1346
+ }
1347
+
1348
+ protected String extractForeignKeyRule(final int rule) {
1349
+ switch (rule) {
1350
+ case DatabaseMetaData.importedKeyNoAction : return null ;
1351
+ case DatabaseMetaData.importedKeyCascade : return "cascade" ;
1352
+ case DatabaseMetaData.importedKeySetNull : return "nullify" ;
1353
+ case DatabaseMetaData.importedKeySetDefault: return "default" ;
1354
+ }
1355
+ return null;
1356
+ }
1357
+
1358
+ protected RubyClass getForeignKeyDefinition(final ThreadContext context) {
1359
+ final RubyClass adapterClass = getAdapter(context).getMetaClass();
1360
+ IRubyObject FKDef = adapterClass.getConstantAt("ForeignKeyDefinition");
1361
+ return FKDef != null ? (RubyClass) FKDef : getForeignKeyDefinition(context.runtime);
1362
+ }
1363
+
1364
+
1365
+ @JRubyMethod(name = "supports_foreign_keys?")
1366
+ public IRubyObject supports_foreign_keys_p(final ThreadContext context) throws SQLException {
1367
+ return withConnection(context, new Callable<IRubyObject>() {
1368
+ public IRubyObject call(final Connection connection) throws SQLException {
1369
+ final DatabaseMetaData metaData = connection.getMetaData();
1370
+ return context.getRuntime().newBoolean( metaData.supportsIntegrityEnhancementFacility() );
1371
+ }
1372
+ });
1373
+ }
1374
+
1268
1375
  @JRubyMethod(name = "supports_views?")
1269
1376
  public IRubyObject supports_views_p(final ThreadContext context) throws SQLException {
1270
1377
  return withConnection(context, new Callable<IRubyObject>() {
@@ -1785,6 +1892,16 @@ public class RubyJdbcConnection extends RubyObject {
1785
1892
  return value;
1786
1893
  }
1787
1894
 
1895
+ /**
1896
+ * @return AR::Type-casted value
1897
+ * @since 1.3.18
1898
+ */
1899
+ protected static IRubyObject typeCastFromDatabase(final ThreadContext context,
1900
+ final IRubyObject adapter, final RubySymbol typeName, final RubyString value) {
1901
+ final IRubyObject type = adapter.callMethod(context, "lookup_cast_type", typeName);
1902
+ return type.callMethod(context, "type_cast_from_database", value);
1903
+ }
1904
+
1788
1905
  protected IRubyObject dateToRuby(final ThreadContext context,
1789
1906
  final Ruby runtime, final ResultSet resultSet, final int column)
1790
1907
  throws SQLException {
@@ -1800,6 +1917,11 @@ public class RubyJdbcConnection extends RubyObject {
1800
1917
 
1801
1918
  final IRubyObject adapter = callMethod(context, "adapter"); // self.adapter
1802
1919
  if ( adapter.isNil() ) return strValue; // NOTE: we warn on init_connection
1920
+
1921
+ if ( usesType(runtime) ) {
1922
+ // NOTE: this CAN NOT be 100% correct - as :date is just a type guess!
1923
+ return typeCastFromDatabase(context, adapter, runtime.newSymbol("date"), strValue);
1924
+ }
1803
1925
  return adapter.callMethod(context, "_string_to_date", strValue);
1804
1926
  }
1805
1927
 
@@ -1818,6 +1940,11 @@ public class RubyJdbcConnection extends RubyObject {
1818
1940
 
1819
1941
  final IRubyObject adapter = callMethod(context, "adapter"); // self.adapter
1820
1942
  if ( adapter.isNil() ) return strValue; // NOTE: we warn on init_connection
1943
+
1944
+ if ( usesType(runtime) ) {
1945
+ // NOTE: this CAN NOT be 100% correct - as :time is just a type guess!
1946
+ return typeCastFromDatabase(context, adapter, runtime.newSymbol("time"), strValue);
1947
+ }
1821
1948
  return adapter.callMethod(context, "_string_to_time", strValue);
1822
1949
  }
1823
1950
 
@@ -1836,6 +1963,11 @@ public class RubyJdbcConnection extends RubyObject {
1836
1963
 
1837
1964
  final IRubyObject adapter = callMethod(context, "adapter"); // self.adapter
1838
1965
  if ( adapter.isNil() ) return strValue; // NOTE: we warn on init_connection
1966
+
1967
+ if ( usesType(runtime) ) {
1968
+ // NOTE: this CAN NOT be 100% correct - as :timestamp is just a type guess!
1969
+ return typeCastFromDatabase(context, adapter, runtime.newSymbol("timestamp"), strValue);
1970
+ }
1839
1971
  return adapter.callMethod(context, "_string_to_timestamp", strValue);
1840
1972
  }
1841
1973
 
@@ -2032,9 +2164,17 @@ public class RubyJdbcConnection extends RubyObject {
2032
2164
  }
2033
2165
 
2034
2166
  protected void setStatementParameter(final ThreadContext context,
2035
- final Ruby runtime, final Connection connection,
2036
- final PreparedStatement statement, final int index,
2037
- final Object value, final IRubyObject column) throws SQLException {
2167
+ final Ruby runtime, final Connection connection,
2168
+ final PreparedStatement statement, final int index,
2169
+ final Object rawValue, final IRubyObject column) throws SQLException {
2170
+ final Object value;
2171
+
2172
+ if ( isAr42(column) ) {
2173
+ final IRubyObject castType = column.callMethod(context, "cast_type");
2174
+ value = castType.callMethod(context, "type_cast_for_database", (IRubyObject) rawValue);
2175
+ } else {
2176
+ value = rawValue;
2177
+ }
2038
2178
 
2039
2179
  final int type = jdbcTypeFor(context, runtime, column, value);
2040
2180
 
@@ -2078,7 +2218,7 @@ public class RubyJdbcConnection extends RubyObject {
2078
2218
  setXmlParameter(context, connection, statement, index, value, column, type);
2079
2219
  break;
2080
2220
  case Types.ARRAY:
2081
- setArrayParameter(context, connection, statement, index, value, column, type);
2221
+ setArrayParameter(context, connection, statement, index, rawValue, column, type);
2082
2222
  break;
2083
2223
  case Types.JAVA_OBJECT:
2084
2224
  case Types.OTHER:
@@ -2135,7 +2275,49 @@ public class RubyJdbcConnection extends RubyObject {
2135
2275
  if ( column == null || column.isNil() ) {
2136
2276
  throw runtime.newArgumentError("nil column passed");
2137
2277
  }
2138
- return (RubySymbol) column.callMethod(context, "type");
2278
+
2279
+ final IRubyObject type = column.callMethod(context, "type");
2280
+ if ( type.isNil() || ! (type instanceof RubySymbol) ) {
2281
+ throw new IllegalStateException("unexpected type = " + type.inspect() + " for " + column.inspect());
2282
+ }
2283
+ return (RubySymbol) type;
2284
+ }
2285
+
2286
+ protected static final Map<String, Integer> JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
2287
+ static {
2288
+ JDBC_TYPE_FOR.put("string", Types.VARCHAR);
2289
+ JDBC_TYPE_FOR.put("text", Types.CLOB);
2290
+ JDBC_TYPE_FOR.put("integer", Types.INTEGER);
2291
+ JDBC_TYPE_FOR.put("float", Types.FLOAT);
2292
+ JDBC_TYPE_FOR.put("real", Types.REAL);
2293
+ JDBC_TYPE_FOR.put("decimal", Types.DECIMAL);
2294
+ JDBC_TYPE_FOR.put("date", Types.DATE);
2295
+ JDBC_TYPE_FOR.put("time", Types.TIME);
2296
+ JDBC_TYPE_FOR.put("datetime", Types.TIMESTAMP);
2297
+ JDBC_TYPE_FOR.put("timestamp", Types.TIMESTAMP);
2298
+ JDBC_TYPE_FOR.put("binary", Types.BLOB);
2299
+ JDBC_TYPE_FOR.put("boolean", Types.BOOLEAN);
2300
+ JDBC_TYPE_FOR.put("array", Types.ARRAY);
2301
+ JDBC_TYPE_FOR.put("xml", Types.SQLXML);
2302
+
2303
+ // also mapping standard SQL names :
2304
+ JDBC_TYPE_FOR.put("bit", Types.BIT);
2305
+ JDBC_TYPE_FOR.put("tinyint", Types.TINYINT);
2306
+ JDBC_TYPE_FOR.put("smallint", Types.SMALLINT);
2307
+ JDBC_TYPE_FOR.put("bigint", Types.BIGINT);
2308
+ JDBC_TYPE_FOR.put("int", Types.INTEGER);
2309
+ JDBC_TYPE_FOR.put("double", Types.DOUBLE);
2310
+ JDBC_TYPE_FOR.put("numeric", Types.NUMERIC);
2311
+ JDBC_TYPE_FOR.put("char", Types.CHAR);
2312
+ JDBC_TYPE_FOR.put("varchar", Types.VARCHAR);
2313
+ JDBC_TYPE_FOR.put("binary", Types.BINARY);
2314
+ JDBC_TYPE_FOR.put("varbinary", Types.VARBINARY);
2315
+ //JDBC_TYPE_FOR.put("struct", Types.STRUCT);
2316
+ JDBC_TYPE_FOR.put("blob", Types.BLOB);
2317
+ JDBC_TYPE_FOR.put("clob", Types.CLOB);
2318
+ JDBC_TYPE_FOR.put("nchar", Types.NCHAR);
2319
+ JDBC_TYPE_FOR.put("nvarchar", Types.NVARCHAR);
2320
+ JDBC_TYPE_FOR.put("nclob", Types.NCLOB);
2139
2321
  }
2140
2322
 
2141
2323
  protected int jdbcTypeFor(final ThreadContext context, final Ruby runtime,
@@ -2150,39 +2332,20 @@ public class RubyJdbcConnection extends RubyObject {
2150
2332
  internedType = "array";
2151
2333
  }
2152
2334
  else {
2153
- final RubySymbol columnType = resolveColumnType(context, runtime, column);
2154
- internedType = columnType.asJavaString();
2335
+ internedType = resolveColumnType(context, runtime, column).asJavaString();
2155
2336
  }
2156
2337
  }
2157
2338
  else {
2158
- if ( value instanceof RubyInteger ) {
2159
- internedType = "integer";
2160
- }
2161
- else if ( value instanceof RubyNumeric ) {
2162
- internedType = "float";
2163
- }
2164
- else if ( value instanceof RubyTime ) {
2165
- internedType = "timestamp";
2166
- }
2167
- else {
2168
- internedType = "string";
2169
- }
2339
+ if ( value instanceof RubyInteger ) internedType = "integer";
2340
+ else if ( value instanceof RubyNumeric ) internedType = "float";
2341
+ else if ( value instanceof RubyTime ) internedType = "timestamp";
2342
+ else internedType = "string";
2170
2343
  }
2171
2344
 
2172
- if ( internedType == (Object) "string" ) return Types.VARCHAR;
2173
- else if ( internedType == (Object) "text" ) return Types.CLOB;
2174
- else if ( internedType == (Object) "integer" ) return Types.INTEGER;
2175
- else if ( internedType == (Object) "decimal" ) return Types.DECIMAL;
2176
- else if ( internedType == (Object) "float" ) return Types.FLOAT;
2177
- else if ( internedType == (Object) "date" ) return Types.DATE;
2178
- else if ( internedType == (Object) "time" ) return Types.TIME;
2179
- else if ( internedType == (Object) "datetime" ) return Types.TIMESTAMP;
2180
- else if ( internedType == (Object) "timestamp" ) return Types.TIMESTAMP;
2181
- else if ( internedType == (Object) "binary" ) return Types.BLOB;
2182
- else if ( internedType == (Object) "boolean" ) return Types.BOOLEAN;
2183
- else if ( internedType == (Object) "xml" ) return Types.SQLXML;
2184
- else if ( internedType == (Object) "array" ) return Types.ARRAY;
2185
- else return Types.OTHER; // -1 as well as 0 are used in Types
2345
+ final Integer sqlType = JDBC_TYPE_FOR.get(internedType);
2346
+ if ( sqlType != null ) return sqlType.intValue();
2347
+
2348
+ return Types.OTHER; // -1 as well as 0 are used in Types
2186
2349
  }
2187
2350
 
2188
2351
  protected void setIntegerParameter(final ThreadContext context,
@@ -2338,6 +2501,9 @@ public class RubyJdbcConnection extends RubyObject {
2338
2501
  if ( value.getMetaClass().getName().indexOf("BigDecimal") != -1 ) {
2339
2502
  statement.setBigDecimal(index, getBigDecimalValue(value));
2340
2503
  }
2504
+ else if ( value instanceof RubyInteger ) {
2505
+ statement.setBigDecimal(index, new BigDecimal(((RubyInteger) value).getBigIntegerValue()));
2506
+ }
2341
2507
  else if ( value instanceof RubyNumeric ) {
2342
2508
  statement.setDouble(index, ((RubyNumeric) value).getDoubleValue());
2343
2509
  }
@@ -2617,15 +2783,15 @@ public class RubyJdbcConnection extends RubyObject {
2617
2783
  }
2618
2784
 
2619
2785
  protected void setArrayParameter(final ThreadContext context,
2620
- final Connection connection, final PreparedStatement statement,
2621
- final int index, final Object value,
2622
- final IRubyObject column, final int type) throws SQLException {
2786
+ final Connection connection, final PreparedStatement statement,
2787
+ final int index, final Object value,
2788
+ final IRubyObject column, final int type) throws SQLException {
2623
2789
  if ( value instanceof IRubyObject ) {
2624
2790
  setArrayParameter(context, connection, statement, index, (IRubyObject) value, column, type);
2625
- }
2626
- else {
2627
- if ( value == null ) statement.setNull(index, Types.ARRAY);
2628
- else {
2791
+ } else {
2792
+ if ( value == null ) {
2793
+ statement.setNull(index, Types.ARRAY);
2794
+ } else {
2629
2795
  String typeName = resolveArrayBaseTypeName(context, value, column, type);
2630
2796
  Array array = connection.createArrayOf(typeName, (Object[]) value);
2631
2797
  statement.setArray(index, array);
@@ -2637,8 +2803,9 @@ public class RubyJdbcConnection extends RubyObject {
2637
2803
  final Connection connection, final PreparedStatement statement,
2638
2804
  final int index, final IRubyObject value,
2639
2805
  final IRubyObject column, final int type) throws SQLException {
2640
- if ( value.isNil() ) statement.setNull(index, Types.ARRAY);
2641
- else {
2806
+ if ( value.isNil() ) {
2807
+ statement.setNull(index, Types.ARRAY);
2808
+ } else {
2642
2809
  String typeName = resolveArrayBaseTypeName(context, value, column, type);
2643
2810
  Array array = connection.createArrayOf(typeName, ((RubyArray) value).toArray());
2644
2811
  statement.setArray(index, array);
@@ -2963,15 +3130,41 @@ public class RubyJdbcConnection extends RubyObject {
2963
3130
  return defaultValue == null ? runtime.getNil() : RubyString.newUnicodeString(runtime, defaultValue);
2964
3131
  }
2965
3132
 
2966
- private RubyArray unmarshalColumns(final ThreadContext context,
3133
+ /**
3134
+ * Internal API that might be subject to change!
3135
+ * @since 1.3.18
3136
+ */
3137
+ protected static boolean usesType(final Ruby runtime) { // AR 4.2
3138
+ return runtime.getModule("ActiveRecord").getConstantAt("Type") != null;
3139
+ }
3140
+
3141
+ /**
3142
+ * This method is considered internal and is not part of AR-JDBC's Java ext
3143
+ * API and thus might be subject to change in the future.
3144
+ * Please copy it to your own class if you rely on it to avoid issues.
3145
+ */
3146
+ protected static boolean isAr42(IRubyObject column) {
3147
+ return column.respondsTo("cast_type");
3148
+ }
3149
+
3150
+ protected RubyArray mapColumnsResult(final ThreadContext context,
2967
3151
  final DatabaseMetaData metaData, final TableName components, final ResultSet results)
2968
3152
  throws SQLException {
2969
3153
 
2970
- final Ruby runtime = context.getRuntime();
2971
- final RubyClass JdbcColumn = getJdbcColumnClass(context);
2972
-
3154
+ final RubyClass Column = getJdbcColumnClass(context);
3155
+ final boolean lookupCastType = Column.isMethodBound("cast_type", false);
2973
3156
  // NOTE: primary/primary= methods were removed from Column in AR 4.2
2974
- final boolean setPrimary = JdbcColumn.isMethodBound("primary=", false);
3157
+ // setPrimary = ! lookupCastType by default ... it's better than checking
3158
+ // whether primary= is bound since it might be a left over in AR-JDBC ext
3159
+ return mapColumnsResult(context, metaData, components, results, Column, lookupCastType, ! lookupCastType);
3160
+ }
3161
+
3162
+ protected final RubyArray mapColumnsResult(final ThreadContext context,
3163
+ final DatabaseMetaData metaData, final TableName components, final ResultSet results,
3164
+ final RubyClass Column, final boolean lookupCastType, final boolean setPrimary)
3165
+ throws SQLException {
3166
+
3167
+ final Ruby runtime = context.getRuntime();
2975
3168
 
2976
3169
  final Collection<String> primaryKeyNames =
2977
3170
  setPrimary ? getPrimaryKeyNames(metaData, components) : null;
@@ -2980,22 +3173,27 @@ public class RubyJdbcConnection extends RubyObject {
2980
3173
  final IRubyObject config = getConfig(context);
2981
3174
  while ( results.next() ) {
2982
3175
  final String colName = results.getString(COLUMN_NAME);
2983
- IRubyObject column = JdbcColumn.callMethod(context, "new",
2984
- new IRubyObject[] {
2985
- config,
2986
- RubyString.newUnicodeString( runtime, caseConvertIdentifierForRails(metaData, colName) ),
2987
- defaultValueFromResultSet( runtime, results ),
2988
- RubyString.newUnicodeString( runtime, typeFromResultSet(results) ),
2989
- runtime.newBoolean( ! results.getString(IS_NULLABLE).trim().equals("NO") )
2990
- });
3176
+ final RubyString railsColumnName = RubyString.newUnicodeString(runtime, caseConvertIdentifierForRails(metaData, colName));
3177
+ final IRubyObject defaultValue = defaultValueFromResultSet( runtime, results );
3178
+ final RubyString sqlType = RubyString.newUnicodeString( runtime, typeFromResultSet(results) );
3179
+ final RubyBoolean nullable = runtime.newBoolean( ! results.getString(IS_NULLABLE).trim().equals("NO") );
3180
+ final IRubyObject[] args;
3181
+ if ( lookupCastType ) {
3182
+ final IRubyObject castType = getAdapter(context).callMethod(context, "lookup_cast_type", sqlType);
3183
+ args = new IRubyObject[] {config, railsColumnName, defaultValue, castType, sqlType, nullable};
3184
+ } else {
3185
+ args = new IRubyObject[] {config, railsColumnName, defaultValue, sqlType, nullable};
3186
+ }
3187
+
3188
+ IRubyObject column = Column.callMethod(context, "new", args);
2991
3189
  columns.append(column);
2992
3190
 
2993
- if ( primaryKeyNames != null && primaryKeyNames.contains(colName) ) {
2994
- column.callMethod(context, "primary=", runtime.getTrue());
3191
+ if ( primaryKeyNames != null ) {
3192
+ final RubyBoolean primary = runtime.newBoolean( primaryKeyNames.contains(colName) );
3193
+ column.getInstanceVariables().setInstanceVariable("@primary", primary);
2995
3194
  }
2996
3195
  }
2997
3196
  return columns;
2998
-
2999
3197
  }
3000
3198
 
3001
3199
  private static Collection<String> getPrimaryKeyNames(final DatabaseMetaData metaData,