activerecord-jdbc-alt-adapter 50.3.4-java → 50.5.0-java

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
  SHA256:
3
- metadata.gz: d9ea9928647cd45dc2732320fe4c7c24089abe52c0fe166a93cb0ce5c7c32e97
4
- data.tar.gz: 6cf8eefe19f595f5f6cdac05ec19401932ceafda7c01e49bab1723cd27646f55
3
+ metadata.gz: e9379d10ca13be5bdfe6b2d9e08f1f0bb90c12b4a87a11a2dbe4122742bd0fd4
4
+ data.tar.gz: 6fbff250cd49c44410829511184f0ef69e4d4aff5b6045da2b4a0d4421dd1387
5
5
  SHA512:
6
- metadata.gz: 4c9e2d2696cdf46120907ab7f43d42993e9cb7e95b1b7cbf4038e9dbf73d1363fe9dd4a09f26bf7e3c19382693346f63e2aa08c6dc6240cfd07a7b08c6167d0d
7
- data.tar.gz: f15fa824f63692f758b4a51a6daae8317dc6e93eff8b387eb54fee0ffb4ae1b9ee2d84191ab0aea3d7a8973efe31157fcc9916a4d9ccd7c5b3cdf15fdcc7538d
6
+ metadata.gz: 69e281f1bdb34d112fca0a78193999b2c8b72e36006e5ac28a11de998f594a314baa156337359fa98b85d8055072a23f1ac1c867a672ffb4b305059332c6e3fa
7
+ data.tar.gz: 2c104450a45f8f84cf740b10ce079fa55e815300ceebcb8d22de91a44e8e33f1758907891e41738b3c9e73289300e3210bd49b7b7032b9de6ad5765ec8d12f17
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.0.7.1" # PS off by default
data/README.md CHANGED
@@ -106,15 +106,16 @@ this adapter should just work.
106
106
  ActiveRecord-JDBC-Adapter (AR-JDBC) is the main database adapter for Rails'
107
107
  *ActiveRecord* component that can be used with [JRuby][0].
108
108
  ActiveRecord-JDBC-Adapter provides full or nearly full support for:
109
- **MySQL**, **PostgreSQL**, **SQLite3**. In the near future there are plans to
110
- add support **MSSQL**. Unless we get more contributions we will not be going
111
- beyond these four adapters. Note that the amount of work needed to get
112
- another adapter is not huge but the amount of testing required to make sure
113
- that adapter continues to work is not something we can do with the resources
114
- we currently have.
109
+ **MySQL**, **PostgreSQL**, **SQLite3** and **MSSQL*** (SQLServer).
115
110
 
116
- For Oracle database users you are encouraged to use
117
- https://github.com/rsim/oracle-enhanced.
111
+ Unless we get more contributions we will not be supporting more adapters.
112
+ Note that the amount of work needed to get another adapter is not huge but
113
+ the amount of testing required to make sure that adapter continues to work
114
+ is not something we can do with the resources we currently have.
115
+
116
+ - for **Oracle** database users you are encouraged to use
117
+ https://github.com/rsim/oracle-enhanced
118
+ - **MSSQL** adapter's gem parts reside in a [separate repository][8]
118
119
 
119
120
  Version **50.x** supports Rails version 5.0.x and it lives on branch 50-stable.
120
121
  Version **51.x** supports Rails version 5.1.x and is currently on master until
@@ -133,6 +134,7 @@ adapters are available:
133
134
  - MySQL (`activerecord-jdbcmysql-adapter`)
134
135
  - PostgreSQL (`activerecord-jdbcpostgresql-adapter`)
135
136
  - SQLite3 (`activerecord-jdbcsqlite3-adapter`)
137
+ - MSSQL (`activerecord-jdbcsqlserver-adapter`)
136
138
 
137
139
  2. If you're generating a new Rails application, use the following command:
138
140
 
@@ -267,4 +269,4 @@ license the database's drivers are licensed. See each driver gem's LICENSE.txt.
267
269
  [5]: https://github.com/jruby/activerecord-jdbc-adapter/wiki
268
270
  [6]: https://webchat.freenode.net/?channels=#jruby
269
271
  [7]: http://badge.fury.io/rb/activerecord-jdbc-adapter
270
- [8]: https://github.com/jruby/activerecord-jdbc-adapter/wiki/Migrating-from-1.2.x-to-1.3.0
272
+ [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!
@@ -28,7 +28,7 @@ Gem::Specification.new do |gem|
28
28
  reject { |f| f =~ /^(gemfiles)/ } # no tests - no Gemfile_s appraised ...
29
29
  gem.files += ['lib/arjdbc/jdbc/adapter_java.jar'] #if ENV['RELEASE'].eql?('true')
30
30
 
31
- if ENV['RELEASE'] != 'true' # @see Rakefile
31
+ if ENV['INCLUDE_JAR_IN_GEM'] != 'true' # @see Rakefile
32
32
  gem.extensions << 'Rakefile' # to support auto-building .jar with :git paths
33
33
 
34
34
  #gem.add_runtime_dependency 'jar-dependencies', '~> 0.1' # development not enough!
@@ -22,7 +22,7 @@ module ArJdbc
22
22
 
23
23
  connection.configure_connection # will call us (maybe)
24
24
  end
25
-
25
+
26
26
  # Retrieve the raw `java.sql.Connection` object.
27
27
  # The unwrap parameter is useful if an attempt to unwrap a pooled (JNDI)
28
28
  # connection should be made - to really return the 'native' JDBC object.
@@ -54,7 +54,7 @@ module ArJdbc
54
54
  case e
55
55
  when SystemExit, SignalException, NoMemoryError then e
56
56
  when ActiveModel::RangeError, TypeError, RuntimeError then e
57
- else ActiveRecord::StatementInvalid.new(message)
57
+ else super
58
58
  end
59
59
  end
60
60
 
@@ -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
Binary file
@@ -26,23 +26,17 @@ module ActiveRecord
26
26
  end
27
27
  end
28
28
 
29
- if ArJdbc::AR50
30
- default = args[0].cast(default)
29
+ default = args[0].cast(default)
31
30
 
32
- sql_type = args.delete_at(1)
33
- type = args.delete_at(0)
31
+ sql_type = args.delete_at(1)
32
+ type = args.delete_at(0)
34
33
 
35
- args.unshift(SqlTypeMetadata.new(:sql_type => sql_type, :type => type))
36
- elsif ArJdbc::AR42
37
- default = args[0].type_cast_from_database(default)
38
- else
39
- default = default_value(default)
40
- end
34
+ args.unshift(SqlTypeMetadata.new(:sql_type => sql_type, :type => type))
41
35
 
42
36
  # super <= 4.1: (name, default, sql_type = nil, null = true)
43
37
  # super >= 4.2: (name, default, cast_type, sql_type = nil, null = true)
44
38
  # super >= 5.0: (name, default, sql_type_metadata = nil, null = true)
45
-
39
+
46
40
  super(name, default, *args)
47
41
  init_column(name, default, *args)
48
42
  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
  class << self
13
9
 
@@ -215,21 +215,11 @@ module ActiveRecord
215
215
  end
216
216
 
217
217
  def configure_connection
218
- execute("SET LOCK_TIMEOUT #{lock_timeout}")
218
+ # Here goes initial settings per connection
219
219
 
220
220
  set_session_transaction_isolation
221
221
  end
222
222
 
223
- def lock_timeout
224
- timeout = config[:lock_timeout].to_i
225
-
226
- if timeout.positive?
227
- timeout
228
- else
229
- 5_000
230
- end
231
- end
232
-
233
223
  def set_session_transaction_isolation
234
224
  isolation_level = config[:transaction_isolation]
235
225
 
@@ -60,12 +60,15 @@ 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]}"
66
67
  url << ( config[:port] ? ":#{config[:port]};" : ';' )
67
68
  url << "databaseName=#{config[:database]};" if config[:database]
68
69
  url << "instanceName=#{config[:instance]};" if config[:instance]
70
+ url << "loginTimeout=#{config[:login_timeout].to_i};" if config[:login_timeout]
71
+ url << "lockTimeout=#{config[:lock_timeout].to_i};"
69
72
  app = config[:appname] || config[:application]
70
73
  url << "applicationName=#{app};" if app
71
74
  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 = '50.3.4'
2
+ VERSION = '50.5.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)");
@@ -3924,6 +3929,12 @@ public class RubyJdbcConnection extends RubyObject {
3924
3929
  }
3925
3930
  }
3926
3931
 
3932
+ public static void debugMessage(final ThreadContext context, final IRubyObject obj) {
3933
+ if ( isDebug(context.runtime) ) {
3934
+ debugMessage(context.runtime, obj.callMethod(context, "inspect"));
3935
+ }
3936
+ }
3937
+
3927
3938
  public static void debugMessage(final Ruby runtime, final String msg, final Object e) {
3928
3939
  if ( isDebug(runtime) ) {
3929
3940
  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
  }
@@ -31,6 +31,7 @@ import java.util.TimeZone;
31
31
  import org.joda.time.Chronology;
32
32
  import org.joda.time.DateTime;
33
33
  import org.joda.time.DateTimeZone;
34
+ import org.joda.time.chrono.GJChronology;
34
35
  import org.joda.time.chrono.ISOChronology;
35
36
  import org.jruby.Ruby;
36
37
  import org.jruby.RubyFloat;
@@ -50,6 +51,9 @@ import static arjdbc.util.StringHelper.decByte;
50
51
  * @author kares
51
52
  */
52
53
  public abstract class DateTimeUtils {
54
+
55
+ private static final GJChronology CHRONO_ITALY_UTC = GJChronology.getInstance(DateTimeZone.UTC);
56
+
53
57
  public static RubyTime toTime(final ThreadContext context, final IRubyObject value) {
54
58
  if (!(value instanceof RubyTime)) { // unlikely
55
59
  return (RubyTime) TypeConverter.convertToTypeWithCheck(value, context.runtime.getTime(), "to_time");
@@ -217,9 +221,11 @@ public abstract class DateTimeUtils {
217
221
  final int hours = time.getHours();
218
222
  final int minutes = time.getMinutes();
219
223
  final int seconds = time.getSeconds();
220
- 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;
221
227
 
222
- DateTime dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, defaultZone);
228
+ DateTime dateTime = new DateTime(2000, 1, 1, hours, minutes, seconds, millis, defaultZone);
223
229
  return RubyTime.newTime(context.runtime, dateTime, nanos);
224
230
  }
225
231
 
@@ -232,9 +238,11 @@ public abstract class DateTimeUtils {
232
238
  final int hours = timestamp.getHours();
233
239
  final int minutes = timestamp.getMinutes();
234
240
  final int seconds = timestamp.getSeconds();
235
- 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;
236
244
 
237
- 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);
238
246
  return RubyTime.newTime(context.runtime, dateTime, nanos);
239
247
  }
240
248
 
@@ -259,6 +267,15 @@ public abstract class DateTimeUtils {
259
267
  return newDate(context, year, month, day, ISOChronology.getInstance(zone));
260
268
  }
261
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
+
262
279
  // @Deprecated
263
280
  public static Timestamp convertToTimestamp(final RubyFloat value) {
264
281
  final Timestamp timestamp = new Timestamp(value.getLongValue() * 1000); // millis
@@ -553,7 +570,7 @@ public abstract class DateTimeUtils {
553
570
  }
554
571
 
555
572
  private static IRubyObject newDate(final ThreadContext context, final int year, final int month, final int day,
556
- final ISOChronology chronology) {
573
+ final Chronology chronology) {
557
574
  // NOTE: JRuby really needs a native date.rb until than its a bit costly going from ...
558
575
  // java.sql.Date -> allocating a DateTime proxy, help a bit by shooting at the internals
559
576
  //
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: 50.3.4
4
+ version: 50.5.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
@@ -263,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
263
  version: '0'
264
264
  requirements: []
265
265
  rubyforge_project:
266
- rubygems_version: 2.7.9
266
+ rubygems_version: 2.7.10
267
267
  signing_key:
268
268
  specification_version: 4
269
269
  summary: ActiveRecord JDBC adapter, for use within JRuby on Rails and SQL Server