activerecord-jdbc-alt-adapter 50.3.2-java → 50.7.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: '09bfb6bcb1adba66ba35e7215833ce8983feb04de74c0e2daf5db33ad82cbb0c'
4
- data.tar.gz: b85e0d8b6fa55c656099c9ee04274cecf4504a76192c620037ad3cee1c295481
3
+ metadata.gz: fccd81ff713725fd656c08d96bbbd658a7443a7260fa722e6d08bc601c093a94
4
+ data.tar.gz: 9729f0e2efe3eed4b5b2b191a773fc0dc1ce0ab74c057755df8b4b77a3ad9a22
5
5
  SHA512:
6
- metadata.gz: 68de6bc73dc2bf00af195f9329c0c4af4272a13fa42fcc01a6d24827ce52ec51d23db63828cd8e9e32e228c499e0e89261f375ef49fedf8d6e27124945848bf7
7
- data.tar.gz: e89361d96b9ea5ad3a2f24d3def7df79c2150973b36fce49148344467ca38a92acba925608a965a4db947072e2014864ce717cc2d7a23fece644c42455d0a1d4
6
+ metadata.gz: 2542dd2750603e0e4fa7a0e550cacda950548ed65d8a95ef6e8c2e060fe4a771a2940dbefb78eefed46d572dcd8f467b96a041a5e62ee6df1dc3490db96f977c
7
+ data.tar.gz: c3d55ae33d32a70f1f666c1385956278417bf0332bd455c34326af36647a9163ed72a0317a4707398d43c7c48d9287ce911828e61bb588bd58ef47693df410dc
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  *.tgz
3
3
  *~
4
4
  *.log
5
+ /log/
5
6
  patches*
6
7
  *#
7
8
  TAGS
@@ -33,3 +34,5 @@ activerecord-jdbc.iml
33
34
  lib/arjdbc/jdbc/adapter_java.jar
34
35
  .jrubyrc
35
36
  tags
37
+ .ruby-version
38
+ pik.sh
@@ -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/Gemfile CHANGED
@@ -49,7 +49,7 @@ group :test do
49
49
 
50
50
  gem 'bcrypt', '~> 3.1.11', require: false
51
51
  gem 'builder', require: false
52
- gem 'jdbc-mssql', '~> 0.6.0', require: nil
52
+ gem 'jdbc-mssql', '~> 0.7.0', require: nil
53
53
  end
54
54
 
55
55
  group :rails do
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!
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
11
11
  gem.license = 'BSD-2-Clause'
12
12
  gem.summary = 'ActiveRecord JDBC adapter, for use within JRuby on Rails and SQL Server'
13
13
  gem.description = "" <<
14
- "Fork of the ActiveRecord JDBC adapter with support for SQL Server/Azure SQL,"
14
+ "Fork of the ActiveRecord JDBC adapter with support for SQL Server and Azure SQL,"
15
15
  "AR-JDBC is a database adapter for Rails' ActiveRecord component designed " <<
16
16
  "to be used with JRuby built upon Java's JDBC API for database access. " <<
17
17
  "Provides (ActiveRecord) built-in adapters: MySQL, PostgreSQL and SQLite3 " <<
@@ -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!
@@ -0,0 +1 @@
1
+ require 'arjdbc'
@@ -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,11 +10,13 @@ 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
- log(sql, name) { @connection.execute_insert(sql) }
16
+ log(sql, name) { @connection.execute_insert_pk(sql, pk) }
15
17
  else
16
18
  log(sql, name, binds) do
17
- @connection.execute_insert(sql, binds)
19
+ @connection.execute_insert_pk(sql, binds, pk)
18
20
  end
19
21
  end
20
22
  end
@@ -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
@@ -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
 
@@ -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
@@ -81,6 +81,12 @@ module ActiveRecord
81
81
  ::ActiveRecord::ConnectionAdapters::MSSQLColumn
82
82
  end
83
83
 
84
+ # Does this adapter support DDL rollbacks in transactions? That is, would
85
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction?
86
+ def supports_ddl_transactions?
87
+ true
88
+ end
89
+
84
90
  # Can this adapter determine the primary key for tables not attached
85
91
  # to an Active Record class, such as join tables?
86
92
  def supports_primary_key?
@@ -92,6 +98,16 @@ module ActiveRecord
92
98
  true
93
99
  end
94
100
 
101
+ # Does this adapter support index sort order?
102
+ def supports_index_sort_order?
103
+ true
104
+ end
105
+
106
+ # Also known as filtered index
107
+ def supports_partial_index?
108
+ true
109
+ end
110
+
95
111
  # Does this adapter support migrations?
96
112
  def supports_migrations?
97
113
  true
@@ -118,6 +134,13 @@ module ActiveRecord
118
134
  super
119
135
  end
120
136
 
137
+ def reset!
138
+ # execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
139
+ # NOTE: it seems the above line interferes with the jdbc driver
140
+ # and ending up in connection closed, issue seen in rails 5.2 and 6.0
141
+ reconnect!
142
+ end
143
+
121
144
  def disable_referential_integrity
122
145
  tables = tables_with_referential_integrity
123
146
 
@@ -199,21 +222,11 @@ module ActiveRecord
199
222
  end
200
223
 
201
224
  def configure_connection
202
- execute("SET LOCK_TIMEOUT #{lock_timeout}")
225
+ # Here goes initial settings per connection
203
226
 
204
227
  set_session_transaction_isolation
205
228
  end
206
229
 
207
- def lock_timeout
208
- timeout = config[:lock_timeout].to_i
209
-
210
- if timeout.positive?
211
- timeout
212
- else
213
- 5_000
214
- end
215
- end
216
-
217
230
  def set_session_transaction_isolation
218
231
  isolation_level = config[:transaction_isolation]
219
232
 
@@ -52,6 +52,8 @@ ArJdbc::ConnectionMethods.module_eval do
52
52
 
53
53
  # @note Assumes SQLServer SQL-JDBC driver on the class-path.
54
54
  def sqlserver_connection(config)
55
+ config = config.deep_dup
56
+
55
57
  config[:adapter_spec] ||= ::ArJdbc::MSSQL
56
58
  config[:adapter_class] = ActiveRecord::ConnectionAdapters::MSSQLAdapter unless config.key?(:adapter_class)
57
59
 
@@ -60,12 +62,15 @@ ArJdbc::ConnectionMethods.module_eval do
60
62
  config[:host] ||= 'localhost'
61
63
  config[:driver] ||= 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
62
64
  config[:connection_alive_sql] ||= 'SELECT 1'
65
+ config[:lock_timeout] ||= 5000
63
66
 
64
67
  config[:url] ||= begin
65
68
  url = "jdbc:sqlserver://#{config[:host]}"
66
69
  url << ( config[:port] ? ":#{config[:port]};" : ';' )
67
70
  url << "databaseName=#{config[:database]};" if config[:database]
68
71
  url << "instanceName=#{config[:instance]};" if config[:instance]
72
+ url << "loginTimeout=#{config[:login_timeout].to_i};" if config[:login_timeout]
73
+ url << "lockTimeout=#{config[:lock_timeout].to_i};"
69
74
  app = config[:appname] || config[:application]
70
75
  url << "applicationName=#{app};" if app
71
76
  isc = config[:integrated_security] # Win only - needs sqljdbc_auth.dll
@@ -87,7 +87,7 @@ module ActiveRecord
87
87
  end
88
88
 
89
89
  def identity_column_name(table_name)
90
- for column in columns(table_name)
90
+ for column in schema_cache.columns(table_name)
91
91
  return column.name if column.identity?
92
92
  end
93
93
  nil
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  # NOTE: This is ready, all implemented in the java part of adapter,
58
58
  # it uses MSSQLColumn, SqlTypeMetadata, etc.
59
59
  def columns(table_name)
60
- @connection.columns(table_name)
60
+ log('JDBC: GETCOLUMNS', 'SCHEMA') { @connection.columns(table_name) }
61
61
  rescue => e
62
62
  raise translate_exception_class(e, nil)
63
63
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  ArJdbc::ConnectionMethods.module_eval do
3
3
  def mysql_connection(config)
4
+ config = config.deep_dup
4
5
  # NOTE: this isn't "really" necessary but Rails (in tests) assumes being able to :
5
6
  # ActiveRecord::Base.mysql2_connection ActiveRecord::Base.configurations['arunit'].merge(database: ...)
6
7
  config = symbolize_keys_if_necessary(config)
@@ -10,11 +11,9 @@ ArJdbc::ConnectionMethods.module_eval do
10
11
 
11
12
  return jndi_connection(config) if jndi_config?(config)
12
13
 
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.')
14
+ driver = config[:driver]
15
+ mysql_driver = driver.nil? || driver.to_s.start_with?('com.mysql.')
16
+ mariadb_driver = ! mysql_driver && driver.to_s.start_with?('org.mariadb.')
18
17
 
19
18
  begin
20
19
  require 'jdbc/mysql'
@@ -22,6 +21,11 @@ ArJdbc::ConnectionMethods.module_eval do
22
21
  rescue LoadError # assuming driver.jar is on the class-path
23
22
  end if mysql_driver
24
23
 
24
+ if driver.nil?
25
+ config[:driver] ||=
26
+ defined?(::Jdbc::MySQL.driver_name) ? ::Jdbc::MySQL.driver_name : 'com.mysql.jdbc.Driver'
27
+ end
28
+
25
29
  config[:username] = 'root' unless config.key?(:username)
26
30
  # jdbc:mysql://[host][,failoverhost...][:port]/[database]
27
31
  # - if the host name is not specified, it defaults to 127.0.0.1
@@ -36,7 +40,8 @@ ArJdbc::ConnectionMethods.module_eval do
36
40
 
37
41
  properties = ( config[:properties] ||= {} )
38
42
  if mysql_driver
39
- properties['zeroDateTimeBehavior'] ||= 'convertToNull'
43
+ properties['zeroDateTimeBehavior'] ||=
44
+ config[:driver].to_s.start_with?('com.mysql.cj.') ? 'CONVERT_TO_NULL' : 'convertToNull'
40
45
  properties['jdbcCompliantTruncation'] ||= false
41
46
  # NOTE: this is "better" than passing what users are used to set on MRI
42
47
  # e.g. 'utf8mb4' will fail cause the driver will check for a Java charset
@@ -108,7 +113,8 @@ ArJdbc::ConnectionMethods.module_eval do
108
113
  rescue LoadError # assuming driver.jar is on the class-path
109
114
  end
110
115
 
111
- config[:driver] ||= 'org.mariadb.jdbc.Driver'
116
+ config[:driver] ||=
117
+ defined?(::Jdbc::MariaDB.driver_name) ? ::Jdbc::MariaDB.driver_name : 'org.mariadb.jdbc.Driver'
112
118
 
113
119
  mysql_connection(config)
114
120
  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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  ArJdbc::ConnectionMethods.module_eval do
3
3
  def postgresql_connection(config)
4
+ config = config.deep_dup
4
5
  # NOTE: this isn't "really" necessary but Rails (in tests) assumes being able to :
5
6
  # ActiveRecord::Base.postgresql_connection ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false)
6
7
  # ... while using symbols by default but than configurations returning string keys ;(
@@ -16,7 +17,8 @@ ArJdbc::ConnectionMethods.module_eval do
16
17
  ::Jdbc::Postgres.load_driver(:require) if defined?(::Jdbc::Postgres.load_driver)
17
18
  rescue LoadError # assuming driver.jar is on the class-path
18
19
  end
19
- driver = config[:driver] ||= 'org.postgresql.Driver'
20
+ driver = (config[:driver] ||=
21
+ defined?(::Jdbc::Postgres.driver_name) ? ::Jdbc::Postgres.driver_name : 'org.postgresql.Driver')
20
22
 
21
23
  host = config[:host] ||= ( config[:hostaddr] || ENV['PGHOST'] || 'localhost' )
22
24
  port = config[:port] ||= ( ENV['PGPORT'] || 5432 )
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  ArJdbc::ConnectionMethods.module_eval do
3
3
  def sqlite3_connection(config)
4
+ config = config.deep_dup
4
5
  config[:adapter_spec] ||= ::ArJdbc::SQLite3
5
6
  config[:adapter_class] = ActiveRecord::ConnectionAdapters::SQLite3Adapter unless config.key?(:adapter_class)
6
7
 
@@ -1,3 +1,3 @@
1
1
  module ArJdbc
2
- VERSION = '50.3.2'
2
+ VERSION = '50.7.0'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  namespace :'tomcat-jndi' do # contains a FS JNDI impl (for tests)
2
2
 
3
- TOMCAT_MAVEN_REPO = 'http://repo2.maven.org/maven2/org/apache/tomcat'
3
+ TOMCAT_MAVEN_REPO = 'https://repo1.maven.org/maven2/org/apache/tomcat'
4
4
  TOMCAT_VERSION = '7.0.54'
5
5
 
6
6
  DOWNLOAD_DIR = File.expand_path('../test/jars', File.dirname(__FILE__))
@@ -48,4 +48,4 @@ namespace :'tomcat-jndi' do # contains a FS JNDI impl (for tests)
48
48
  rm jar_path if File.exist?(jar_path)
49
49
  end
50
50
 
51
- end
51
+ end
@@ -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
 
@@ -57,7 +57,7 @@ namespace :rails do
57
57
  ruby_opts_string += " -C \"#{ar_path}\""
58
58
  ruby_opts_string += " -rbundler/setup"
59
59
  ruby_opts_string += " -rminitest -rminitest/excludes" unless ENV['NO_EXCLUDES'].eql?('true')
60
- file_list = ENV["TEST"] ? FileList[ ENV["TEST"] ] : test_files_finder.call
60
+ file_list = ENV["TEST"] ? FileList[ ENV["TEST"].split(',') ] : test_files_finder.call
61
61
  file_list_string = file_list.map { |fn| "\"#{fn}\"" }.join(' ')
62
62
  # test_loader_code = "-e \"ARGV.each{|f| require f}\"" # :direct
63
63
  option_list = ( ENV["TESTOPTS"] || ENV["TESTOPT"] || ENV["TEST_OPTS"] || '' )
@@ -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)");
@@ -722,7 +727,10 @@ public class RubyJdbcConnection extends RubyObject {
722
727
 
723
728
  private void connectImpl(final boolean forceConnection) throws SQLException {
724
729
  setConnection( forceConnection ? newConnection() : null );
725
- if ( forceConnection ) configureConnection();
730
+ if (forceConnection) {
731
+ if (getConnectionImpl() == null) throw new SQLException("Didn't get a connection. Wrong URL?");
732
+ configureConnection();
733
+ }
726
734
  }
727
735
 
728
736
  @JRubyMethod(name = "read_only?")
@@ -880,15 +888,31 @@ public class RubyJdbcConnection extends RubyObject {
880
888
  return mapQueryResult(context, connection, resultSet);
881
889
  }
882
890
 
891
+ private static String[] createStatementPk(IRubyObject pk) {
892
+ String[] statementPk;
893
+ if (pk instanceof RubyArray) {
894
+ RubyArray ary = (RubyArray) pk;
895
+ int size = ary.size();
896
+ statementPk = new String[size];
897
+ for (int i = 0; i < size; i++) {
898
+ statementPk[i] = sqlString(ary.eltInternal(i));
899
+ }
900
+ } else {
901
+ statementPk = new String[] { sqlString(pk) };
902
+ }
903
+ return statementPk;
904
+ }
905
+
883
906
  /**
884
907
  * Executes an INSERT SQL statement
885
908
  * @param context
886
909
  * @param sql
910
+ * @param pk Rails PK
887
911
  * @return ActiveRecord::Result
888
912
  * @throws SQLException
889
913
  */
890
- @JRubyMethod(name = "execute_insert", required = 1)
891
- public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql) {
914
+ @JRubyMethod(name = "execute_insert_pk", required = 2)
915
+ public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
892
916
  return withConnection(context, new Callable<IRubyObject>() {
893
917
  public IRubyObject call(final Connection connection) throws SQLException {
894
918
  Statement statement = null;
@@ -896,7 +920,13 @@ public class RubyJdbcConnection extends RubyObject {
896
920
  try {
897
921
 
898
922
  statement = createStatement(context, connection);
899
- statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
923
+
924
+ if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
925
+ statement.executeUpdate(query, Statement.RETURN_GENERATED_KEYS);
926
+ } else {
927
+ statement.executeUpdate(query, createStatementPk(pk));
928
+ }
929
+
900
930
  return mapGeneratedKeys(context, connection, statement);
901
931
 
902
932
  } catch (final SQLException e) {
@@ -909,23 +939,35 @@ public class RubyJdbcConnection extends RubyObject {
909
939
  });
910
940
  }
911
941
 
942
+ @Deprecated
943
+ @JRubyMethod(name = "execute_insert", required = 1)
944
+ public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql) {
945
+ return execute_insert_pk(context, sql, context.nil);
946
+ }
947
+
912
948
  /**
913
949
  * Executes an INSERT SQL statement using a prepared statement
914
950
  * @param context
915
951
  * @param sql
916
952
  * @param binds RubyArray of values to be bound to the query
953
+ * @param pk Rails PK
917
954
  * @return ActiveRecord::Result
918
955
  * @throws SQLException
919
956
  */
920
- @JRubyMethod(name = "execute_insert", required = 2)
921
- public IRubyObject execute_insert(final ThreadContext context, final IRubyObject sql, final IRubyObject binds) {
957
+ @JRubyMethod(name = "execute_insert_pk", required = 3)
958
+ public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
959
+ final IRubyObject pk) {
922
960
  return withConnection(context, new Callable<IRubyObject>() {
923
961
  public IRubyObject call(final Connection connection) throws SQLException {
924
962
  PreparedStatement statement = null;
925
963
  final String query = sqlString(sql);
926
964
  try {
965
+ if (pk == context.nil || pk == context.fals || !supportsGeneratedKeys(connection)) {
966
+ statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
967
+ } else {
968
+ statement = connection.prepareStatement(query, createStatementPk(pk));
969
+ }
927
970
 
928
- statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
929
971
  setStatementParameters(context, connection, statement, (RubyArray) binds);
930
972
  statement.executeUpdate();
931
973
  return mapGeneratedKeys(context, connection, statement);
@@ -940,6 +982,12 @@ public class RubyJdbcConnection extends RubyObject {
940
982
  });
941
983
  }
942
984
 
985
+ @Deprecated
986
+ @JRubyMethod(name = "execute_insert", required = 2)
987
+ public IRubyObject execute_insert(final ThreadContext context, final IRubyObject binds, final IRubyObject sql) {
988
+ return execute_insert_pk(context, sql, binds, context.nil);
989
+ }
990
+
943
991
  /**
944
992
  * Executes an UPDATE (DELETE) SQL statement
945
993
  * @param context
@@ -3924,6 +3972,12 @@ public class RubyJdbcConnection extends RubyObject {
3924
3972
  }
3925
3973
  }
3926
3974
 
3975
+ public static void debugMessage(final ThreadContext context, final IRubyObject obj) {
3976
+ if ( isDebug(context.runtime) ) {
3977
+ debugMessage(context.runtime, obj.callMethod(context, "inspect"));
3978
+ }
3979
+ }
3980
+
3927
3981
  public static void debugMessage(final Ruby runtime, final String msg, final Object e) {
3928
3982
  if ( isDebug(runtime) ) {
3929
3983
  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,176 @@ 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
+ /**
207
+ * Executes an INSERT SQL statement
208
+ * @param context
209
+ * @param sql
210
+ * @param pk Rails PK
211
+ * @return ActiveRecord::Result
212
+ * @throws SQLException
213
+ */
214
+ @Override
215
+ @JRubyMethod(name = "execute_insert_pk", required = 2)
216
+ public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject pk) {
217
+
218
+ // MSSQL does not like composite primary keys here so chop it if there is more than one column
219
+ IRubyObject modifiedPk = pk;
220
+
221
+ if (pk instanceof RubyArray) {
222
+ RubyArray ary = (RubyArray) pk;
223
+ if (ary.size() > 0) {
224
+ modifiedPk = ary.eltInternal(0);
225
+ }
226
+ }
227
+
228
+ return super.execute_insert_pk(context, sql, modifiedPk);
229
+ }
230
+
231
+ /**
232
+ * Executes an INSERT SQL statement using a prepared statement
233
+ * @param context
234
+ * @param sql
235
+ * @param binds RubyArray of values to be bound to the query
236
+ * @param pk Rails PK
237
+ * @return ActiveRecord::Result
238
+ * @throws SQLException
239
+ */
240
+ @Override
241
+ @JRubyMethod(name = "execute_insert_pk", required = 3)
242
+ public IRubyObject execute_insert_pk(final ThreadContext context, final IRubyObject sql, final IRubyObject binds,
243
+ final IRubyObject pk) {
244
+ // MSSQL does not like composite primary keys here so chop it if there is more than one column
245
+ IRubyObject modifiedPk = pk;
246
+
247
+ if (pk instanceof RubyArray) {
248
+ RubyArray ary = (RubyArray) pk;
249
+ if (ary.size() > 0) {
250
+ modifiedPk = ary.eltInternal(0);
251
+ }
252
+ }
253
+
254
+ return super.execute_insert_pk(context, sql, binds, modifiedPk);
255
+ }
256
+
257
+ @Override
258
+ protected Integer jdbcTypeFor(final String type) {
259
+
260
+ Integer typeValue = MSSQL_JDBC_TYPE_FOR.get(type);
261
+
262
+ if ( typeValue != null ) {
263
+ return typeValue;
264
+ }
265
+
266
+ return super.jdbcTypeFor(type);
267
+ }
268
+
269
+ // Datetimeoffset values also make it into here
270
+ @Override
271
+ protected void setStringParameter(final ThreadContext context, final Connection connection,
272
+ final PreparedStatement statement, final int index, final IRubyObject value,
273
+ final IRubyObject attribute, final int type) throws SQLException {
274
+
275
+ // datetimeoffset values also make it in here
276
+ if (type == DATETIMEOFFSET_TYPE) {
277
+
278
+ Object dto = convertToDateTimeOffset(context, value);
279
+
280
+ try {
281
+
282
+ Object[] setStatementArgs = { index, dto };
283
+ PreparedStatementSetDateTimeOffsetMethod.invoke(statement, setStatementArgs);
284
+
285
+ } catch (IllegalAccessException e) {
286
+ debugMessage(context.runtime, e.getMessage());
287
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
288
+ } catch (InvocationTargetException e) {
289
+ debugMessage(context.runtime, e.getMessage());
290
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
291
+ }
292
+
293
+ return;
294
+ }
295
+ super.setStringParameter(context, connection, statement, index, value, attribute, type);
296
+ }
297
+
298
+ private Object convertToDateTimeOffset(final ThreadContext context, final IRubyObject value) {
299
+
300
+ RubyTime time = (RubyTime) value;
301
+ DateTime dt = time.getDateTime();
302
+ Timestamp timestamp = new Timestamp(dt.getMillis());
303
+ timestamp.setNanos(timestamp.getNanos() + (int) time.getNSec());
304
+ int offsetMinutes = dt.getZone().getOffset(dt.getMillis()) / 60000;
305
+
306
+ try {
307
+
308
+ Object[] dtoArgs = { timestamp, offsetMinutes };
309
+ return DateTimeOffsetValueOfMethod.invoke(null, dtoArgs);
310
+
311
+ } catch (IllegalAccessException e) {
312
+ debugMessage(context.runtime, e.getMessage());
313
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
314
+ } catch (InvocationTargetException e) {
315
+ debugMessage(context.runtime, e.getMessage());
316
+ throw new RuntimeException("Please make sure you are using the latest version of the Microsoft JDBC driver");
317
+ }
318
+ }
319
+
320
+
92
321
  @Override
93
322
  protected RubyArray mapTables(final ThreadContext context, final Connection connection,
94
323
  final String catalog, final String schemaPattern, final String tablePattern,
@@ -305,29 +534,6 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
305
534
  statement.setObject(index, timeStr, Types.NVARCHAR);
306
535
  }
307
536
 
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
537
  //----------------------------------------------------------------
332
538
  // read_uncommitted: "READ UNCOMMITTED",
333
539
  // read_committed: "READ COMMITTED",
@@ -453,16 +659,64 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
453
659
 
454
660
  /**
455
661
  * Treat LONGVARCHAR as CLOB on MSSQL for purposes of converting a JDBC value to Ruby.
662
+ * Also handle datetimeoffset values here
456
663
  */
457
664
  @Override
458
665
  protected IRubyObject jdbcToRuby(
459
666
  final ThreadContext context, final Ruby runtime,
460
667
  final int column, int type, final ResultSet resultSet)
461
668
  throws SQLException {
462
- if ( type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR ) type = Types.CLOB;
669
+
670
+ if (type == DATETIMEOFFSET_TYPE) {
671
+
672
+ Object dto = resultSet.getObject(column); // Returns a microsoft.sql.DateTimeOffset
673
+
674
+ if (dto == null) return context.nil;
675
+
676
+ try {
677
+
678
+ int minutes = (int) DateTimeOffsetGetMinutesOffsetMethod.invoke(dto);
679
+ DateTimeZone zone = DateTimeZone.forOffsetHoursMinutes(minutes / 60, minutes % 60);
680
+ Timestamp ts = (Timestamp) DateTimeOffsetGetTimestampMethod.invoke(dto);
681
+
682
+ int nanos = ts.getNanos(); // max 999-999-999
683
+ nanos = nanos % 1000000;
684
+
685
+ // We have to do this differently than the newTime helper because the Timestamp loses its zone information when passed around
686
+ DateTime dateTime = new DateTime(ts.getTime(), zone);
687
+ return RubyTime.newTime(context.runtime, dateTime, nanos);
688
+
689
+ } catch (IllegalAccessException e) {
690
+ debugMessage(runtime, e.getMessage());
691
+ return context.nil;
692
+ } catch (InvocationTargetException e) {
693
+ debugMessage(runtime, e.getMessage());
694
+ return context.nil;
695
+ }
696
+ }
697
+
698
+ if (type == Types.LONGVARCHAR || type == Types.LONGNVARCHAR) type = Types.CLOB;
463
699
  return super.jdbcToRuby(context, runtime, column, type, resultSet);
464
700
  }
465
701
 
702
+ /**
703
+ * Converts a JDBC date object to a Ruby date by referencing Date#civil
704
+ * @param context current thread context
705
+ * @param resultSet the jdbc result set to pull the value from
706
+ * @param index the index of the column to convert
707
+ * @return RubyNil if NULL or RubyDate if there is a value
708
+ * @throws SQLException if it fails to retrieve the value from the result set
709
+ */
710
+ @Override
711
+ protected IRubyObject dateToRuby(ThreadContext context, Ruby runtime, ResultSet resultSet, int index) throws SQLException {
712
+
713
+ final Date value = resultSet.getDate(index);
714
+
715
+ if (value == null) return context.nil;
716
+
717
+ return DateTimeUtils.newDate(context, value);
718
+ }
719
+
466
720
  @Override
467
721
  protected ColumnData[] extractColumns(final ThreadContext context,
468
722
  final Connection connection, final ResultSet resultSet,
@@ -492,19 +746,9 @@ public class MSSQLRubyJdbcConnection extends RubyJdbcConnection {
492
746
  return columns;
493
747
  }
494
748
 
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
- });
749
+ @Override
750
+ protected void releaseSavepoint(final Connection connection, final Savepoint savepoint) throws SQLException {
751
+ // MSSQL doesn't support releasing savepoints
508
752
  }
509
753
 
510
754
  }
@@ -366,8 +366,9 @@ public class PostgreSQLRubyJdbcConnection extends arjdbc.jdbc.RubyJdbcConnection
366
366
  value = value.callMethod(context, "to_date");
367
367
  }
368
368
 
369
- // NOTE: assuming Date#to_s does right ...
370
- statement.setDate(index, Date.valueOf(value.toString()));
369
+ // NOTE: Here we rely in ActiveRecord (ActiveSupport) to get
370
+ // the date as a string in the database format.
371
+ statement.setDate(index, Date.valueOf(value.callMethod(context, "to_s", context.runtime.newSymbol("db")).toString()));
371
372
  }
372
373
 
373
374
  @Override
@@ -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.2
4
+ version: 50.7.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-06-29 00:00:00.000000000 Z
11
+ date: 2021-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -20,8 +20,8 @@ dependencies:
20
20
  - !ruby/object:Gem::Version
21
21
  version: 5.0.3
22
22
  name: activerecord
23
- prerelease: false
24
23
  type: :runtime
24
+ prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
@@ -30,8 +30,8 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 5.0.3
33
- description: Fork of the ActiveRecord JDBC adapter with support for SQL Server/Azure
34
- SQL,
33
+ description: Fork of the ActiveRecord JDBC adapter with support for SQL Server and
34
+ Azure SQL,
35
35
  email:
36
36
  - nick@nicksieger.com
37
37
  - ola.bini@gmail.com
@@ -70,6 +70,7 @@ files:
70
70
  - lib/active_record/connection_adapters/sqlite3_adapter.rb
71
71
  - lib/active_record/connection_adapters/sqlserver_adapter.rb
72
72
  - lib/activerecord-jdbc-adapter.rb
73
+ - lib/activerecord-jdbc-alt-adapter.rb
73
74
  - lib/arel/visitors/compat.rb
74
75
  - lib/arel/visitors/db2.rb
75
76
  - lib/arel/visitors/derby.rb
@@ -261,8 +262,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
262
  - !ruby/object:Gem::Version
262
263
  version: '0'
263
264
  requirements: []
264
- rubyforge_project:
265
- rubygems_version: 2.7.6
265
+ rubygems_version: 3.0.6
266
266
  signing_key:
267
267
  specification_version: 4
268
268
  summary: ActiveRecord JDBC adapter, for use within JRuby on Rails and SQL Server