activerecord-jdbc-alt-adapter 50.3.2-java → 50.7.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: '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