activerecord-jdbc-adapter 1.3.5 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a2ea14fa2985e6b568284ddd24df9db9511f6437
4
- data.tar.gz: 72570ca5a15d8f3bd04b3efc5c9b3c7b86911215
3
+ metadata.gz: ebdc2f21df1a1fbe848c242a5a9c6e351a20056e
4
+ data.tar.gz: 67e764365675757678fc29d3a2422faaa0b2920a
5
5
  SHA512:
6
- metadata.gz: 72bbce793d260cfdd46f81007543bd0e6aff6a81149b45e0d21de5cef98e0c96b6cdb1127b1dc7ab03a372510a664c70996dd879ca7d7a53f5a3b32f89466a0e
7
- data.tar.gz: 07087ad9b681f0c0d6cb4008fc5f432991baf24fd62615e9864d8c420938c8585767c324493c239888ff50550bd76aa1b5a3e943eb0716d4a14615dbd36ad39d
6
+ metadata.gz: 006bb63ddde830d1b02a26c5780349d1ad7e6204dbcd9195583fcf65b11f102a22e4e9dab43a6e242c344293503dd56140b6fa55f7b48e967289785b74bec88a
7
+ data.tar.gz: e9e05577e5fb7965d0a5b38b64f1b7985d9412a31f5d0c7d414a4e8c1b865293ca8e77c120ee7402b7088c70c4a0b9c3f93a07c77868cde11a27ed9406b61e52
@@ -3,8 +3,7 @@ bundler_args: --without development
3
3
  script: bundle exec rake test_$DB
4
4
  before_script: export JRUBY_OPTS="--server -Xcext.enabled=false -Xcompile.invokedynamic=false"
5
5
  before_install:
6
- #- gem update --system 2.1.11
7
- #- 'ruby -e "puts RUBY_VERSION" | grep ^1\.8 && gem update --system -v 2.1.11 || true'
6
+ - jruby --1.9 -S gem update --system 2.1.11
8
7
  rvm:
9
8
  - jruby
10
9
  gemfile:
data/Gemfile CHANGED
@@ -20,8 +20,8 @@ gem 'appraisal', :require => nil
20
20
 
21
21
  # appraisal ignores group block declarations :
22
22
 
23
- gem 'test-unit', '2.5.4', :group => :test
24
- gem 'test-unit-context', '>= 0.3.0', :group => :test
23
+ gem 'test-unit', '~> 2.5.4', :group => :test
24
+ gem 'test-unit-context', '>= 0.4.0', :group => :test
25
25
  gem 'mocha', '~> 0.13.1', :require => nil, :group => :test
26
26
 
27
27
  gem 'simplecov', :require => nil, :group => :test
data/History.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 1.3.6 (02/04/14)
2
+
3
+ - fix rails 4-0-stable compatibility (see #530)
4
+ - [mysql] support "disabling" abandoned connection cleanup thread
5
+ - [mssql] Handling of 'GROUP BY' and selected columns (#529)
6
+ + SELECT DISTINCT clause with ORDER BY for MSSQL (partially fixes #437)
7
+ - [derby] only do the patched select_limited_ids if connection is Derby's
8
+ - [derby] support getting and setting transaction isolation on a connection
9
+ + allow to configure whether isolation will be 'serializable' (work-around for #497)
10
+ - match 'int' as well as 'integer' when converting to SQL types for MSSQL (#527)
11
+
12
+ Code Contributors: Sean McCarthy, Jesko, Konstantin Shabanov
13
+
1
14
  ## 1.3.5 (01/10/14)
2
15
 
3
16
  We're now green against Rails 4.1 (master), test and report issues if any.
@@ -8,7 +21,6 @@ We're now green against Rails 4.1 (master), test and report issues if any.
8
21
  - [firebird] Insert quotes for blobs to prevent failed inserts on not-null cols
9
22
 
10
23
  Code Contributors: Ray Zane, Gary S. Weaver
11
- Code Contributors: @rzane, @garysweaver
12
24
 
13
25
  ## 1.3.4 (12/12/13)
14
26
 
@@ -1,9 +1,10 @@
1
- # Needed because Rails is broken wrt to quoting of some values.
2
- # Most databases are nice about it, but not Derby.
1
+ # Needed because Rails is broken wrt to quoting of some values.
2
+ # Most databases are nice about it, but not Derby.
3
3
  # The real issue is that you can't compare a CHAR value to a NUMBER column.
4
4
  ActiveRecord::Associations::ClassMethods.module_eval do
5
5
  private
6
6
  def select_limited_ids_list(options, join_dependency)
7
+ return super unless connection.is_a?(ArJdbc::Derby)
7
8
  connection.select_all(
8
9
  construct_finder_sql_for_association_limiting(options, join_dependency),
9
10
  "#{name} Load IDs For Limited Eager Loading"
@@ -109,7 +109,9 @@ module ArJdbc
109
109
 
110
110
  def configure_connection
111
111
  # must be done or SELECT...FOR UPDATE won't work how we expect :
112
- execute("SET ISOLATION = SERIALIZABLE")
112
+ tx_isolation = config[:transaction_isolation] # set false to leave as is
113
+ tx_isolation = :serializable if tx_isolation.nil?
114
+ @connection.transaction_isolation = tx_isolation if tx_isolation
113
115
  # if a user name was specified upon connection, the user's name is the
114
116
  # default schema for the connection, if a schema with that name exists
115
117
  set_schema(config[:schema]) if config.key?(:schema)
@@ -507,8 +507,8 @@ module ActiveRecord
507
507
 
508
508
  # @private
509
509
  # @override
510
- def select_rows(sql, name = nil)
511
- exec_query_raw(sql, name).map!(&:values)
510
+ def select_rows(sql, name = nil, binds = [])
511
+ exec_query_raw(sql, name, binds).map!(&:values)
512
512
  end
513
513
 
514
514
  if ActiveRecord::VERSION::MAJOR > 3 # expects AR::Result e.g. from select_all
@@ -123,7 +123,7 @@ module ArJdbc
123
123
  'NVARCHAR(MAX)'
124
124
  elsif NO_LIMIT_TYPES.include?(type_s)
125
125
  super(type)
126
- elsif type_s == 'integer'
126
+ elsif type_s == 'integer' || type_s == 'int'
127
127
  if limit.nil? || limit == 4
128
128
  'int'
129
129
  elsif limit == 2
@@ -360,6 +360,32 @@ module ArJdbc
360
360
  execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
361
361
  end
362
362
 
363
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
364
+ # MSSQL requires the ORDER BY columns in the select list for distinct queries.
365
+ def distinct(columns, order_by)
366
+ "DISTINCT #{columns_for_distinct(columns, order_by)}"
367
+ end
368
+
369
+ def columns_for_distinct(columns, orders)
370
+ return columns if orders.blank?
371
+
372
+ # construct a clean list of column names from the ORDER BY clause,
373
+ # removing any ASC/DESC modifiers
374
+ order_columns = [ orders ]; order_columns.flatten! # AR 3.x vs 4.x
375
+ order_columns.map! do |column|
376
+ column = column.to_sql unless column.is_a?(String) # handle AREL node
377
+ column.split(',').collect!{ |s| s.split.first }
378
+ end.flatten!
379
+ order_columns.reject!(&:blank?)
380
+ order_columns = order_columns.zip(0...order_columns.size).to_a
381
+ order_columns = order_columns.map{ |s, i| "#{s}" }
382
+
383
+ columns = [ columns ]; columns.flatten!
384
+ columns.push( *order_columns ).join(', ')
385
+ # return a DISTINCT clause that's distinct on the columns we want but
386
+ # includes all the required columns for the ORDER BY to work properly
387
+ end
388
+
363
389
  # @override
364
390
  def change_column(table_name, column_name, type, options = {})
365
391
 
@@ -24,13 +24,34 @@ module ArJdbc
24
24
  rest_of_query = "#{from_table}.#{rest_of_query}"
25
25
  end
26
26
 
27
+ # Ensure correct queries if the rest_of_query contains a 'GROUP BY'. Otherwise the following error occurs:
28
+ # ActiveRecord::StatementInvalid: ActiveRecord::JDBCError: Column 'users.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
29
+ # SELECT t.* FROM ( SELECT ROW_NUMBER() OVER(ORDER BY users.id) AS _row_num, [users].[lft], COUNT([users].[lft]) FROM [users] GROUP BY [users].[lft] HAVING COUNT([users].[lft]) > 1 ) AS t WHERE t._row_num BETWEEN 1 AND 1
30
+ if rest_of_query.downcase.include?('group by')
31
+ if order.count(',') == 0
32
+ order.gsub!(/ORDER BY (.*)/, 'ORDER BY MIN(\1)')
33
+ else
34
+ raise('Only one order condition allowed.')
35
+ end
36
+ end
37
+
27
38
  if distinct # select =~ /DISTINCT/i
28
39
  order = order.gsub(/([a-z0-9_])+\./, 't.')
29
40
  new_sql = "SELECT t.* FROM "
30
41
  new_sql << "( SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, t.* FROM (#{select} #{rest_of_query}) AS t ) AS t"
31
42
  new_sql << " WHERE t._row_num BETWEEN #{start_row} AND #{end_row}"
32
43
  else
33
- new_sql = "#{select} t.* FROM "
44
+ select_columns_before_from = rest_of_query.gsub(/FROM.*/, '').strip
45
+ only_one_column = !select_columns_before_from.include?(',')
46
+ only_one_id_column = only_one_column && (select_columns_before_from.ends_with?('.id') || select_columns_before_from.ends_with?('.[id]'))
47
+
48
+ if only_one_id_column
49
+ # If there's only one id column a subquery will be created which only contains this column
50
+ new_sql = "#{select} t.id FROM "
51
+ else
52
+ # All selected columns are used
53
+ new_sql = "#{select} t.* FROM "
54
+ end
34
55
  new_sql << "( SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query} ) AS t"
35
56
  new_sql << " WHERE t._row_num BETWEEN #{start_row} AND #{end_row}"
36
57
  end
@@ -1,5 +1,5 @@
1
1
  module ArJdbc
2
- VERSION = "1.3.5"
2
+ VERSION = "1.3.6"
3
3
  # @deprecated
4
4
  module Version
5
5
  # @private 1.2.x compatibility
@@ -1,17 +1,20 @@
1
1
  require File.expand_path('../../test/shared_helper', __FILE__)
2
2
 
3
+ test_tasks = [ 'test_mysql', 'test_sqlite3', 'test_postgresql_with_hint' ]
3
4
  if defined?(JRUBY_VERSION)
4
- databases = [ :test_mysql, :test_sqlite3, :test_derby, :test_hsqldb, :test_h2 ]
5
- databases << :test_postgres if PostgresHelper.have_postgres?(false)
6
- databases << :test_jdbc ; databases << :test_jndi
7
- task :test do
8
- unless PostgresHelper.have_postgres?
9
- warn "... won't run test_postgres tests"
10
- end
11
- databases.each { |task| Rake::Task[task.to_s].invoke }
5
+ test_tasks.push :test_derby, :test_hsqldb, :test_h2
6
+ test_tasks.push :test_jndi, :test_jdbc
7
+ end
8
+
9
+ desc "Run \"most\" available test_xxx tasks"
10
+ task :test => test_tasks
11
+
12
+ task 'test_postgresql_with_hint' do
13
+ if PostgresHelper.have_postgres?(false)
14
+ Rake::Task['test_postgresql'].invoke
15
+ else
16
+ puts "NOTE: won't run test_postgresql"
12
17
  end
13
- else
14
- task :test => [ :test_mysql ]
15
18
  end
16
19
 
17
20
  def set_test_task_compat_version(task)
@@ -26,14 +29,18 @@ def set_task_description(task, desc)
26
29
  task = task.name if task.is_a?(Rake::TestTask)
27
30
  task = Rake::Task[task]
28
31
  end
29
- # reset the desc set-up by TestTask :
30
- task.instance_variable_set(:@full_comment, nil)
32
+ # reset the description set-up by TestTask :
33
+ if task.instance_variable_defined? :@full_comment
34
+ task.instance_variable_set(:@full_comment, nil)
35
+ else
36
+ task.instance_variable_get(:@comments).clear
37
+ end
31
38
  task.add_description(desc)
32
39
  end
33
40
 
34
41
  task 'test_appraisal_hint' do
35
42
  next if File.exists?('.disable-appraisal-hint')
36
- unless (ENV['BUNDLE_GEMFILE'] rescue '') =~ /gemfiles\/.*?\.gemfile/
43
+ unless (ENV['BUNDLE_GEMFILE'] || '') =~ /gemfiles\/.*?\.gemfile/
37
44
  appraisals = []; Appraisal::File.each { |file| appraisals << file.name }
38
45
  puts "HINT: specify AR version with `rake appraisal:{version} test_{adapter}'" +
39
46
  " where version=(#{appraisals.join('|')}) (`touch .disable-appraisal-hint' to disable)"
@@ -80,8 +87,8 @@ test_task_for :MSSQL, :driver => :jtds, :database_name => 'MS-SQL (SQLServer)'
80
87
  test_task_for :MySQL, :prereqs => 'db:mysql'
81
88
  test_task_for :PostgreSQL, :prereqs => 'db:postgresql', :driver => 'postgres'
82
89
  task :test_postgres => :test_postgresql # alias
83
- task :test_pgsql => :test_postgresql # alias
84
90
  test_task_for :SQLite3
91
+ task :test_sqlite => :test_sqlite3 # alias
85
92
  test_task_for :Firebird
86
93
 
87
94
  # ensure driver for these DBs is on your class-path
@@ -118,4 +118,46 @@ public class DerbyRubyJdbcConnection extends RubyJdbcConnection {
118
118
  return super.matchTables(runtime, connection, catalog, schemaPattern, tablePattern, types, checkExistsOnly);
119
119
  }
120
120
 
121
+ @JRubyMethod(name = "transaction_isolation", alias = "get_transaction_isolation")
122
+ public IRubyObject get_transaction_isolation(final ThreadContext context) {
123
+ return withConnection(context, new Callable<IRubyObject>() {
124
+ public IRubyObject call(final Connection connection) throws SQLException {
125
+ final int level = connection.getTransactionIsolation();
126
+ final String isolationSymbol = formatTransactionIsolationLevel(level);
127
+ if ( isolationSymbol == null ) return context.getRuntime().getNil();
128
+ return context.getRuntime().newSymbol(isolationSymbol);
129
+ }
130
+ });
131
+ }
132
+
133
+ @JRubyMethod(name = "transaction_isolation=", alias = "set_transaction_isolation")
134
+ public IRubyObject set_transaction_isolation(final ThreadContext context, final IRubyObject isolation) {
135
+ return withConnection(context, new Callable<IRubyObject>() {
136
+ public IRubyObject call(final Connection connection) throws SQLException {
137
+ final int level;
138
+ if ( isolation.isNil() ) {
139
+ level = connection.getMetaData().getDefaultTransactionIsolation();
140
+ }
141
+ else {
142
+ level = mapTransactionIsolationLevel(isolation);
143
+ }
144
+
145
+ connection.setTransactionIsolation(level);
146
+
147
+ final String isolationSymbol = formatTransactionIsolationLevel(level);
148
+ if ( isolationSymbol == null ) return context.getRuntime().getNil();
149
+ return context.getRuntime().newSymbol(isolationSymbol);
150
+ }
151
+ });
152
+ }
153
+
154
+ public static String formatTransactionIsolationLevel(final int level) {
155
+ if ( level == Connection.TRANSACTION_READ_UNCOMMITTED ) return "read_uncommitted"; // 1
156
+ if ( level == Connection.TRANSACTION_READ_COMMITTED ) return "read_committed"; // 2
157
+ if ( level == Connection.TRANSACTION_REPEATABLE_READ ) return "repeatable_read"; // 4
158
+ if ( level == Connection.TRANSACTION_SERIALIZABLE ) return "serializable"; // 8
159
+ if ( level == 0 ) return null;
160
+ throw new IllegalArgumentException("unexpected transaction isolation level: " + level);
161
+ }
162
+
121
163
  }
@@ -29,6 +29,7 @@ import arjdbc.jdbc.RubyJdbcConnection;
29
29
  import arjdbc.jdbc.Callable;
30
30
 
31
31
  import java.lang.reflect.Field;
32
+ import java.lang.reflect.InvocationTargetException;
32
33
  import java.lang.reflect.Proxy;
33
34
  import java.sql.Connection;
34
35
  import java.sql.PreparedStatement;
@@ -234,10 +235,48 @@ public class MySQLRubyJdbcConnection extends RubyJdbcConnection {
234
235
  @Override
235
236
  protected Connection newConnection() throws RaiseException, SQLException {
236
237
  final Connection connection = super.newConnection();
238
+ if ( doStopCleanupThread() ) shutdownCleanupThread();
237
239
  if ( doKillCancelTimer(connection) ) killCancelTimer(connection);
238
240
  return connection;
239
241
  }
240
242
 
243
+ private static Boolean stopCleanupThread;
244
+ static {
245
+ final String stopThread = System.getProperty("arjdbc.mysql.stop_cleanup_thread");
246
+ if ( stopThread != null ) stopCleanupThread = Boolean.parseBoolean(stopThread);
247
+ }
248
+
249
+ private static boolean doStopCleanupThread() throws SQLException {
250
+ // TODO when refactoring default behavior to "stop" consider not doing so for JNDI
251
+ return stopCleanupThread != null && stopCleanupThread.booleanValue();
252
+ }
253
+
254
+ private static boolean cleanupThreadShutdown;
255
+
256
+ private static void shutdownCleanupThread() {
257
+ if ( cleanupThreadShutdown ) return;
258
+ try {
259
+ Class threadClass = Class.forName("com.mysql.jdbc.AbandonedConnectionCleanupThread");
260
+ threadClass.getMethod("shutdown").invoke(null);
261
+ }
262
+ catch (ClassNotFoundException e) {
263
+ debugMessage("INFO: missing MySQL JDBC cleanup thread: " + e);
264
+ }
265
+ catch (NoSuchMethodException e) {
266
+ debugMessage( e.toString() );
267
+ }
268
+ catch (IllegalAccessException e) {
269
+ debugMessage( e.toString() );
270
+ }
271
+ catch (InvocationTargetException e) {
272
+ debugMessage( e.getTargetException().toString() );
273
+ }
274
+ catch (SecurityException e) {
275
+ debugMessage( e.toString() );
276
+ }
277
+ finally { cleanupThreadShutdown = true; }
278
+ }
279
+
241
280
  private static Boolean killCancelTimer;
242
281
  static {
243
282
  final String killTimer = System.getProperty("arjdbc.mysql.kill_cancel_timer");
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-jdbc-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sieger, Ola Bini, Karol Bucek and JRuby contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-10 00:00:00.000000000 Z
11
+ date: 2014-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -238,7 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
238
  version: '0'
239
239
  requirements: []
240
240
  rubyforge_project: jruby-extras
241
- rubygems_version: 2.1.9
241
+ rubygems_version: 2.1.11
242
242
  signing_key:
243
243
  specification_version: 4
244
244
  summary: JDBC adapter for ActiveRecord, for use within JRuby on Rails.