activerecord-jdbc-alt-adapter 52.2.3-java → 52.4.0-java

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
  SHA256:
3
- metadata.gz: afa74b6cea1f8d613568c65eafa0e35b2f40518f81c75616d9780eb9d7331807
4
- data.tar.gz: 8fa023e378edad7552c2209326646d62381832fd18273b38d00158810da01009
3
+ metadata.gz: 1c26b1aeb852d6a51090bf9084b5371336673343434f2ac71e8ce11bacbf1c12
4
+ data.tar.gz: 770683c6f0474d1393224eb342768abf987614a6fdb8affc2095cb60dc6ce8fd
5
5
  SHA512:
6
- metadata.gz: 0a94794211883f5c3c39a75211990e09492e1dc7d3535aa0a103b5b7175d5a885813fccc1ad5555973a12b6b73d1b81162f15d39244744dace487c4ce1c9f16d
7
- data.tar.gz: 3d648085d101929af897bded31b1534f243930bf56847b36cd586b5a7172b24e5ac18393001176fcc2c71a92b5ed18755d6ea0a2d1e53c28955ecb111a9ae95f
6
+ metadata.gz: d76c562fb3f75d56af58bec90913e2f60dee2866e18b1c1f8589c6918fcddec36dac70763560af9bb334c021d198a81511e3a3501142b658fe98bd306ad42525
7
+ data.tar.gz: 2a4a6fa05469f1246cbc278b10d65a4fd9003e9e963a7bbb8abc302751c6bfabe37a12404f6da0a87c6c9804cd11e868eb11c94f93e58d0843da01e79c77ec6c
data/.travis.yml CHANGED
@@ -1,18 +1,27 @@
1
- language: ruby
2
1
  sudo: false
3
- branches:
4
- only:
5
- - master
6
- - /.*-stable$/
7
- - /^test-.*/
8
- - /maintenance|support/
9
- - /.*dev$/
10
- bundler_args: --without development
11
- script: bundle exec rake ${TEST_PREFIX}test_$DB
2
+ dist: xenial
3
+
4
+ services:
5
+ - mysql
6
+ addons:
7
+ postgresql: 9.4
8
+
12
9
  before_install:
13
10
  - unset _JAVA_OPTIONS
14
11
  - rvm @default,@global do gem uninstall bundler -a -x -I || true
15
12
  - gem install bundler -v "~>1.17.3"
13
+ install:
14
+ - bundle install --retry 3 --without development
15
+ # to fix this issue: https://travis-ci.community/t/mariadb-10-1-fails-to-install-on-xenial/3151/3
16
+ - mysql -u root -e 'CREATE USER IF NOT EXISTS travis@localhost; GRANT ALL ON *.* TO travis@localhost;' || true
17
+
18
+ language: ruby
19
+ rvm:
20
+ - jruby-9.1.16.0
21
+ jdk:
22
+ - openjdk8
23
+
24
+ script: bundle exec rake ${TEST_PREFIX}test_$DB
16
25
  before_script:
17
26
  - echo "JAVA_OPTS=$JAVA_OPTS"
18
27
  - export JRUBY_OPTS="-J-Xms64M -J-Xmx1024M"
@@ -38,12 +47,7 @@ before_script:
38
47
  psql -c "create database activerecord_unittest;" -U postgres && \
39
48
  psql -c "create database activerecord_unittest2;" -U postgres \
40
49
  || true
41
- rvm:
42
- - jruby-9.1.16.0
43
- jdk:
44
- - openjdk8
45
- addons:
46
- postgresql: "9.4"
50
+
47
51
  env:
48
52
  - DB=mysql2 PREPARED_STATEMENTS=false
49
53
  - DB=mysql2 PREPARED_STATEMENTS=true
@@ -86,10 +90,10 @@ matrix:
86
90
  env: DB=sqlite3
87
91
  # testing against MariaDB
88
92
  - addons:
89
- mariadb: '10.0'
93
+ mariadb: 10.2
90
94
  env: DB=mariadb PREPARED_STATEMENTS=false
91
95
  - addons:
92
- mariadb: '10.1'
96
+ mariadb: 10.3
93
97
  env: DB=mariadb PREPARED_STATEMENTS=true
94
98
  # Rails test-suite :
95
99
  - env: DB=mysql2 TEST_PREFIX="rails:" AR_VERSION="v5.2.3" # PS off by default
data/Gemfile CHANGED
@@ -48,7 +48,7 @@ group :test do
48
48
  gem 'mocha', '~> 1.2', require: false # Rails has '~> 0.14'
49
49
 
50
50
  gem 'bcrypt', '~> 3.1.11', require: false
51
- gem 'jdbc-mssql', '~> 0.6.0', require: nil
51
+ gem 'jdbc-mssql', '~> 0.7.0', require: nil
52
52
  # gem 'pry-debugger-jruby', platform: :jruby
53
53
  end
54
54
 
data/README.md CHANGED
@@ -117,15 +117,16 @@ this adapter should just work.
117
117
  ActiveRecord-JDBC-Adapter (AR-JDBC) is the main database adapter for Rails'
118
118
  *ActiveRecord* component that can be used with [JRuby][0].
119
119
  ActiveRecord-JDBC-Adapter provides full or nearly full support for:
120
- **MySQL**, **PostgreSQL**, **SQLite3**. In the near future there are plans to
121
- add support **MSSQL**. Unless we get more contributions we will not be going
122
- beyond these four adapters. Note that the amount of work needed to get
123
- another adapter is not huge but the amount of testing required to make sure
124
- that adapter continues to work is not something we can do with the resources
125
- we currently have.
120
+ **MySQL**, **PostgreSQL**, **SQLite3** and **MSSQL*** (SQLServer).
126
121
 
127
- For Oracle database users you are encouraged to use
128
- https://github.com/rsim/oracle-enhanced.
122
+ Unless we get more contributions we will not be supporting more adapters.
123
+ Note that the amount of work needed to get another adapter is not huge but
124
+ the amount of testing required to make sure that adapter continues to work
125
+ is not something we can do with the resources we currently have.
126
+
127
+ - for **Oracle** database users you are encouraged to use
128
+ https://github.com/rsim/oracle-enhanced
129
+ - **MSSQL** adapter's gem parts reside in a [separate repository][8]
129
130
 
130
131
  Versions are targeted at certain versions of Rails and live on their own branches.
131
132
 
@@ -151,6 +152,7 @@ adapters are available:
151
152
  - MySQL (`activerecord-jdbcmysql-adapter`)
152
153
  - PostgreSQL (`activerecord-jdbcpostgresql-adapter`)
153
154
  - SQLite3 (`activerecord-jdbcsqlite3-adapter`)
155
+ - MSSQL (`activerecord-jdbcsqlserver-adapter`)
154
156
 
155
157
  2. If you're generating a new Rails application, use the following command:
156
158
 
@@ -285,4 +287,4 @@ license the database's drivers are licensed. See each driver gem's LICENSE.txt.
285
287
  [5]: https://github.com/jruby/activerecord-jdbc-adapter/wiki
286
288
  [6]: https://webchat.freenode.net/?channels=#jruby
287
289
  [7]: http://badge.fury.io/rb/activerecord-jdbc-adapter
288
- [8]: https://github.com/jruby/activerecord-jdbc-adapter/wiki/Migrating-from-1.2.x-to-1.3.0
290
+ [8]: https://github.com/jruby/activerecord-jdbcsqlserver-adapter
data/Rakefile CHANGED
@@ -1,3 +1,29 @@
1
+ # Common usage
2
+ #
3
+ # rake build:adapters - to build all specific adapter gems and the base gem
4
+ # rake release:do - build:adapters + git tag + push gems
5
+ #
6
+ # Environment variables used by this Rakefile:
7
+ #
8
+ # INCLUDE_JAR_IN_GEM [default task - false, other taks - true]:
9
+ # Note: This is something you should not normally have to set.
10
+ # For local development we always will end up including the jar file
11
+ # in any task which generates our main gem. The wrinkle to this
12
+ # is when we do a custom github link in bundler:
13
+ #
14
+ # gem 'ar-jdbc', github: '...'
15
+ #
16
+ # Because we stopped committing a .jar file for every change and so when
17
+ # we include a gem like this it clones the repository and does a default
18
+ # build in rake. This in turn will end up forcing a compile to generate
19
+ # that jar (similar to how c-extensions compile at the time of install).
20
+ # For shipped gems we do include the jar so that people do not require
21
+ # this compile step.
22
+ #
23
+ # NOOP [release:do - false]
24
+ #
25
+ # No commands or gem pushing during a release.
26
+
1
27
  require 'rake/clean'
2
28
 
3
29
  CLEAN.include 'derby*', 'test.db.*', '*test.sqlite3', 'test/reports'
@@ -38,7 +64,8 @@ rake = lambda { |task| ruby "-S", "rake", task }
38
64
 
39
65
  desc "Build #{gem_name} gem into the pkg directory."
40
66
  task :build => :jar do
41
- sh("RELEASE=#{ENV['RELEASE']} gem build -V '#{gemspec_path}'") do
67
+ include_jar = ENV['INCLUDE_JAR_IN_GEM'] || 'true'
68
+ sh("INCLUDE_JAR_IN_GEM=#{include_jar} gem build -V '#{gemspec_path}'") do
42
69
  gem_path = built_gem_path.call
43
70
  file_name = File.basename(gem_path)
44
71
  FileUtils.mkdir_p(File.join(base_dir, 'pkg'))
@@ -58,7 +85,6 @@ end
58
85
 
59
86
  desc "Releasing AR-JDBC gems (use NOOP=true to disable gem pushing)"
60
87
  task 'release:do' do
61
- ENV['RELEASE'] = 'true' # so that .gemspec is built with adapter_java.jar
62
88
  Rake::Task['build'].invoke
63
89
  # Rake::Task['build:adapters'].invoke
64
90
 
@@ -257,7 +283,7 @@ if defined? JRUBY_VERSION
257
283
  begin
258
284
  require 'arjdbc/version'
259
285
  rescue LoadError
260
- path = File.expand_path('../lib', File.dirname(__FILE__))
286
+ path = File.expand_path('lib', File.dirname(__FILE__))
261
287
  unless $LOAD_PATH.include?(path)
262
288
  $LOAD_PATH << path; retry
263
289
  end
@@ -297,7 +323,7 @@ if defined? JRUBY_VERSION
297
323
  # class_files.gsub!('$', '\$') unless windows?
298
324
  # args = class_files.map { |path| [ "-C #{classes_dir}", path ] }.flatten
299
325
 
300
- if ENV['RELEASE'] == 'true'; require 'tempfile'
326
+ if ENV['INCLUDE_JAR_IN_GEM'] == 'true'; require 'tempfile'
301
327
  manifest = "Built-Time: #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S')}\n"
302
328
  manifest += "Built-JRuby: #{JRUBY_VERSION}\n"
303
329
  manifest += "Specification-Title: ActiveRecord-JDBC\n"
@@ -27,7 +27,7 @@ Gem::Specification.new do |gem|
27
27
  reject { |f| f =~ /^(gemfiles)/ } # no tests - no Gemfile_s appraised ...
28
28
  gem.files += ['lib/arjdbc/jdbc/adapter_java.jar'] #if ENV['RELEASE'].eql?('true')
29
29
 
30
- if ENV['RELEASE'] != 'true' # @see Rakefile
30
+ if ENV['INCLUDE_JAR_IN_GEM'] != 'true' # @see Rakefile
31
31
  gem.extensions << 'Rakefile' # to support auto-building .jar with :git paths
32
32
 
33
33
  #gem.add_runtime_dependency 'jar-dependencies', '~> 0.1' # development not enough!
@@ -29,7 +29,7 @@ Gem::Specification.new do |gem|
29
29
  reject { |f| f =~ /^(gemfiles)/ } # no tests - no Gemfile_s appraised ...
30
30
  gem.files += ['lib/arjdbc/jdbc/adapter_java.jar'] #if ENV['RELEASE'].eql?('true')
31
31
 
32
- if ENV['RELEASE'] != 'true' # @see Rakefile
32
+ if ENV['INCLUDE_JAR_IN_GEM'] != 'true' # @see Rakefile
33
33
  gem.extensions << 'Rakefile' # to support auto-building .jar with :git paths
34
34
 
35
35
  #gem.add_runtime_dependency 'jar-dependencies', '~> 0.1' # development not enough!
@@ -56,7 +56,7 @@ module ArJdbc
56
56
  case e
57
57
  when SystemExit, SignalException, NoMemoryError then e
58
58
  when ActiveModel::RangeError, TypeError, RuntimeError then e
59
- else ActiveRecord::StatementInvalid.new(message)
59
+ else super
60
60
  end
61
61
  end
62
62
 
@@ -10,6 +10,8 @@ module ArJdbc
10
10
  NO_BINDS = [].freeze
11
11
 
12
12
  def exec_insert(sql, name = nil, binds = NO_BINDS, pk = nil, sequence_name = nil)
13
+ binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
14
+
13
15
  if without_prepared_statement?(binds)
14
16
  log(sql, name) { @connection.execute_insert(sql) }
15
17
  else
@@ -22,6 +24,8 @@ module ArJdbc
22
24
  # It appears that at this point (AR 5.0) "prepare" should only ever be true
23
25
  # if prepared statements are enabled
24
26
  def exec_query(sql, name = nil, binds = NO_BINDS, prepare: false)
27
+ binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
28
+
25
29
  if without_prepared_statement?(binds)
26
30
  log(sql, name) { @connection.execute_query(sql) }
27
31
  else
@@ -34,6 +38,8 @@ module ArJdbc
34
38
  end
35
39
 
36
40
  def exec_update(sql, name = nil, binds = NO_BINDS)
41
+ binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
42
+
37
43
  if without_prepared_statement?(binds)
38
44
  log(sql, name) { @connection.execute_update(sql) }
39
45
  else
@@ -42,25 +48,6 @@ module ArJdbc
42
48
  end
43
49
  alias :exec_delete :exec_update
44
50
 
45
- # overridden to support legacy binds
46
- def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
47
- binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
48
- super
49
- end
50
- alias create insert
51
-
52
- # overridden to support legacy binds
53
- def update(arel, name = nil, binds = [])
54
- binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
55
- super
56
- end
57
-
58
- # overridden to support legacy binds
59
- def delete(arel, name = nil, binds = [])
60
- binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
61
- super
62
- end
63
-
64
51
  def execute(sql, name = nil)
65
52
  log(sql, name) { @connection.execute(sql) }
66
53
  end
data/lib/arjdbc/jdbc.rb CHANGED
@@ -2,12 +2,8 @@ require 'active_support/deprecation'
2
2
 
3
3
  module ArJdbc
4
4
 
5
- # @private
6
- AR40 = ::ActiveRecord::VERSION::MAJOR > 3
7
5
  # @private
8
6
  AR42 = ::ActiveRecord::VERSION::STRING >= '4.2'
9
- # @private
10
- AR50 = ::ActiveRecord::VERSION::MAJOR > 4
11
7
 
12
8
  # @private
13
9
  AR52 = ::ActiveRecord::VERSION::STRING >= '5.2'
@@ -28,17 +28,13 @@ module ActiveRecord
28
28
 
29
29
  if ArJdbc::AR52
30
30
  # undefined method `cast' for #<ActiveRecord::ConnectionAdapters::SqlTypeMetadata> on AR52
31
- elsif ArJdbc::AR50
31
+ else
32
32
  default = args[0].cast(default)
33
33
 
34
34
  sql_type = args.delete_at(1)
35
35
  type = args.delete_at(0)
36
36
 
37
37
  args.unshift(SqlTypeMetadata.new(:sql_type => sql_type, :type => type))
38
- elsif ArJdbc::AR42
39
- default = args[0].type_cast_from_database(default)
40
- else
41
- default = default_value(default)
42
38
  end
43
39
 
44
40
  # super <= 4.1: (name, default, sql_type = nil, null = true)
@@ -216,21 +216,11 @@ module ActiveRecord
216
216
  end
217
217
 
218
218
  def configure_connection
219
- execute("SET LOCK_TIMEOUT #{lock_timeout}")
219
+ # Here goes initial settings per connection
220
220
 
221
221
  set_session_transaction_isolation
222
222
  end
223
223
 
224
- def lock_timeout
225
- timeout = config[:lock_timeout].to_i
226
-
227
- if timeout.positive?
228
- timeout
229
- else
230
- 5_000
231
- end
232
- end
233
-
234
224
  def set_session_transaction_isolation
235
225
  isolation_level = config[:transaction_isolation]
236
226
 
@@ -60,6 +60,7 @@ ArJdbc::ConnectionMethods.module_eval do
60
60
  config[:host] ||= 'localhost'
61
61
  config[:driver] ||= 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
62
62
  config[:connection_alive_sql] ||= 'SELECT 1'
63
+ config[:lock_timeout] ||= 5000
63
64
 
64
65
  config[:url] ||= begin
65
66
  url = "jdbc:sqlserver://#{config[:host]}"
@@ -67,6 +68,8 @@ ArJdbc::ConnectionMethods.module_eval do
67
68
  url << "databaseName=#{config[:database]};" if config[:database]
68
69
  url << "instanceName=#{config[:instance]};" if config[:instance]
69
70
  url << "sendTimeAsDatetime=#{config[:send_time_as_datetime] || false};"
71
+ url << "loginTimeout=#{config[:login_timeout].to_i};" if config[:login_timeout]
72
+ url << "lockTimeout=#{config[:lock_timeout].to_i};"
70
73
  app = config[:appname] || config[:application]
71
74
  url << "applicationName=#{app};" if app
72
75
  isc = config[:integrated_security] # Win only - needs sqljdbc_auth.dll
@@ -10,11 +10,9 @@ ArJdbc::ConnectionMethods.module_eval do
10
10
 
11
11
  return jndi_connection(config) if jndi_config?(config)
12
12
 
13
- driver = config[:driver] ||=
14
- defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
15
-
16
- mysql_driver = driver.start_with?('com.mysql.')
17
- mariadb_driver = ! mysql_driver && driver.start_with?('org.mariadb.')
13
+ driver = config[:driver]
14
+ mysql_driver = driver.nil? || driver.to_s.start_with?('com.mysql.')
15
+ mariadb_driver = ! mysql_driver && driver.to_s.start_with?('org.mariadb.')
18
16
 
19
17
  begin
20
18
  require 'jdbc/mysql'
@@ -22,6 +20,11 @@ ArJdbc::ConnectionMethods.module_eval do
22
20
  rescue LoadError # assuming driver.jar is on the class-path
23
21
  end if mysql_driver
24
22
 
23
+ if driver.nil?
24
+ config[:driver] ||=
25
+ defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
26
+ end
27
+
25
28
  config[:username] = 'root' unless config.key?(:username)
26
29
  # jdbc:mysql://[host][,failoverhost...][:port]/[database]
27
30
  # - if the host name is not specified, it defaults to 127.0.0.1
@@ -36,7 +39,8 @@ ArJdbc::ConnectionMethods.module_eval do
36
39
 
37
40
  properties = ( config[:properties] ||= {} )
38
41
  if mysql_driver
39
- properties['zeroDateTimeBehavior'] ||= 'convertToNull'
42
+ properties['zeroDateTimeBehavior'] ||=
43
+ config[:driver].to_s.start_with?('com.mysql.cj.') ? 'CONVERT_TO_NULL' : 'convertToNull'
40
44
  properties['jdbcCompliantTruncation'] ||= false
41
45
  # NOTE: this is "better" than passing what users are used to set on MRI
42
46
  # e.g. 'utf8mb4' will fail cause the driver will check for a Java charset
@@ -108,7 +112,8 @@ ArJdbc::ConnectionMethods.module_eval do
108
112
  rescue LoadError # assuming driver.jar is on the class-path
109
113
  end
110
114
 
111
- config[:driver] ||= 'org.mariadb.jdbc.Driver'
115
+ config[:driver] ||=
116
+ defined?(::Jdbc::MariaDB.driver_name) ? ::Jdbc::MariaDB.driver_name : 'org.mariadb.jdbc.Driver'
112
117
 
113
118
  mysql_connection(config)
114
119
  end
@@ -15,7 +15,7 @@ module ArJdbc
15
15
  end
16
16
 
17
17
  # Extracts the value from a PostgreSQL column default definition.
18
- def extract_value_from_default(default) # :nodoc:
18
+ def extract_value_from_default(default)
19
19
  case default
20
20
  # Quoted types
21
21
  when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
@@ -41,10 +41,13 @@ module ArJdbc
41
41
  end
42
42
  end
43
43
 
44
- def extract_default_function(default_value, default) # :nodoc:
45
- default if ! default_value && ( %r{\w+\(.*\)|\(.*\)::\w+} === default )
44
+ def extract_default_function(default_value, default)
45
+ default if has_default_function?(default_value, default)
46
46
  end
47
47
 
48
+ def has_default_function?(default_value, default)
49
+ !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP} === default
50
+ end
48
51
  end
49
52
 
50
53
  end
@@ -16,7 +16,8 @@ ArJdbc::ConnectionMethods.module_eval do
16
16
  ::Jdbc::Postgres.load_driver(:require) if defined?(::Jdbc::Postgres.load_driver)
17
17
  rescue LoadError # assuming driver.jar is on the class-path
18
18
  end
19
- driver = config[:driver] ||= 'org.postgresql.Driver'
19
+ driver = (config[:driver] ||=
20
+ defined?(::Jdbc::Postgres.driver_name) ? ::Jdbc::Postgres.driver_name : 'org.postgresql.Driver')
20
21
 
21
22
  host = config[:host] ||= ( config[:hostaddr] || ENV['PGHOST'] || 'localhost' )
22
23
  port = config[:port] ||= ( ENV['PGPORT'] || 5432 )
@@ -1,3 +1,3 @@
1
1
  module ArJdbc
2
- VERSION = '52.2.3'
2
+ VERSION = '52.4.0'
3
3
  end
data/rakelib/02-test.rake CHANGED
@@ -76,8 +76,6 @@ end
76
76
  test_task_for adapter, :desc => "Run tests against #{adapter} (ensure driver is on class-path)"
77
77
  end
78
78
 
79
- #test_task_for :MSSQL, :name => 'test_sqlserver', :driver => nil, :database_name => 'MS-SQL using SQLJDBC'
80
-
81
79
  test_task_for :AS400, :desc => "Run tests against AS400 (DB2) (ensure driver is on class-path)",
82
80
  :files => FileList["test/db2*_test.rb"] + FileList["test/db/db2/*_test.rb"]
83
81
 
@@ -487,7 +487,7 @@ public class RubyJdbcConnection extends RubyObject {
487
487
  savepoint = ((IRubyObject) savepoint).toJava(Savepoint.class);
488
488
  }
489
489
 
490
- connection.releaseSavepoint((Savepoint) savepoint);
490
+ releaseSavepoint(connection, (Savepoint) savepoint);
491
491
  return context.nil;
492
492
  }
493
493
  catch (SQLException e) {
@@ -495,6 +495,11 @@ public class RubyJdbcConnection extends RubyObject {
495
495
  }
496
496
  }
497
497
 
498
+ // MSSQL doesn't support releasing savepoints so we make it possible to override the actual release action
499
+ protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
500
+ connection.releaseSavepoint(savepoint);
501
+ }
502
+
498
503
  protected static RuntimeException newSavepointNotSetError(final ThreadContext context, final IRubyObject name, final String op) {
499
504
  RubyClass StatementInvalid = ActiveRecord(context).getClass("StatementInvalid");
500
505
  return context.runtime.newRaiseException(StatementInvalid, "could not " + op + " savepoint: '" + name + "' (not set)");
@@ -3928,6 +3933,12 @@ public class RubyJdbcConnection extends RubyObject {
3928
3933
  }
3929
3934
  }
3930
3935
 
3936
+ public static void debugMessage(final ThreadContext context, final IRubyObject obj) {
3937
+ if ( isDebug(context.runtime) ) {
3938
+ debugMessage(context.runtime, obj.callMethod(context, "inspect"));
3939
+ }
3940
+ }
3941
+
3931
3942
  public static void debugMessage(final Ruby runtime, final String msg, final Object e) {
3932
3943
  if ( isDebug(runtime) ) {
3933
3944
  final PrintStream out = runtime != null ? runtime.getOut() : System.out;
@@ -29,22 +29,33 @@ import arjdbc.jdbc.Callable;
29
29
  import arjdbc.jdbc.RubyJdbcConnection;
30
30
  import arjdbc.util.DateTimeUtils;
31
31
 
32
+ import java.lang.reflect.InvocationTargetException;
33
+ import java.lang.reflect.Method;
32
34
  import java.sql.Connection;
33
35
  import java.sql.DatabaseMetaData;
36
+ import java.sql.Date;
34
37
  import java.sql.PreparedStatement;
35
38
  import java.sql.ResultSet;
36
39
  import java.sql.Savepoint;
40
+ import java.sql.Statement;
37
41
  import java.sql.SQLException;
38
- import java.sql.Types;
39
42
  import java.sql.Timestamp;
43
+ import java.sql.Types;
40
44
  import java.util.Locale;
45
+ import java.util.ArrayList;
46
+ import java.util.HashMap;
47
+ import java.util.List;
48
+ import java.util.Map;
41
49
 
50
+ import org.joda.time.DateTime;
51
+ import org.joda.time.DateTimeZone;
42
52
  import org.jruby.Ruby;
43
53
  import org.jruby.RubyArray;
44
54
  import org.jruby.RubyBoolean;
45
55
  import org.jruby.RubyClass;
46
56
  import org.jruby.RubyString;
47
57
  import org.jruby.RubySymbol;
58
+ import org.jruby.RubyTime;
48
59
  import org.jruby.anno.JRubyMethod;
49
60
  import org.jruby.runtime.ObjectAllocator;
50
61
  import org.jruby.runtime.ThreadContext;
@@ -58,6 +69,54 @@ import org.jruby.util.ByteList;
58
69
  public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
59
70
  private static final long serialVersionUID = -745716565005219263L;
60
71
 
72
+ private static final int DATETIMEOFFSET_TYPE;
73
+ private static final Method DateTimeOffsetGetMinutesOffsetMethod;
74
+ private static final Method DateTimeOffsetGetTimestampMethod;
75
+ private static final Method DateTimeOffsetValueOfMethod;
76
+ private static final Method PreparedStatementSetDateTimeOffsetMethod;
77
+
78
+ private static final Map<String, Integer> MSSQL_JDBC_TYPE_FOR = new HashMap<String, Integer>(32, 1);
79
+ static {
80
+
81
+ Class<?> DateTimeOffset;
82
+ Class<?> MssqlPreparedStatement;
83
+ Class<?> MssqlTypes;
84
+ try {
85
+ DateTimeOffset = Class.forName("microsoft.sql.DateTimeOffset");
86
+ MssqlPreparedStatement = Class.forName("com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement");
87
+ MssqlTypes = Class.forName("microsoft.sql.Types");
88
+ } catch (ClassNotFoundException e) {
89
+ System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
90
+ throw new RuntimeException("You must require the Microsoft JDBC driver to use this gem");
91
+ }
92
+
93
+ try {
94
+ DATETIMEOFFSET_TYPE = MssqlTypes.getField("DATETIMEOFFSET").getInt(null);
95
+ DateTimeOffsetGetMinutesOffsetMethod = DateTimeOffset.getDeclaredMethod("getMinutesOffset");
96
+ DateTimeOffsetGetTimestampMethod = DateTimeOffset.getDeclaredMethod("getTimestamp");
97
+
98
+ Class<?>[] valueOfArgTypes = { Timestamp.class, int.class };
99
+ DateTimeOffsetValueOfMethod = DateTimeOffset.getDeclaredMethod("valueOf", valueOfArgTypes);
100
+
101
+ Class<?>[] setOffsetArgTypes = { int.class, DateTimeOffset };
102
+ PreparedStatementSetDateTimeOffsetMethod = MssqlPreparedStatement.getDeclaredMethod("setDateTimeOffset", setOffsetArgTypes);
103
+ } catch (Exception e) {
104
+ System.err.println("You must require the Microsoft JDBC driver to use this gem"); // The exception doesn't bubble when ruby is initializing
105
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
106
+ }
107
+
108
+ MSSQL_JDBC_TYPE_FOR.put("binary_basic", Types.BINARY);
109
+ MSSQL_JDBC_TYPE_FOR.put("image", Types.BINARY);
110
+ MSSQL_JDBC_TYPE_FOR.put("datetimeoffset", DATETIMEOFFSET_TYPE);
111
+ MSSQL_JDBC_TYPE_FOR.put("money", Types.DECIMAL);
112
+ MSSQL_JDBC_TYPE_FOR.put("smalldatetime", Types.TIMESTAMP);
113
+ MSSQL_JDBC_TYPE_FOR.put("smallmoney", Types.DECIMAL);
114
+ MSSQL_JDBC_TYPE_FOR.put("ss_timestamp", Types.BINARY);
115
+ MSSQL_JDBC_TYPE_FOR.put("text_basic", Types.LONGVARCHAR);
116
+ MSSQL_JDBC_TYPE_FOR.put("uuid", Types.CHAR);
117
+ MSSQL_JDBC_TYPE_FOR.put("varchar_max", Types.VARCHAR);
118
+ }
119
+
61
120
  public MSSQLRubyJdbcConnection(Ruby runtime, RubyClass metaClass) {
62
121
  super(runtime, metaClass);
63
122
  }
@@ -89,6 +148,125 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
89
148
  return context.runtime.newBoolean( startsWithIgnoreCase(sqlBytes, EXEC) );
90
149
  }
91
150
 
151
+ // Support multiple result sets for mssql
152
+ @Override
153
+ @JRubyMethod(name = "execute", required = 1)
154
+ public IRubyObject execute(final ThreadContext context, final IRubyObject sql) {
155
+ final String query = sqlString(sql);
156
+ return withConnection(context, new Callable<IRubyObject>() {
157
+ public IRubyObject call(final Connection connection) throws SQLException {
158
+ Statement statement = null;
159
+ try {
160
+ statement = createStatement(context, connection);
161
+
162
+ // For DBs that do support multiple statements, lets return the last result set
163
+ // to be consistent with AR
164
+ boolean hasResultSet = doExecute(statement, query);
165
+ int updateCount = statement.getUpdateCount();
166
+
167
+ final List<IRubyObject> results = new ArrayList<IRubyObject>();
168
+ ResultSet resultSet;
169
+
170
+ while (hasResultSet || updateCount != -1) {
171
+
172
+ if (hasResultSet) {
173
+ resultSet = statement.getResultSet();
174
+
175
+ // Unfortunately the result set gets closed when getMoreResults()
176
+ // is called, so we have to process the result sets as we get them
177
+ // this shouldn't be an issue in most cases since we're only getting 1 result set anyways
178
+ results.add(mapExecuteResult(context, connection, resultSet));
179
+ } else {
180
+ results.add(context.runtime.newFixnum(updateCount));
181
+ }
182
+
183
+ // Check to see if there is another result set
184
+ hasResultSet = statement.getMoreResults();
185
+ updateCount = statement.getUpdateCount();
186
+ }
187
+
188
+ if (results.size() == 0) {
189
+ return context.nil; // If no results, return nil
190
+ } else if (results.size() == 1) {
191
+ return results.get(0);
192
+ } else {
193
+ return context.runtime.newArray(results);
194
+ }
195
+
196
+ } catch (final SQLException e) {
197
+ debugErrorSQL(context, query);
198
+ throw e;
199
+ } finally {
200
+ close(statement);
201
+ }
202
+ }
203
+ });
204
+ }
205
+
206
+ @Override
207
+ protected Integer jdbcTypeFor(final String type) {
208
+
209
+ Integer typeValue = MSSQL_JDBC_TYPE_FOR.get(type);
210
+
211
+ if ( typeValue != null ) {
212
+ return typeValue;
213
+ }
214
+
215
+ return super.jdbcTypeFor(type);
216
+ }
217
+
218
+ // Datetimeoffset values also make it into here
219
+ @Override
220
+ protected void setStringParameter(final ThreadContext context, final Connection connection,
221
+ final PreparedStatement statement, final int index, final IRubyObject value,
222
+ final IRubyObject attribute, final int type) throws SQLException {
223
+
224
+ // datetimeoffset values also make it in here
225
+ if (type == DATETIMEOFFSET_TYPE) {
226
+
227
+ Object dto = convertToDateTimeOffset(context, value);
228
+
229
+ try {
230
+
231
+ Object[] setStatementArgs = { index, dto };
232
+ PreparedStatementSetDateTimeOffsetMethod.invoke(statement, setStatementArgs);
233
+
234
+ } catch (IllegalAccessException e) {
235
+ debugMessage(context.runtime, e.getMessage());
236
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
237
+ } catch (InvocationTargetException e) {
238
+ debugMessage(context.runtime, e.getMessage());
239
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
240
+ }
241
+
242
+ return;
243
+ }
244
+ super.setStringParameter(context, connection, statement, index, value, attribute, type);
245
+ }
246
+
247
+ private Object convertToDateTimeOffset(final ThreadContext context, final IRubyObject value) {
248
+
249
+ RubyTime time = (RubyTime) value;
250
+ DateTime dt = time.getDateTime();
251
+ Timestamp timestamp = new Timestamp(dt.getMillis());
252
+ timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
253
+ int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
254
+
255
+ try {
256
+
257
+ Object[] dtoArgs = { timestamp, offsetMinutes };
258
+ return DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
259
+
260
+ } catch (IllegalAccessException e) {
261
+ debugMessage(context.runtime, e.getMessage());
262
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
263
+ } catch (InvocationTargetException e) {
264
+ debugMessage(context.runtime, e.getMessage());
265
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
266
+ }
267
+ }
268
+
269
+
92
270
  @Override
93
271
  protected RubyArray mapTables(final ThreadContext context, final Connection connection,
94
272
  final String catalog, final String schemaPattern, final String tablePattern,
@@ -305,29 +483,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
305
483
  statement.setObject(index, timeStr, Types.NVARCHAR);
306
484
  }
307
485
 
308
- // Overrides the method in parent, we only remove the savepoint
309
- // from the getSavepoints Map
310
- @JRubyMethod(name = "release_savepoint", required = 1)
311
- public IRubyObject release_savepoint(final ThreadContext context, final IRubyObject name) {
312
- if (name == context.nil) throw context.runtime.newArgumentError("nil savepoint name given");
313
-
314
- final Connection connection = getConnection(true);
315
-
316
- Object savepoint = getSavepoints(context).remove(name);
317
-
318
- if (savepoint == null) throw newSavepointNotSetError(context, name, "release");
319
-
320
- // NOTE: RubyHash.remove does not convert to Java as get does :
321
- if (!(savepoint instanceof Savepoint)) {
322
- savepoint = ((IRubyObject) savepoint).toJava(Savepoint.class);
323
- }
324
-
325
- // The 'releaseSavepoint' method is not currently supported
326
- // by the Microsoft SQL Server JDBC Driver
327
- // connection.releaseSavepoint((Savepoint) savepoint);
328
- return context.nil;
329
- }
330
-
331
486
  //----------------------------------------------------------------
332
487
  // read_uncommitted: "READ UNCOMMITTED",
333
488
  // read_committed: "READ COMMITTED",
@@ -453,16 +608,64 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
453
608
 
454
609
  /**
455
610
  * Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
611
+ * Also handle datetimeoffset values here
456
612
  */
457
613
  @Override
458
614
  protected IRubyObject jdbcToRuby(
459
615
  final ThreadContext context, final Ruby runtime,
460
616
  final int column, int type, final ResultSet resultSet)
461
617
  throws SQLException {
462
- if ( type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR ) type = Types.CLOB;
618
+
619
+ if (type == DATETIMEOFFSET_TYPE) {
620
+
621
+ Object dto = resultSet.getObject(column); // Returns a microsoft.sql.DateTimeOffset
622
+
623
+ if (dto == null) return context.nil;
624
+
625
+ try {
626
+
627
+ int minutes = (int) DateTimeOffsetGetMinutesOffsetMethod.invoke(dto);
628
+ DateTimeZone zone = DateTimeZone.forOffsetHoursMinutes(minutes / 60, minutes % 60);
629
+ Timestamp ts = (Timestamp) DateTimeOffsetGetTimestampMethod.invoke(dto);
630
+
631
+ int nanos = ts.getNanos(); // max 999-999-999
632
+ nanos = nanos % 1000000;
633
+
634
+ // We have to do this differently than the newTime helper because the Timestamp loses its zone information when passed around
635
+ DateTime dateTime = new DateTime(ts.getTime(), zone);
636
+ return RubyTime.newTime(context.runtime, dateTime, nanos);
637
+
638
+ } catch (IllegalAccessException e) {
639
+ debugMessage(runtime, e.getMessage());
640
+ return context.nil;
641
+ } catch (InvocationTargetException e) {
642
+ debugMessage(runtime, e.getMessage());
643
+ return context.nil;
644
+ }
645
+ }
646
+
647
+ if (type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR) type = Types.CLOB;
463
648
  return super.jdbcToRuby(context, runtime, column, type, resultSet);
464
649
  }
465
650
 
651
+ /**
652
+ * Converts a JDBC date object to a Ruby date by referencing Date#civil
653
+ * @param context current thread context
654
+ * @param resultSet the jdbc result set to pull the value from
655
+ * @param index the index of the column to convert
656
+ * @return RubyNil if NULL or RubyDate if there is a value
657
+ * @throws SQLException if it fails to retrieve the value from the result set
658
+ */
659
+ @Override
660
+ protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
661
+
662
+ final Date value = resultSet.getDate(index);
663
+
664
+ if (value == null) return context.nil;
665
+
666
+ return DateTimeUtils.newDate(context, value);
667
+ }
668
+
466
669
  @Override
467
670
  protected ColumnData[] extractColumns(final ThreadContext context,
468
671
  final Connection connection, final ResultSet resultSet,
@@ -492,19 +695,9 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
492
695
  return columns;
493
696
  }
494
697
 
495
- // internal helper not meant as a "public" API - used in one place thus every
496
- @JRubyMethod(name = "jtds_driver?")
497
- public RubyBoolean jtds_driver_p(final ThreadContext context) throws SQLException {
498
- // "jTDS Type 4 JDBC Driver for MS SQL Server and Sybase"
499
- // SQLJDBC: "Microsoft JDBC Driver 4.0 for SQL Server"
500
- return withConnection(context, new Callable<RubyBoolean>() {
501
- // NOTE: only used in one place for now (on release_savepoint) ...
502
- // might get optimized to only happen once since driver won't change
503
- public RubyBoolean call(final Connection connection) throws SQLException {
504
- final String driver = connection.getMetaData().getDriverName();
505
- return context.getRuntime().newBoolean( driver.indexOf("jTDS") >= 0 );
506
- }
507
- });
698
+ @Override
699
+ protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
700
+ // MSSQL doesn't support releasing savepoints
508
701
  }
509
702
 
510
703
  }
@@ -51,6 +51,9 @@ import static arjdbc.util.StringHelper.decByte;
51
51
  * @author kares
52
52
  */
53
53
  public abstract class DateTimeUtils {
54
+
55
+ private static final GJChronology CHRONO_ITALY_UTC = GJChronology.getInstance(DateTimeZone.UTC);
56
+
54
57
  public static RubyTime toTime(final ThreadContext context, final IRubyObject value) {
55
58
  if (!(value instanceof RubyTime)) { // unlikely
56
59
  return (RubyTime) TypeConverter.convertToTypeWithCheck(value, context.runtime.getTime(), "to_time");
@@ -218,9 +221,11 @@ public abstract class DateTimeUtils {
218
221
  final int hours = time.getHours();
219
222
  final int minutes = time.getMinutes();
220
223
  final int seconds = time.getSeconds();
221
- final int nanos = time.getNanos(); // max 999-999-999
224
+ int nanos = time.getNanos(); // max 999-999-999
225
+ final int millis = nanos / 1000000;
226
+ nanos = nanos % 1000000;
222
227
 
223
- DateTime dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, defaultZone);
228
+ DateTime dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, millis, defaultZone);
224
229
  return RubyTime.newTime(context.runtime, dateTime, nanos);
225
230
  }
226
231
 
@@ -233,9 +238,11 @@ public abstract class DateTimeUtils {
233
238
  final int hours = timestamp.getHours();
234
239
  final int minutes = timestamp.getMinutes();
235
240
  final int seconds = timestamp.getSeconds();
236
- final int nanos = timestamp.getNanos(); // max 999-999-999
241
+ int nanos = timestamp.getNanos(); // max 999-999-999
242
+ final int millis = nanos / 1000000;
243
+ nanos = nanos % 1000000;
237
244
 
238
- DateTime dateTime = new DateTime(year, month, day, hours, minutes, seconds, 0, defaultZone);
245
+ DateTime dateTime = new DateTime(year, month, day, hours, minutes, seconds, millis, defaultZone);
239
246
  return RubyTime.newTime(context.runtime, dateTime, nanos);
240
247
  }
241
248
 
@@ -260,6 +267,15 @@ public abstract class DateTimeUtils {
260
267
  return newDate(context, year, month, day, ISOChronology.getInstance(zone));
261
268
  }
262
269
 
270
+ @SuppressWarnings("deprecation")
271
+ public static IRubyObject newDate(final ThreadContext context, final Date date) {
272
+ final int year = date.getYear() + 1900;
273
+ final int month = date.getMonth() + 1;
274
+ final int day = date.getDate();
275
+
276
+ return newDate(context, year, month, day, CHRONO_ITALY_UTC);
277
+ }
278
+
263
279
  // @Deprecated
264
280
  public static Timestamp convertToTimestamp(final RubyFloat value) {
265
281
  final Timestamp timestamp = new Timestamp(value.getLongValue() * 1000); // millis
@@ -332,9 +348,7 @@ public abstract class DateTimeUtils {
332
348
 
333
349
  if ( bcEra ) year = -1 * year; // no + 1 since we use GJChronology
334
350
 
335
- DateTime dateTime = new DateTime(year, month, day, 0, 0, 0, GJChronology.getInstance(defaultZone));
336
- final Ruby runtime = context.runtime;
337
- return runtime.getClass("Date").newInstance(context, Java.getInstance(runtime, dateTime), Block.NULL_BLOCK);
351
+ return newDate(context, year, month, day, GJChronology.getInstance(defaultZone));
338
352
  }
339
353
 
340
354
  public static IRubyObject parseTime(final ThreadContext context, final CharSequence str, final DateTimeZone defaultZone)
@@ -559,7 +573,7 @@ public abstract class DateTimeUtils {
559
573
  }
560
574
 
561
575
  private static IRubyObject newDate(final ThreadContext context, final int year, final int month, final int day,
562
- final ISOChronology chronology) {
576
+ final Chronology chronology) {
563
577
  // NOTE: JRuby really needs a native date.rb until than its a bit costly going from ...
564
578
  // java.sql.Date -> allocating a DateTime proxy, help a bit by shooting at the internals
565
579
  //
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-jdbc-alt-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 52.2.3
4
+ version: 52.4.0
5
5
  platform: java
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: 2019-09-30 00:00:00.000000000 Z
11
+ date: 2019-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -274,7 +274,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
274
274
  version: '0'
275
275
  requirements: []
276
276
  rubyforge_project:
277
- rubygems_version: 2.7.9
277
+ rubygems_version: 2.7.10
278
278
  signing_key:
279
279
  specification_version: 4
280
280
  summary: ActiveRecord JDBC adapter, for use within JRuby on Rails and SQL Server