activerecord-jdbc-adapter 1.3.17 → 1.3.18

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