activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha2-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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +141 -24
  3. data/.github/workflows/ruby.yml +12 -12
  4. data/.gitignore +7 -3
  5. data/.solargraph.yml +15 -0
  6. data/Gemfile +17 -4
  7. data/README.md +7 -3
  8. data/RUNNING_TESTS.md +36 -0
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  11. data/lib/arjdbc/abstract/connection_management.rb +26 -10
  12. data/lib/arjdbc/abstract/core.rb +5 -12
  13. data/lib/arjdbc/abstract/database_statements.rb +35 -25
  14. data/lib/arjdbc/abstract/statement_cache.rb +2 -7
  15. data/lib/arjdbc/abstract/transaction_support.rb +37 -22
  16. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  17. data/lib/arjdbc/jdbc/column.rb +0 -34
  18. data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
  19. data/lib/arjdbc/mssql/adapter.rb +101 -79
  20. data/lib/arjdbc/mssql/column.rb +1 -0
  21. data/lib/arjdbc/mssql/connection_methods.rb +7 -55
  22. data/lib/arjdbc/mssql/database_statements.rb +182 -71
  23. data/lib/arjdbc/mssql/explain_support.rb +8 -5
  24. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  25. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  26. data/lib/arjdbc/mssql/schema_statements.rb +25 -14
  27. data/lib/arjdbc/mssql/server_version.rb +56 -0
  28. data/lib/arjdbc/mssql/utils.rb +23 -9
  29. data/lib/arjdbc/mysql/adapter.rb +104 -27
  30. data/lib/arjdbc/postgresql/adapter.rb +71 -44
  31. data/lib/arjdbc/postgresql/oid_types.rb +8 -27
  32. data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
  33. data/lib/arjdbc/sqlite3/adapter.rb +205 -147
  34. data/lib/arjdbc/sqlite3/column.rb +103 -0
  35. data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
  36. data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
  37. data/lib/arjdbc/version.rb +1 -1
  38. data/rakelib/02-test.rake +1 -1
  39. data/rakelib/rails.rake +2 -0
  40. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3 -1
  41. data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +11 -0
  42. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
  43. metadata +10 -12
  44. data/lib/arel/visitors/sql_server/ng42.rb +0 -294
  45. data/lib/arel/visitors/sql_server.rb +0 -124
  46. data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
  47. data/lib/arjdbc/mssql/lock_methods.rb +0 -77
  48. data/lib/arjdbc/mssql/old_adapter.rb +0 -804
  49. data/lib/arjdbc/mssql/old_column.rb +0 -200
@@ -9,83 +9,85 @@ module ArJdbc
9
9
 
10
10
  NO_BINDS = [].freeze
11
11
 
12
- def exec_insert(sql, name = nil, binds = NO_BINDS, pk = nil, sequence_name = nil)
12
+ def exec_insert(sql, name = nil, binds = NO_BINDS, pk = nil, sequence_name = nil, returning: nil)
13
13
  sql = transform_query(sql)
14
14
 
15
15
  if preventing_writes?
16
16
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
17
17
  end
18
18
 
19
- materialize_transactions
20
19
  mark_transaction_written_if_write(sql)
21
20
 
22
21
  binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
23
22
 
24
- if without_prepared_statement?(binds)
25
- log(sql, name) { @connection.execute_insert_pk(sql, pk) }
26
- else
27
- log(sql, name, binds) do
28
- @connection.execute_insert_pk(sql, binds, pk)
23
+ with_raw_connection do |conn|
24
+ if without_prepared_statement?(binds)
25
+ log(sql, name) { conn.execute_insert_pk(sql, pk) }
26
+ else
27
+ log(sql, name, binds) do
28
+ conn.execute_insert_pk(sql, binds, pk)
29
+ end
29
30
  end
30
31
  end
31
32
  end
32
33
 
33
34
  # It appears that at this point (AR 5.0) "prepare" should only ever be true
34
35
  # if prepared statements are enabled
35
- def exec_query(sql, name = nil, binds = NO_BINDS, prepare: false, async: false)
36
+ def internal_exec_query(sql, name = nil, binds = NO_BINDS, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
36
37
  sql = transform_query(sql)
37
38
 
38
39
  if preventing_writes? && write_query?(sql)
39
40
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
40
41
  end
41
42
 
42
- materialize_transactions
43
43
  mark_transaction_written_if_write(sql)
44
44
 
45
45
  binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
46
46
 
47
- if without_prepared_statement?(binds)
48
- log(sql, name) { @connection.execute_query(sql) }
49
- else
50
- log(sql, name, binds) do
51
- # this is different from normal AR that always caches
52
- cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
53
- @connection.execute_prepared_query(sql, binds, cached_statement)
47
+ with_raw_connection do |conn|
48
+ if without_prepared_statement?(binds)
49
+ log(sql, name, async: async) { conn.execute_query(sql) }
50
+ else
51
+ log(sql, name, binds, async: async) do
52
+ # this is different from normal AR that always caches
53
+ cached_statement = fetch_cached_statement(sql) if prepare && @jdbc_statement_cache_enabled
54
+ conn.execute_prepared_query(sql, binds, cached_statement)
55
+ end
54
56
  end
55
57
  end
56
58
  end
57
59
 
58
- def exec_update(sql, name = nil, binds = NO_BINDS)
60
+ def exec_update(sql, name = 'SQL', binds = NO_BINDS)
59
61
  sql = transform_query(sql)
60
62
 
61
63
  if preventing_writes?
62
64
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
63
65
  end
64
66
 
65
- materialize_transactions
66
67
  mark_transaction_written_if_write(sql)
67
68
 
68
69
  binds = convert_legacy_binds_to_attributes(binds) if binds.first.is_a?(Array)
69
70
 
70
- if without_prepared_statement?(binds)
71
- log(sql, name) { @connection.execute_update(sql) }
72
- else
73
- log(sql, name, binds) { @connection.execute_prepared_update(sql, binds) }
71
+ with_raw_connection do |conn|
72
+ if without_prepared_statement?(binds)
73
+ log(sql, name) { conn.execute_update(sql) }
74
+ else
75
+ log(sql, name, binds) { conn.execute_prepared_update(sql, binds) }
76
+ end
74
77
  end
75
78
  end
76
79
  alias :exec_delete :exec_update
77
80
 
78
- def execute(sql, name = nil, async: false)
81
+ def execute(sql, name = nil, async: false, allow_retry: false, materialize_transactions: true)
79
82
  sql = transform_query(sql)
80
83
 
81
84
  if preventing_writes? && write_query?(sql)
82
85
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
83
86
  end
84
87
 
85
- materialize_transactions
86
88
  mark_transaction_written_if_write(sql)
87
89
 
88
- log(sql, name, async: async) { @connection.execute(sql) }
90
+ raw_execute(sql, name, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
89
91
  end
90
92
 
91
93
  # overridden to support legacy binds
@@ -102,6 +104,14 @@ module ArJdbc
102
104
  end
103
105
  end
104
106
 
107
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
108
+ log(sql, name, async: async) do
109
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
110
+ conn.execute(sql)
111
+ end
112
+ end
113
+ end
114
+
105
115
  end
106
116
  end
107
117
  end
@@ -24,23 +24,18 @@ module ArJdbc
24
24
 
25
25
  # Only say we support the statement cache if we are using prepared statements
26
26
  # and have a max number of statements defined
27
- statement_limit = self.class.type_cast_config_to_integer(config[:statement_limit])
27
+ statement_limit = self.class.type_cast_config_to_integer(@config[:statement_limit])
28
28
  @jdbc_statement_cache_enabled = prepared_statements && (statement_limit.nil? || statement_limit > 0)
29
29
 
30
30
  @statements = StatementPool.new(statement_limit) # AR (5.0) expects this to be stored as @statements
31
31
  end
32
32
 
33
- # Clears the prepared statements cache.
34
- def clear_cache!
35
- @statements.clear
36
- end
37
-
38
33
  def delete_cached_statement(sql)
39
34
  @statements.delete(sql_key(sql))
40
35
  end
41
36
 
42
37
  def fetch_cached_statement(sql)
43
- @statements[sql_key(sql)] ||= @connection.prepare_statement(sql)
38
+ @statements[sql_key(sql)] ||= @raw_connection.prepare_statement(sql)
44
39
  end
45
40
 
46
41
  private
@@ -12,11 +12,11 @@ module ArJdbc
12
12
  # @since 1.3.0
13
13
  # @override
14
14
  def supports_savepoints?
15
- @connection.supports_savepoints?
15
+ @raw_connection.supports_savepoints?
16
16
  end
17
17
 
18
18
  def supports_transaction_isolation?
19
- @connection.supports_transaction_isolation?
19
+ @raw_connection.supports_transaction_isolation?
20
20
  end
21
21
 
22
22
  ########################## Transaction Interface ##########################
@@ -24,26 +24,42 @@ module ArJdbc
24
24
  # Starts a database transaction.
25
25
  # @override
26
26
  def begin_db_transaction
27
- log('BEGIN', 'TRANSACTION') { @connection.begin }
27
+ log('BEGIN', 'TRANSACTION') do
28
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
29
+ conn.begin
30
+ end
31
+ end
28
32
  end
29
33
 
30
34
  # Starts a database transaction.
31
35
  # @param isolation the transaction isolation to use
32
36
  def begin_isolated_db_transaction(isolation)
33
- log("BEGIN ISOLATED - #{isolation}", 'TRANSACTION') { @connection.begin(isolation) }
37
+ log("BEGIN ISOLATED - #{isolation}", 'TRANSACTION') do
38
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
39
+ conn.begin(isolation)
40
+ end
41
+ end
34
42
  end
35
43
 
36
44
  # Commits the current database transaction.
37
45
  # @override
38
46
  def commit_db_transaction
39
- log('COMMIT', 'TRANSACTION') { @connection.commit }
47
+ log('COMMIT', 'TRANSACTION') do
48
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
49
+ conn.commit
50
+ end
51
+ end
40
52
  end
41
53
 
42
54
  # Rolls back the current database transaction.
43
55
  # Called from 'rollback_db_transaction' in the AbstractAdapter
44
56
  # @override
45
57
  def exec_rollback_db_transaction
46
- log('ROLLBACK', 'TRANSACTION') { @connection.rollback }
58
+ log('ROLLBACK', 'TRANSACTION') do
59
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
60
+ conn.rollback
61
+ end
62
+ end
47
63
  end
48
64
 
49
65
  ########################## Savepoint Interface ############################
@@ -55,7 +71,11 @@ module ArJdbc
55
71
  # @since 1.3.0
56
72
  # @extension added optional name parameter
57
73
  def create_savepoint(name = current_savepoint_name)
58
- log("SAVEPOINT #{name}", 'TRANSACTION') { @connection.create_savepoint(name) }
74
+ log("SAVEPOINT #{name}", 'TRANSACTION') do
75
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
76
+ conn.create_savepoint(name)
77
+ end
78
+ end
59
79
  end
60
80
 
61
81
  # Transaction rollback to a given (previously created) save-point.
@@ -64,7 +84,11 @@ module ArJdbc
64
84
  # @param name the save-point name
65
85
  # @extension added optional name parameter
66
86
  def exec_rollback_to_savepoint(name = current_savepoint_name)
67
- log("ROLLBACK TO SAVEPOINT #{name}", 'TRANSACTION') { @connection.rollback_savepoint(name) }
87
+ log("ROLLBACK TO SAVEPOINT #{name}", 'TRANSACTION') do
88
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
89
+ conn.rollback_savepoint(name)
90
+ end
91
+ end
68
92
  end
69
93
 
70
94
  # Release a previously created save-point.
@@ -73,22 +97,13 @@ module ArJdbc
73
97
  # @param name the save-point name
74
98
  # @extension added optional name parameter
75
99
  def release_savepoint(name = current_savepoint_name)
76
- log("RELEASE SAVEPOINT #{name}", 'TRANSACTION') { @connection.release_savepoint(name) }
100
+ log("RELEASE SAVEPOINT #{name}", 'TRANSACTION') do
101
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
102
+ conn.release_savepoint(name)
103
+ end
104
+ end
77
105
  end
78
106
 
79
107
  end
80
108
  end
81
109
  end
82
-
83
- # patch to avoid the usage of WeakMap
84
- require 'active_record/connection_adapters/abstract/transaction'
85
- module ActiveRecord
86
- module ConnectionAdapters
87
- class Transaction
88
- def add_record(record, ensure_finalize = true)
89
- @records ||= []
90
- @records << record
91
- end
92
- end
93
- end
94
- end
Binary file
@@ -12,45 +12,11 @@ module ActiveRecord
12
12
  # specific type.
13
13
  # @see JdbcAdapter#jdbc_column_class
14
14
  class JdbcColumn < Column
15
- # @deprecated attribute writers will be removed in 1.4
16
- attr_writer :limit, :precision # unless ArJdbc::AR42
17
-
18
- def initialize(config, name, *args)
19
- if self.class == JdbcColumn
20
- # NOTE: extending classes do not want this if they do they shall call
21
- call_discovered_column_callbacks(config) if config
22
- default = args.shift
23
- else # for extending classes allow ignoring first argument :
24
- if ! config.nil? && ! config.is_a?(Hash)
25
- default = name; name = config # initialize(name, default, *args)
26
- else
27
- default = args.shift
28
- end
29
- end
30
-
31
- super(name, default, *args)
32
- init_column(name, default, *args)
33
- end
34
-
35
- # Additional column initialization for sub-classes.
36
- def init_column(*args); end
37
15
 
38
16
  # Similar to `ActiveRecord`'s `extract_value_from_default(default)`.
39
17
  # @return default value for a column (possibly extracted from driver value)
40
18
  def default_value(value); value; end
41
19
 
42
- protected
43
-
44
- # @private
45
- def call_discovered_column_callbacks(config)
46
- dialect = (config[:dialect] || config[:driver]).to_s
47
- for matcher, block in self.class.column_types
48
- block.call(config, self) if matcher === dialect
49
- end
50
- end
51
-
52
- public
53
-
54
20
  # Returns the available column types
55
21
  # @return [Hash] of (matcher, block) pairs
56
22
  def self.column_types
@@ -7,7 +7,7 @@ module ArJdbc
7
7
 
8
8
  def jdbc_connection(config)
9
9
  adapter_class = config[:adapter_class] || ::ActiveRecord::ConnectionAdapters::JdbcAdapter
10
- adapter_class.new(nil, logger, nil, config)
10
+ adapter_class.new(config)
11
11
  end
12
12
 
13
13
  def jndi_connection(config); jdbc_connection(config) end
@@ -15,6 +15,8 @@ require 'arjdbc/abstract/database_statements'
15
15
  require 'arjdbc/abstract/statement_cache'
16
16
  require 'arjdbc/abstract/transaction_support'
17
17
 
18
+ require 'arjdbc/mssql/utils'
19
+ require 'arjdbc/mssql/server_version'
18
20
  require 'arjdbc/mssql/column'
19
21
  require 'arjdbc/mssql/types'
20
22
  require 'arjdbc/mssql/quoting'
@@ -34,23 +36,11 @@ module ActiveRecord
34
36
  class MSSQLAdapter < AbstractAdapter
35
37
  ADAPTER_NAME = 'MSSQL'.freeze
36
38
 
37
- MSSQL_VERSION_YEAR = {
38
- 8 => '2000',
39
- 9 => '2005',
40
- 10 => '2008',
41
- 11 => '2012',
42
- 12 => '2014',
43
- 13 => '2016',
44
- 14 => '2017',
45
- 15 => '2019',
46
- 16 => '2022'
47
- }.freeze
48
-
49
- include Jdbc::ConnectionPoolCallbacks
39
+ # include Jdbc::ConnectionPoolCallbacks
50
40
  include ArJdbc::Abstract::Core
51
- include ArJdbc::Abstract::ConnectionManagement
52
- include ArJdbc::Abstract::DatabaseStatements
53
- include ArJdbc::Abstract::StatementCache
41
+ # include ArJdbc::Abstract::ConnectionManagement
42
+ # include ArJdbc::Abstract::DatabaseStatements
43
+ # include ArJdbc::Abstract::StatementCache
54
44
  include ArJdbc::Abstract::TransactionSupport
55
45
 
56
46
  include MSSQL::Quoting
@@ -63,32 +53,55 @@ module ActiveRecord
63
53
 
64
54
  class << self
65
55
  attr_accessor :cs_equality_operator
56
+
57
+ # Returns the (JDBC) connection class to be used for this adapter.
58
+ # The class is defined in the java part
59
+ def jdbc_connection_class
60
+ ::ActiveRecord::ConnectionAdapters::MSSQLJdbcConnection
61
+ end
62
+
63
+ def new_client(conn_params, adapter_instance)
64
+ jdbc_connection_class.new(conn_params, adapter_instance)
65
+ rescue ActiveRecord::JDBCError => error
66
+ if conn_params && conn_params[:database] && error.message.include?(conn_params[:database])
67
+ raise ActiveRecord::NoDatabaseError.db_error(conn_params[:database])
68
+ elsif conn_params && conn_params[:username] && error.message.include?(conn_params[:username])
69
+ raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:username])
70
+ elsif conn_params && conn_params[:host] && error.message.include?(conn_params[:host])
71
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
72
+ else
73
+ raise ActiveRecord::ConnectionNotEstablished, error.message
74
+ end
75
+ end
66
76
  end
67
77
 
68
- def initialize(connection, logger, _connection_parameters, config = {})
78
+ def initialize(...)
69
79
  # configure_connection happens in super
70
- super(connection, logger, config)
80
+ super
71
81
 
72
- if database_version < '11'
73
- raise "Your #{mssql_product_name} #{mssql_version_year} is too old. This adapter supports #{mssql_product_name} >= 2012."
74
- end
82
+ conn_params = @config.compact
83
+
84
+ @raw_connection = nil
85
+
86
+ @connection_parameters = conn_params
75
87
  end
76
88
 
77
- def self.database_exists?(config)
78
- !!ActiveRecord::Base.sqlserver_connection(config)
79
- rescue ActiveRecord::JDBCError => e
80
- case e.message
81
- when /Cannot open database .* requested by the login/
82
- false
83
- else
84
- raise
89
+ # @override
90
+ def active?
91
+ @lock.synchronize do
92
+ return false unless @raw_connection
93
+
94
+ @raw_connection.active?
85
95
  end
86
96
  end
87
97
 
88
- # Returns the (JDBC) connection class to be used for this adapter.
89
- # The class is defined in the java part
90
- def jdbc_connection_class(_spec)
91
- ::ActiveRecord::ConnectionAdapters::MSSQLJdbcConnection
98
+ # @override
99
+ def disconnect!
100
+ @lock.synchronize do
101
+ super # clear_cache! && reset_transaction
102
+ @raw_connection&.disconnect!
103
+ @raw_connection = nil
104
+ end
92
105
  end
93
106
 
94
107
  # Returns the (JDBC) `ActiveRecord` column class for this adapter.
@@ -177,10 +190,10 @@ module ActiveRecord
177
190
  !native_database_types[type].nil?
178
191
  end
179
192
 
180
- def clear_cache!
181
- # reload_type_map
182
- super
183
- end
193
+ # def clear_cache!
194
+ # # reload_type_map
195
+ # super
196
+ # end
184
197
 
185
198
  def reset!
186
199
  # execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
@@ -193,12 +206,12 @@ module ActiveRecord
193
206
  tables = tables_with_referential_integrity
194
207
 
195
208
  tables.each do |table_name|
196
- execute "ALTER TABLE #{table_name} NOCHECK CONSTRAINT ALL"
209
+ internal_execute("ALTER TABLE #{table_name} NOCHECK CONSTRAINT ALL")
197
210
  end
198
211
  yield
199
212
  ensure
200
213
  tables.each do |table_name|
201
- execute "ALTER TABLE #{table_name} CHECK CONSTRAINT ALL"
214
+ internal_execute("ALTER TABLE #{table_name} CHECK CONSTRAINT ALL")
202
215
  end
203
216
  end
204
217
 
@@ -222,13 +235,17 @@ module ActiveRecord
222
235
 
223
236
  # Returns the name of the current security context
224
237
  def current_user
225
- @current_user ||= select_value('SELECT CURRENT_USER')
238
+ @current_user ||= internal_execute('SELECT CURRENT_USER').rows.flatten.first
226
239
  end
227
240
 
228
241
  # Returns the default schema (to be used for table resolution)
229
242
  # used for the {#current_user}.
230
243
  def default_schema
231
- @default_schema ||= select_value('SELECT default_schema_name FROM sys.database_principals WHERE name = CURRENT_USER')
244
+ @default_schema ||= internal_execute(default_schema_query).rows.flatten.first
245
+ end
246
+
247
+ def default_schema_query
248
+ 'SELECT default_schema_name FROM sys.database_principals WHERE name = CURRENT_USER'
232
249
  end
233
250
 
234
251
  alias_method :current_schema, :default_schema
@@ -236,7 +253,7 @@ module ActiveRecord
236
253
  # Allows for changing of the default schema.
237
254
  # (to be used during unqualified table name resolution).
238
255
  def default_schema=(default_schema)
239
- execute("ALTER #{current_user} WITH DEFAULT_SCHEMA=#{default_schema}")
256
+ internal_execute("ALTER #{current_user} WITH DEFAULT_SCHEMA=#{default_schema}")
240
257
  @default_schema = nil if defined?(@default_schema)
241
258
  end
242
259
 
@@ -273,7 +290,7 @@ module ActiveRecord
273
290
  end
274
291
 
275
292
  def set_session_transaction_isolation
276
- isolation_level = config[:transaction_isolation]
293
+ isolation_level = @config[:transaction_isolation]
277
294
 
278
295
  self.transaction_isolation = isolation_level if isolation_level
279
296
  end
@@ -282,35 +299,31 @@ module ActiveRecord
282
299
  true
283
300
  end
284
301
 
285
- def mssql_major_version
286
- return @mssql_major_version if defined? @mssql_major_version
287
-
288
- @mssql_major_version = @connection.database_major_version
289
- end
290
-
291
- def mssql_version_year
292
- MSSQL_VERSION_YEAR[mssql_major_version.to_i]
302
+ def get_database_version # :nodoc:
303
+ MSSQLAdapter::Version.new(mssql_version.major, mssql_version.complete)
293
304
  end
294
305
 
295
- def mssql_product_version
296
- return @mssql_product_version if defined? @mssql_product_version
306
+ def check_version # :nodoc:
307
+ return unless database_version.to_s <= mssql_version.min_major
297
308
 
298
- @mssql_product_version = @connection.database_product_version
309
+ raise "Your #{mssql_version.product_name} is too old. #{mssql_version.support_message}"
299
310
  end
300
311
 
301
- def mssql_product_name
302
- return @mssql_product_name if defined? @mssql_product_name
312
+ def mssql_version
313
+ return @mssql_version if defined? @mssql_version
303
314
 
304
- @mssql_product_name = @connection.database_product_name
305
- end
315
+ result = internal_execute("SELECT #{mssql_version_properties.join(', ')}").rows
306
316
 
307
- def get_database_version # :nodoc:
308
- MSSQLAdapter::Version.new(mssql_product_version)
317
+ @mssql_version = MSSQL::Version.new(result.flatten)
309
318
  end
310
319
 
311
- def check_version # :nodoc:
312
- # NOTE: hitting the database from here causes trouble when adapter
313
- # uses JNDI or Data Source setup.
320
+ def mssql_version_properties
321
+ [
322
+ "SERVERPROPERTY('productVersion')",
323
+ "SERVERPROPERTY('productMajorVersion')",
324
+ "SERVERPROPERTY('productLevel')",
325
+ "SERVERPROPERTY('edition')"
326
+ ]
314
327
  end
315
328
 
316
329
  def tables_with_referential_integrity
@@ -404,6 +417,8 @@ module ActiveRecord
404
417
  register_class_with_precision map, %r{\Adatetime2\(\d+\)}i, MSSQL::Type::DateTime2
405
418
  # map.register_type 'datetime2(7)', MSSQL::Type::DateTime2.new
406
419
 
420
+ # map.register_type %r(^json)i, Type::Json.new
421
+
407
422
  # TODO: we should have identity separated from the sql_type
408
423
  # let's say in another attribute (this will help to pass more AR tests),
409
424
  # also we add collation attribute per column.
@@ -440,6 +455,20 @@ module ActiveRecord
440
455
 
441
456
  private
442
457
 
458
+ def connect
459
+ @raw_connection = self.class.new_client(@connection_parameters, self)
460
+ rescue ConnectionNotEstablished => ex
461
+ raise ex.set_pool(@pool)
462
+ end
463
+
464
+ def reconnect
465
+ @raw_connection&.disconnect!
466
+
467
+ @raw_connection = nil
468
+
469
+ connect
470
+ end
471
+
443
472
  def type_map
444
473
  TYPE_MAP
445
474
  end
@@ -454,6 +483,8 @@ module ActiveRecord
454
483
  LockTimeout.new(message, sql: sql, binds: binds)
455
484
  when /The .* statement conflicted with the FOREIGN KEY constraint/
456
485
  InvalidForeignKey.new(message, sql: sql, binds: binds)
486
+ when /Could not drop object .* because it is referenced by a FOREIGN KEY constraint/
487
+ StatementInvalid.new(message, sql: sql, binds: binds)
457
488
  when /The .* statement conflicted with the REFERENCE constraint/
458
489
  InvalidForeignKey.new(message, sql: sql, binds: binds)
459
490
  when /(String or binary data would be truncated)/i
@@ -462,6 +493,12 @@ module ActiveRecord
462
493
  NotNullViolation.new(message, sql: sql, binds: binds)
463
494
  when /Arithmetic overflow error converting expression/
464
495
  RangeError.new(message, sql: sql, binds: binds)
496
+ when /Snapshot isolation transaction aborted due to update conflict. You cannot use snapshot isolation/
497
+ StatementInvalid.new(message, sql: sql, binds: binds)
498
+ when /Incorrect syntax near the keyword .*/
499
+ StatementInvalid.new(message, sql: sql, binds: binds)
500
+ when /Could not find stored procedure .*/
501
+ StatementInvalid.new(message, sql: sql, binds: binds)
465
502
  else
466
503
  super
467
504
  end
@@ -472,13 +509,11 @@ module ActiveRecord
472
509
  # NOTE: This is ready, all implemented in the java part of adapter,
473
510
  # it uses MSSQLColumn, SqlTypeMetadata, etc.
474
511
  def column_definitions(table_name)
475
- log('JDBC: GETCOLUMNS', 'SCHEMA') { @connection.columns(table_name, nil, default_schema) }
476
- rescue => e
512
+ log('JDBC: GETCOLUMNS', 'SCHEMA') { valid_raw_connection.columns(table_name, nil, default_schema) }
477
513
  # raise translate_exception_class(e, nil)
478
514
  # FIXME: this breaks one arjdbc test but fixes activerecord tests
479
515
  # (table name alias). Also it behaves similarly to the CRuby adapter
480
516
  # which returns an empty array too. (postgres throws a exception)
481
- []
482
517
  end
483
518
 
484
519
  def arel_visitor # :nodoc:
@@ -491,16 +526,3 @@ module ActiveRecord
491
526
  end
492
527
  end
493
528
  end
494
-
495
- # FIXME: this is not used by the adapter anymore, it is here because
496
- # it is a dependency of old tests that needs to be reviewed
497
- module ArJdbc
498
- module MSSQL
499
- require 'arjdbc/mssql/utils'
500
- require 'arjdbc/mssql/limit_helpers'
501
- require 'arjdbc/mssql/lock_methods'
502
-
503
- include LimitHelpers
504
- include Utils
505
- end
506
- end
@@ -29,6 +29,7 @@ module ActiveRecord
29
29
  def identity?
30
30
  sql_type.downcase.include? 'identity'
31
31
  end
32
+ alias_method :auto_incremented_by_db?, :identity?
32
33
 
33
34
  def ==(other)
34
35
  other.is_a?(MSSQLColumn) &&