activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha2-java

Sign up to get free protection for your applications and to get access to all the features.
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) &&