activerecord-jdbc-adapter 1.3.5 → 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
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.