activerecord-jdbc-adapter 52.1-java → 52.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +34 -15
  3. data/Gemfile +1 -2
  4. data/README.md +10 -3
  5. data/lib/arjdbc/abstract/core.rb +12 -2
  6. data/lib/arjdbc/abstract/database_statements.rb +1 -1
  7. data/lib/arjdbc/abstract/statement_cache.rb +4 -4
  8. data/lib/arjdbc/db2/adapter.rb +68 -60
  9. data/lib/arjdbc/db2/as400.rb +12 -0
  10. data/lib/arjdbc/db2/column.rb +3 -0
  11. data/lib/arjdbc/db2/connection_methods.rb +4 -0
  12. data/lib/arjdbc/jdbc.rb +3 -0
  13. data/lib/arjdbc/jdbc/adapter.rb +0 -6
  14. data/lib/arjdbc/jdbc/column.rb +4 -2
  15. data/lib/arjdbc/mysql/adapter.rb +8 -0
  16. data/lib/arjdbc/postgresql/adapter.rb +5 -72
  17. data/lib/arjdbc/postgresql/oid_types.rb +82 -14
  18. data/lib/arjdbc/version.rb +1 -1
  19. data/rakelib/rails.rake +4 -3
  20. data/src/java/arjdbc/ArJdbcModule.java +5 -15
  21. data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +2 -2
  22. data/src/java/arjdbc/jdbc/ConnectionFactory.java +0 -87
  23. data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +0 -1
  24. data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
  25. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +46 -18
  26. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +2 -2
  27. data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
  28. data/src/java/arjdbc/postgresql/PostgreSQLResult.java +90 -17
  29. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +68 -49
  30. data/src/java/arjdbc/util/DateTimeUtils.java +119 -0
  31. data/src/java/arjdbc/util/PG.java +8 -0
  32. data/src/java/arjdbc/util/QuotingUtils.java +6 -7
  33. metadata +6 -4
  34. data/src/java/arjdbc/postgresql/PgResultSetMetaDataWrapper.java +0 -23
@@ -32,7 +32,15 @@ module ActiveRecord
32
32
  include ArJdbc::MySQL
33
33
 
34
34
  def initialize(connection, logger, connection_parameters, config)
35
+ # workaround to skip version check on JNDI to be lazy, dummy version is high enough for Rails 5.0 - 6.0
36
+ is_jndi = ::ActiveRecord::ConnectionAdapters::JdbcConnection.jndi_config?(config)
37
+ @version = '8.1.5' if is_jndi
38
+
35
39
  super
40
+
41
+ # set to nil to have it lazy-load the real value when required
42
+ @version = nil if is_jndi
43
+
36
44
  @prepared_statements = false unless config.key?(:prepared_statements)
37
45
  # configure_connection taken care of at ArJdbc::Abstract::Core
38
46
  end
@@ -531,77 +531,6 @@ module ArJdbc
531
531
  execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
532
532
  end
533
533
 
534
- # Returns an array of indexes for the given table.
535
- def indexes(table_name)
536
-
537
- # FIXME: AR version => table = Utils.extract_schema_qualified_name(table_name.to_s)
538
- schema, table = extract_schema_and_table(table_name.to_s)
539
-
540
- result = query(<<-SQL, 'SCHEMA')
541
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
542
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment
543
- FROM pg_class t
544
- INNER JOIN pg_index d ON t.oid = d.indrelid
545
- INNER JOIN pg_class i ON d.indexrelid = i.oid
546
- LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
547
- WHERE i.relkind = 'i'
548
- AND d.indisprimary = 'f'
549
- AND t.relname = '#{table}'
550
- AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
551
- ORDER BY i.relname
552
- SQL
553
-
554
- result.map do |row|
555
- index_name = row[0]
556
- # FIXME: These values [1,2] are returned in a different format than AR expects, maybe we could update it on the Java side to be more accurate
557
- unique = row[1].is_a?(String) ? row[1] == 't' : row[1] # JDBC gets us a boolean
558
- indkey = row[2].is_a?(Java::OrgPostgresqlUtil::PGobject) ? row[2].value : row[2]
559
- indkey = indkey.split(" ").map(&:to_i)
560
- inddef = row[3]
561
- oid = row[4]
562
- comment = row[5]
563
-
564
- using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
565
-
566
- orders = {}
567
- opclasses = {}
568
-
569
- if indkey.include?(0)
570
- columns = expressions
571
- else
572
- columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
573
- SELECT a.attnum, a.attname
574
- FROM pg_attribute a
575
- WHERE a.attrelid = #{oid}
576
- AND a.attnum IN (#{indkey.join(",")})
577
- SQL
578
-
579
- # add info on sort order (only desc order is explicitly specified, asc is the default)
580
- # and non-default opclasses
581
- expressions.scan(/(?<column>\w+)\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
582
- opclasses[column] = opclass.to_sym if opclass
583
- if nulls
584
- orders[column] = [desc, nulls].compact.join(' ')
585
- elsif desc
586
- orders[column] = :desc
587
- end
588
- end
589
- end
590
-
591
- IndexDefinition.new(
592
- table_name,
593
- index_name,
594
- unique,
595
- columns,
596
- orders: orders,
597
- opclasses: opclasses,
598
- where: where,
599
- using: using.to_sym,
600
- comment: comment.presence
601
- )
602
- end
603
- end
604
-
605
534
  # @private
606
535
  def column_name_for_operation(operation, node)
607
536
  case operation
@@ -690,6 +619,10 @@ module ArJdbc
690
619
  @local_tz ||= execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
691
620
  end
692
621
 
622
+ def bind_params_length
623
+ 32767
624
+ end
625
+
693
626
  end
694
627
  end
695
628
 
@@ -766,7 +699,7 @@ module ActiveRecord::ConnectionAdapters
766
699
 
767
700
  # Prepared statements aren't schema aware so we need to make sure we
768
701
  # store different PreparedStatement objects for different schemas
769
- def cached_statement_key(sql)
702
+ def sql_key(sql)
770
703
  "#{schema_search_path}-#{sql}"
771
704
  end
772
705
 
@@ -9,6 +9,61 @@ module ArJdbc
9
9
  # @private
10
10
  OID = ::ActiveRecord::ConnectionAdapters::PostgreSQL::OID
11
11
 
12
+ # this version makes sure to register the types by name as well
13
+ # we still need to version with OID since it's used from SchemaStatements as well
14
+ class ArjdbcTypeMapInitializer < OID::TypeMapInitializer
15
+ private
16
+
17
+ def name_with_ns(row)
18
+ if row['in_ns']
19
+ row['typname']
20
+ else
21
+ %Q("#{row['nspname']}"."#{row['typname']}")
22
+ end
23
+ end
24
+
25
+ def register_enum_type(row)
26
+ super
27
+ register name_with_ns(row), OID::Enum.new
28
+ end
29
+
30
+ def register_array_type(row)
31
+ super
32
+ register_with_subtype(name_with_ns(row), row['typelem'].to_i) do |subtype|
33
+ OID::Array.new(subtype, row['typdelim'])
34
+ end
35
+ end
36
+
37
+ def register_range_type(row)
38
+ super
39
+ name = name_with_ns(row)
40
+ register_with_subtype(name, row['rngsubtype'].to_i) do |subtype|
41
+ OID::Range.new(subtype, name.to_sym)
42
+ end
43
+ end
44
+
45
+ def register_domain_type(row)
46
+ if base_type = @store.lookup(row['typbasetype'].to_i)
47
+ register row['oid'], base_type
48
+ register name_with_ns(row), base_type
49
+ else
50
+ warn "unknown base type (OID: #{row['typbasetype']}) for domain #{row['typname']}."
51
+ end
52
+ end
53
+
54
+ def register_composite_type(row)
55
+ if subtype = @store.lookup(row['typelem'].to_i)
56
+ register row['oid'], OID::Vector.new(row['typdelim'], subtype)
57
+ register name_with_ns(row), OID::Vector.new(row['typdelim'], subtype)
58
+ end
59
+ end
60
+
61
+ def assert_valid_registration(oid, oid_type)
62
+ ret = super
63
+ ret == 0 ? oid : ret
64
+ end
65
+ end
66
+
12
67
  # @private
13
68
  module OIDTypes
14
69
 
@@ -33,15 +88,9 @@ module ArJdbc
33
88
  @extensions ||= super
34
89
  end
35
90
 
36
- # @override
37
- def lookup_cast_type(sql_type)
38
- oid = execute("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA")
39
- super oid.first['oid'].to_i
40
- end
41
-
42
91
  def get_oid_type(oid, fmod, column_name, sql_type = '') # :nodoc:
43
92
  if !type_map.key?(oid)
44
- load_additional_types([oid])
93
+ load_additional_types(type_map, oid)
45
94
  end
46
95
 
47
96
  type_map.fetch(oid, fmod, sql_type) {
@@ -132,27 +181,46 @@ module ArJdbc
132
181
  end
133
182
  end
134
183
 
135
- load_additional_types
184
+ load_additional_types(m)
185
+
186
+ # pgjdbc returns these if the column is auto-incrmenting
187
+ m.alias_type 'serial', 'int4'
188
+ m.alias_type 'bigserial', 'int8'
136
189
  end
137
190
 
138
- def load_additional_types(oids = nil) # :nodoc:
139
- initializer = OID::TypeMapInitializer.new(type_map)
191
+ def load_additional_types(type_map, oid = nil) # :nodoc:
192
+ initializer = ArjdbcTypeMapInitializer.new(type_map)
140
193
 
141
194
  if supports_ranges?
142
195
  query = <<-SQL
143
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
196
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype,
197
+ ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
144
198
  FROM pg_type as t
145
199
  LEFT JOIN pg_range as r ON oid = rngtypid
200
+ JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
146
201
  SQL
147
202
  else
148
203
  query = <<-SQL
149
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
204
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype,
205
+ ns.nspname, ns.nspname = ANY(current_schemas(true)) in_ns
150
206
  FROM pg_type as t
207
+ JOIN pg_namespace AS ns ON t.typnamespace = ns.oid
151
208
  SQL
152
209
  end
153
210
 
154
- if oids
155
- query += "WHERE t.oid::integer IN (%s)" % oids.join(", ")
211
+ if oid
212
+ if oid.is_a? Numeric || oid.match(/^\d+$/)
213
+ # numeric OID
214
+ query += "WHERE t.oid::integer = %s" % oid
215
+
216
+ elsif m = oid.match(/"?(\w+)"?\."?(\w+)"?/)
217
+ # namespace and type name
218
+ query += "WHERE ns.nspname = '%s' AND t.typname = '%s'" % [m[1], m[2]]
219
+
220
+ else
221
+ # only type name
222
+ query += "WHERE t.typname = '%s' AND ns.nspname = ANY(current_schemas(true))" % oid
223
+ end
156
224
  else
157
225
  query += initializer.query_conditions_for_initial_load
158
226
  end
@@ -1,3 +1,3 @@
1
1
  module ArJdbc
2
- VERSION = '52.1'
2
+ VERSION = '52.2'
3
3
  end
@@ -9,14 +9,15 @@ namespace :rails do
9
9
  if ENV['RAILS']
10
10
  ar_path = File.join(ENV['RAILS'], 'activerecord')
11
11
  end
12
-
12
+
13
13
  unless ar_path && File.exist?(ar_path)
14
- ar_path = `bundle info --path activerecord`.chomp
14
+ ar_path = `bundle info --path activerecord`.lines.last.chomp
15
15
  end
16
16
 
17
17
  unless File.exist? ar_test_dir = File.join(ar_path, 'test')
18
18
  raise "can not directly load Rails tests;" +
19
- " try setting a local repository path e.g. export RAILS=`pwd`/../rails"
19
+ " try setting a local repository path e.g. export RAILS=`pwd`/../rails;" +
20
+ " failed guess: #{ar_path}"
20
21
  end
21
22
 
22
23
  driver = "jdbc-#{ENV['DRIVER'] ? ENV['DRIVER'].downcase : (adapter =~ /postgres/i ? 'postgres' : adapter)}"
@@ -29,7 +29,6 @@ import java.util.HashMap;
29
29
  import java.util.Map;
30
30
  import java.util.WeakHashMap;
31
31
 
32
- import org.jruby.NativeException;
33
32
  import org.jruby.Ruby;
34
33
  import org.jruby.RubyArray;
35
34
  import org.jruby.RubyClass;
@@ -148,13 +147,7 @@ public class ArJdbcModule {
148
147
  }
149
148
  }
150
149
  catch (ClassNotFoundException e) { /* ignored */ }
151
- catch (NoSuchMethodException e) {
152
- throw newNativeException(runtime, e);
153
- }
154
- catch (IllegalAccessException e) {
155
- throw newNativeException(runtime, e);
156
- }
157
- catch (InvocationTargetException e) {
150
+ catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
158
151
  throw newNativeException(runtime, e);
159
152
  }
160
153
 
@@ -263,18 +256,15 @@ public class ArJdbcModule {
263
256
  try {
264
257
  return klass.getMethod(name, argType).invoke(null, arg);
265
258
  }
266
- catch (IllegalAccessException e) {
267
- throw newNativeException(runtime, e);
268
- }
269
- catch (InvocationTargetException e) {
259
+ catch (IllegalAccessException | InvocationTargetException e) {
270
260
  throw newNativeException(runtime, e);
271
261
  }
272
262
  }
273
263
 
274
264
  private static RaiseException newNativeException(final Ruby runtime, final Throwable cause) {
275
- RubyClass nativeClass = runtime.getClass(NativeException.CLASS_NAME);
276
- NativeException nativeException = new NativeException(runtime, nativeClass, cause);
277
- return new RaiseException(cause, nativeException);
265
+ final RaiseException error = runtime.newRuntimeError(cause.toString());
266
+ error.initCause(cause);
267
+ return error;
278
268
  }
279
269
 
280
270
  @JRubyMethod(meta = true)
@@ -83,8 +83,8 @@ public class DerbyRubyJdbcConnection extends RubyJdbcConnection {
83
83
  final int minor = jdbcDriver.getMinorVersion();
84
84
  if ( major < 10 || ( major == 10 && minor < 5 ) ) {
85
85
  final RubyClass errorClass = getConnectionNotEstablished(context.runtime);
86
- throw new RaiseException(context.runtime, errorClass,
87
- "adapter requires Derby >= 10.5 got: " + major + "." + minor + "", false);
86
+ throw context.runtime.newRaiseException(errorClass,
87
+ "adapter requires Derby >= 10.5 got: " + major + "." + minor + "");
88
88
  }
89
89
  if ( major == 10 && minor < 8 ) { // 10.8 ~ supports JDBC 4.1
90
90
  // config[:connection_alive_sql] ||= 'SELECT 1 FROM SYS.SYSSCHEMAS FETCH FIRST 1 ROWS ONLY'
@@ -27,12 +27,6 @@ package arjdbc.jdbc;
27
27
 
28
28
  import java.sql.Connection;
29
29
  import java.sql.SQLException;
30
- import javax.sql.DataSource;
31
-
32
- import org.jruby.RubyObject;
33
- import org.jruby.RubyString;
34
- import org.jruby.runtime.ThreadContext;
35
- import org.jruby.runtime.builtin.IRubyObject;
36
30
 
37
31
  /**
38
32
  * Interface to be implemented in Ruby for retrieving a new connection.
@@ -49,84 +43,3 @@ public interface ConnectionFactory {
49
43
  Connection newConnection() throws SQLException;
50
44
 
51
45
  }
52
-
53
- class DataSourceConnectionFactoryImpl implements ConnectionFactory {
54
-
55
- private final DataSource dataSource;
56
- final String username, password; // optional
57
-
58
- public DataSourceConnectionFactoryImpl(final DataSource dataSource) {
59
- this.dataSource = dataSource;
60
- this.username = null; this.password = null;
61
- }
62
-
63
- public DataSourceConnectionFactoryImpl(final DataSource dataSource,
64
- final String username, final String password) {
65
- this.dataSource = dataSource;
66
- this.username = username; this.password = password;
67
- }
68
-
69
- @Override
70
- public Connection newConnection() throws SQLException {
71
- if ( username != null ) {
72
- dataSource.getConnection(username, password);
73
- }
74
- return dataSource.getConnection();
75
- }
76
-
77
- DataSource getDataSource() { return dataSource; } /* for tests */
78
-
79
- }
80
-
81
- class DriverConnectionFactoryImpl implements ConnectionFactory {
82
-
83
- private final DriverWrapper driverWrapper;
84
- final String url;
85
- final String username, password; // null allowed
86
-
87
- public DriverConnectionFactoryImpl(final DriverWrapper driver, final String url) {
88
- this.driverWrapper = driver; this.url = url;
89
- this.username = null; this.password = null;
90
- }
91
-
92
- public DriverConnectionFactoryImpl(final DriverWrapper driver, final String url,
93
- final String username, final String password) {
94
- this.driverWrapper = driver; this.url = url;
95
- this.username = username; this.password = password;
96
- }
97
-
98
- @Override
99
- public Connection newConnection() throws SQLException {
100
- return driverWrapper.connect(url, username, password);
101
- }
102
-
103
- DriverWrapper getDriverWrapper() { return driverWrapper; } /* for tests */
104
-
105
- }
106
-
107
- // @legacy ActiveRecord::ConnectionAdapters::JdbcDriver
108
- class RubyConnectionFactoryImpl implements ConnectionFactory {
109
-
110
- private final IRubyObject driver;
111
- final RubyString url;
112
- final IRubyObject username, password; // null allowed
113
-
114
- private final RubyObject contextProvider;
115
-
116
- public RubyConnectionFactoryImpl(final IRubyObject driver, final RubyString url,
117
- final IRubyObject username, final IRubyObject password) {
118
- this.driver = driver; this.url = url;
119
- this.username = username; this.password = password;
120
- contextProvider = (RubyObject) driver;
121
- }
122
-
123
- @Override
124
- public Connection newConnection() throws SQLException {
125
- final ThreadContext context = contextProvider.getRuntime().getCurrentContext();
126
- final IRubyObject connection = driver.callMethod(context, "connection", new IRubyObject[] { url, username, password });
127
- return (Connection) connection.toJava(Connection.class);
128
- }
129
-
130
- IRubyObject getDriver() { return driver; } /* for tests */
131
-
132
- }
@@ -68,7 +68,6 @@ final class DataSourceConnectionFactory implements ConnectionFactory {
68
68
 
69
69
  @Override
70
70
  public Connection newConnection() throws SQLException {
71
- DataSource dataSource = this.dataSource;
72
71
  // in case DS failed previously look it up again from JNDI :
73
72
  if (dataSource == null) {
74
73
  lookupDataSource();
@@ -0,0 +1,61 @@
1
+ /***** BEGIN LICENSE BLOCK *****
2
+ * Copyright (c) 2012-2014 Karol Bucek <self@kares.org>
3
+ * Copyright (c) 2006-2010 Nick Sieger <nick@nicksieger.com>
4
+ * Copyright (c) 2006-2007 Ola Bini <ola.bini@gmail.com>
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining
7
+ * a copy of this software and associated documentation files (the
8
+ * "Software"), to deal in the Software without restriction, including
9
+ * without limitation the rights to use, copy, modify, merge, publish,
10
+ * distribute, sublicense, and/or sell copies of the Software, and to
11
+ * permit persons to whom the Software is furnished to do so, subject to
12
+ * the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be
15
+ * included in all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ ***** END LICENSE BLOCK *****/
25
+
26
+ package arjdbc.jdbc;
27
+
28
+ import java.sql.Connection;
29
+ import java.sql.SQLException;
30
+
31
+ import org.jruby.RubyObject;
32
+ import org.jruby.RubyString;
33
+ import org.jruby.runtime.ThreadContext;
34
+ import org.jruby.runtime.builtin.IRubyObject;
35
+
36
+ // @legacy ActiveRecord::ConnectionAdapters::JdbcDriver
37
+ class RubyConnectionFactory implements ConnectionFactory {
38
+
39
+ private final IRubyObject driver;
40
+ final RubyString url;
41
+ final IRubyObject username, password; // null allowed
42
+
43
+ private final RubyObject contextProvider;
44
+
45
+ public RubyConnectionFactory(final IRubyObject driver, final RubyString url,
46
+ final IRubyObject username, final IRubyObject password) {
47
+ this.driver = driver; this.url = url;
48
+ this.username = username; this.password = password;
49
+ contextProvider = (RubyObject) driver;
50
+ }
51
+
52
+ @Override
53
+ public Connection newConnection() throws SQLException {
54
+ final ThreadContext context = contextProvider.getRuntime().getCurrentContext();
55
+ final IRubyObject connection = driver.callMethod(context, "connection", new IRubyObject[] { url, username, password });
56
+ return (Connection) connection.toJava(Connection.class);
57
+ }
58
+
59
+ IRubyObject getDriver() { return driver; } /* for tests */
60
+
61
+ }