activerecord-sqlserver-adapter 7.0.4.0 → 7.1.0.beta1

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -3
  3. data/.gitignore +3 -1
  4. data/CHANGELOG.md +2 -69
  5. data/Gemfile +3 -0
  6. data/README.md +16 -11
  7. data/Rakefile +2 -6
  8. data/VERSION +1 -1
  9. data/activerecord-sqlserver-adapter.gemspec +1 -1
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/abstract_adapter.rb +20 -0
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +29 -6
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +4 -4
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -2
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +15 -3
  15. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -31
  16. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +87 -131
  17. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +5 -5
  18. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +3 -2
  19. data/lib/active_record/connection_adapters/sqlserver/savepoints.rb +24 -0
  20. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +71 -32
  21. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +3 -3
  22. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +6 -0
  23. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +4 -6
  24. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +10 -0
  25. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +81 -114
  26. data/lib/active_record/connection_adapters/sqlserver_column.rb +1 -0
  27. data/lib/active_record/sqlserver_base.rb +1 -10
  28. data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -2
  29. data/lib/arel/visitors/sqlserver.rb +0 -33
  30. data/test/cases/adapter_test_sqlserver.rb +8 -7
  31. data/test/cases/coerced_tests.rb +573 -208
  32. data/test/cases/column_test_sqlserver.rb +6 -6
  33. data/test/cases/connection_test_sqlserver.rb +3 -6
  34. data/test/cases/disconnected_test_sqlserver.rb +5 -8
  35. data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
  36. data/test/cases/rake_test_sqlserver.rb +1 -1
  37. data/test/cases/schema_dumper_test_sqlserver.rb +2 -2
  38. data/test/cases/view_test_sqlserver.rb +46 -0
  39. data/test/config.yml +1 -2
  40. data/test/support/connection_reflection.rb +2 -8
  41. data/test/support/core_ext/query_cache.rb +7 -1
  42. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  43. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic.dump +0 -0
  44. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_7_1_topic_associations.dump +0 -0
  45. metadata +20 -12
@@ -23,10 +23,10 @@ module ActiveRecord
23
23
  pktable = fkdata["PKTABLE_NAME"]
24
24
  pkcolmn = fkdata["PKCOLUMN_NAME"]
25
25
  remove_foreign_key fktable, name: fkdata["FK_NAME"]
26
- do_execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
26
+ execute "DELETE FROM #{quote_table_name(fktable)} WHERE #{quote_column_name(fkcolmn)} IN ( SELECT #{quote_column_name(pkcolmn)} FROM #{quote_table_name(pktable)} )"
27
27
  end
28
28
  end
29
- if options[:if_exists] && @version_year < 2016
29
+ if options[:if_exists] && version_year < 2016
30
30
  execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}", "SCHEMA"
31
31
  else
32
32
  super
@@ -39,12 +39,12 @@ module ActiveRecord
39
39
  data.reduce([]) do |indexes, index|
40
40
  index = index.with_indifferent_access
41
41
 
42
- if index[:index_description] =~ /primary key/
42
+ if index[:index_description].match?(/primary key/)
43
43
  indexes
44
44
  else
45
45
  name = index[:index_name]
46
- unique = index[:index_description] =~ /unique/
47
- where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}")
46
+ unique = index[:index_description].match?(/unique/)
47
+ where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}", "SCHEMA")
48
48
  orders = {}
49
49
  columns = []
50
50
 
@@ -118,15 +118,20 @@ module ActiveRecord
118
118
  AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
119
119
  ORDER BY KCU.ORDINAL_POSITION ASC
120
120
  }.gsub(/[[:space:]]/, " ")
121
+
121
122
  binds = []
122
123
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
123
124
  binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
124
125
  binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
125
- sp_executesql(sql, "SCHEMA", binds).map { |r| r["name"] }
126
+
127
+ internal_exec_query(sql, "SCHEMA", binds).map { |row| row["name"] }
126
128
  end
127
129
 
128
- def rename_table(table_name, new_name)
129
- do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
130
+ def rename_table(table_name, new_name, **options)
131
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
132
+ schema_cache.clear_data_source_cache!(table_name.to_s)
133
+ schema_cache.clear_data_source_cache!(new_name.to_s)
134
+ execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
130
135
  rename_table_indexes(table_name, new_name)
131
136
  end
132
137
 
@@ -137,7 +142,7 @@ module ActiveRecord
137
142
  remove_check_constraints(table_name, column_name)
138
143
  remove_default_constraint(table_name, column_name)
139
144
  remove_indexes(table_name, column_name)
140
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
145
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
141
146
  end
142
147
 
143
148
  def change_column(table_name, column_name, type, options = {})
@@ -182,7 +187,7 @@ module ActiveRecord
182
187
  sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.map { |c| quote_column_name(c) }.join(', ')})"
183
188
  end
184
189
 
185
- sql_commands.each { |c| do_execute(c) }
190
+ sql_commands.each { |c| execute(c) }
186
191
  clear_cache!
187
192
  end
188
193
 
@@ -193,7 +198,7 @@ module ActiveRecord
193
198
 
194
199
  remove_default_constraint(table_name, column_name)
195
200
  default = extract_new_default_value(default_or_changes)
196
- do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(default, column)} FOR #{quote_column_name(column_name)}"
201
+ execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote_default_expression(default, column)} FOR #{quote_column_name(column_name)}"
197
202
  clear_cache!
198
203
  end
199
204
 
@@ -213,23 +218,45 @@ module ActiveRecord
213
218
  end
214
219
 
215
220
  def remove_index!(table_name, index_name)
216
- do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
221
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
222
+ end
223
+
224
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
225
+ td = create_table_definition(table_name)
226
+ cd = td.new_column_definition(column_name, type, **options)
227
+ ChangeColumnDefinition.new(cd, column_name)
228
+ end
229
+
230
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
231
+ column = column_for(table_name, column_name)
232
+ return unless column
233
+
234
+ default = extract_new_default_value(default_or_changes)
235
+ ChangeColumnDefaultDefinition.new(column, default)
217
236
  end
218
237
 
219
238
  def foreign_keys(table_name)
220
239
  identifier = SQLServer::Utils.extract_identifiers(table_name)
221
240
  fk_info = execute_procedure :sp_fkeys, nil, identifier.schema, nil, identifier.object, identifier.schema
222
- fk_info.map do |row|
223
- from_table = identifier.object
224
- to_table = row["PKTABLE_NAME"]
241
+
242
+ grouped_fk = fk_info.group_by { |row| row["FK_NAME"] }.values.each { |group| group.sort_by! { |row| row["KEY_SEQ"] } }
243
+ grouped_fk.map do |group|
244
+ row = group.first
225
245
  options = {
226
246
  name: row["FK_NAME"],
227
- column: row["FKCOLUMN_NAME"],
228
- primary_key: row["PKCOLUMN_NAME"],
229
247
  on_update: extract_foreign_key_action("update", row["FK_NAME"]),
230
248
  on_delete: extract_foreign_key_action("delete", row["FK_NAME"])
231
249
  }
232
- ForeignKeyDefinition.new from_table, to_table, options
250
+
251
+ if group.one?
252
+ options[:column] = row["FKCOLUMN_NAME"]
253
+ options[:primary_key] = row["PKCOLUMN_NAME"]
254
+ else
255
+ options[:column] = group.map { |row| row["FKCOLUMN_NAME"] }
256
+ options[:primary_key] = group.map { |row| row["PKCOLUMN_NAME"] }
257
+ end
258
+
259
+ ForeignKeyDefinition.new(identifier.object, row["PKTABLE_NAME"], options)
233
260
  end
234
261
  end
235
262
 
@@ -268,6 +295,13 @@ module ActiveRecord
268
295
  end
269
296
  end
270
297
 
298
+ # In SQL Server only the first column added should have the `ADD` keyword.
299
+ def add_timestamps(table_name, **options)
300
+ fragments = add_timestamps_for_alter(table_name, **options)
301
+ fragments[1..].each { |fragment| fragment.sub!('ADD ', '') }
302
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{fragments.join(', ')}"
303
+ end
304
+
271
305
  def columns_for_distinct(columns, orders)
272
306
  order_columns = orders.reject(&:blank?).map { |s|
273
307
  s = s.to_sql unless s.is_a?(String)
@@ -282,16 +316,19 @@ module ActiveRecord
282
316
  SQLServer::Table.new(table_name, base)
283
317
  end
284
318
 
285
- def change_column_null(table_name, column_name, allow_null, default = nil)
319
+ def change_column_null(table_name, column_name, null, default = nil)
320
+ validate_change_column_null_argument!(null)
321
+
286
322
  table_id = SQLServer::Utils.extract_identifiers(table_name)
287
323
  column_id = SQLServer::Utils.extract_identifiers(column_name)
288
324
  column = column_for(table_name, column_name)
289
- if !allow_null.nil? && allow_null == false && !default.nil?
290
- do_execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
325
+ if !null.nil? && null == false && !default.nil?
326
+ execute("UPDATE #{table_id} SET #{column_id}=#{quote(default)} WHERE #{column_id} IS NULL")
291
327
  end
292
328
  sql = "ALTER TABLE #{table_id} ALTER COLUMN #{column_id} #{type_to_sql column.type, limit: column.limit, precision: column.precision, scale: column.scale}"
293
- sql += " NOT NULL" if !allow_null.nil? && allow_null == false
294
- do_execute sql
329
+ sql += " NOT NULL" if !null.nil? && null == false
330
+
331
+ execute sql
295
332
  end
296
333
 
297
334
  def create_schema_dumper(options)
@@ -387,12 +424,13 @@ module ActiveRecord
387
424
  view_tblnm = view_table_name(table_name) if view_exists
388
425
 
389
426
  if view_exists
390
- results = sp_executesql %{
391
- SELECT c.COLUMN_NAME AS [name], c.COLUMN_DEFAULT AS [default]
427
+ sql = <<~SQL
428
+ SELECT LOWER(c.COLUMN_NAME) AS [name], c.COLUMN_DEFAULT AS [default]
392
429
  FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
393
430
  WHERE c.TABLE_NAME = #{quote(view_tblnm)}
394
- }.squish, "SCHEMA", []
395
- default_functions = results.each.with_object({}) {|row, out| out[row["name"]] = row["default"] }.compact
431
+ SQL
432
+ results = internal_exec_query(sql, "SCHEMA")
433
+ default_functions = results.each.with_object({}) { |row, out| out[row["name"]] = row["default"] }.compact
396
434
  end
397
435
 
398
436
  sql = column_definitions_sql(database, identifier)
@@ -401,7 +439,8 @@ module ActiveRecord
401
439
  nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
402
440
  binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
403
441
  binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
404
- results = sp_executesql(sql, "SCHEMA", binds)
442
+ results = internal_exec_query(sql, "SCHEMA", binds)
443
+
405
444
  columns = results.map do |ci|
406
445
  ci = ci.symbolize_keys
407
446
  ci[:_type] = ci[:type]
@@ -426,7 +465,7 @@ module ActiveRecord
426
465
  ci[:default_function] = begin
427
466
  default = ci[:default_value]
428
467
  if default.nil? && view_exists
429
- view_column = views_real_column_name(table_name, ci[:name])
468
+ view_column = views_real_column_name(table_name, ci[:name]).downcase
430
469
  default = default_functions[view_column] if view_column.present?
431
470
  end
432
471
  case default
@@ -543,7 +582,7 @@ module ActiveRecord
543
582
  def remove_check_constraints(table_name, column_name)
544
583
  constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", "SCHEMA"
545
584
  constraints.each do |constraint|
546
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
585
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
547
586
  end
548
587
  end
549
588
 
@@ -552,7 +591,7 @@ module ActiveRecord
552
591
  execute_procedure(:sp_helpconstraint, table_name, "nomsg").flatten.select do |row|
553
592
  row["constraint_type"] == "DEFAULT on column #{column_name}"
554
593
  end.each do |row|
555
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
594
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
556
595
  end
557
596
  end
558
597
 
@@ -620,7 +659,7 @@ module ActiveRecord
620
659
  view_definition = view_information(table_name)[:VIEW_DEFINITION]
621
660
  return column_name unless view_definition
622
661
 
623
- match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
662
+ match_data = view_definition.match(/CREATE\s+VIEW.*AS\s+SELECT.*\W([\w-]*)\s+AS\s+#{column_name}/im)
624
663
  match_data ? match_data[1] : column_name
625
664
  end
626
665
 
@@ -12,9 +12,9 @@ module ActiveRecord
12
12
  OPTION_XML = "SHOWPLAN_XML"
13
13
  OPTIONS = [OPTION_ALL, OPTION_TEXT, OPTION_XML]
14
14
 
15
- def explain(arel, binds = [])
15
+ def explain(arel, binds = [], options = [])
16
16
  sql = to_sql(arel)
17
- result = with_showplan_on { sp_executesql(sql, "EXPLAIN", binds) }
17
+ result = with_showplan_on { internal_exec_query(sql, "EXPLAIN", binds) }
18
18
  printer = showplan_printer.new(result)
19
19
  printer.pp
20
20
  end
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
 
31
31
  def set_showplan_option(enable = true)
32
32
  sql = "SET #{showplan_option} #{enable ? 'ON' : 'OFF'}"
33
- raw_connection_do(sql)
33
+ raw_execute(sql, "SCHEMA")
34
34
  rescue Exception
35
35
  raise ActiveRecordError, "#{showplan_option} could not be turned #{enable ? 'ON' : 'OFF'}, perhaps you do not have SHOWPLAN permissions?"
36
36
  end
@@ -113,6 +113,12 @@ module ActiveRecord
113
113
 
114
114
  super
115
115
  end
116
+
117
+ private
118
+
119
+ def valid_column_definition_options
120
+ super + [:is_identity]
121
+ end
116
122
  end
117
123
 
118
124
  class Table < ActiveRecord::ConnectionAdapters::Table
@@ -5,14 +5,12 @@ require "active_record/connection_adapters/abstract/transaction"
5
5
  module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module SQLServerTransaction
8
- private
8
+ delegate :sqlserver?, to: :connection, prefix: true
9
9
 
10
- def sqlserver?
11
- connection.respond_to?(:sqlserver?) && connection.sqlserver?
12
- end
10
+ private
13
11
 
14
12
  def current_isolation_level
15
- return unless sqlserver?
13
+ return unless connection_sqlserver?
16
14
 
17
15
  level = connection.user_options_isolation_level
18
16
  # When READ_COMMITTED_SNAPSHOT is set to ON,
@@ -50,7 +48,7 @@ module ActiveRecord
50
48
  private
51
49
 
52
50
  def reset_starting_isolation_level
53
- if sqlserver? && starting_isolation_level
51
+ if connection_sqlserver? && starting_isolation_level
54
52
  connection.set_transaction_isolation_level(starting_isolation_level)
55
53
  end
56
54
  end
@@ -36,6 +36,16 @@ module ActiveRecord
36
36
  self.class == other.class && value == other.value
37
37
  end
38
38
  alias :== :eql?
39
+
40
+ def self.from_msgpack_ext(string)
41
+ type, value = string.chomp!("msgpack_ext").split(',')
42
+
43
+ Data.new(value, type.constantize)
44
+ end
45
+
46
+ def to_msgpack_ext
47
+ [type.class.to_s, value].join(',') + "msgpack_ext"
48
+ end
39
49
  end
40
50
  end
41
51
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tiny_tds"
3
4
  require "base64"
4
5
  require "active_record"
5
6
  require "arel_sqlserver"
@@ -11,11 +12,13 @@ require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber
11
12
  require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
12
13
  require "active_record/connection_adapters/sqlserver/core_ext/finder_methods"
13
14
  require "active_record/connection_adapters/sqlserver/core_ext/preloader"
15
+ require "active_record/connection_adapters/sqlserver/core_ext/abstract_adapter"
14
16
  require "active_record/connection_adapters/sqlserver/version"
15
17
  require "active_record/connection_adapters/sqlserver/type"
16
18
  require "active_record/connection_adapters/sqlserver/database_limits"
17
19
  require "active_record/connection_adapters/sqlserver/database_statements"
18
20
  require "active_record/connection_adapters/sqlserver/database_tasks"
21
+ require "active_record/connection_adapters/sqlserver/savepoints"
19
22
  require "active_record/connection_adapters/sqlserver/transaction"
20
23
  require "active_record/connection_adapters/sqlserver/errors"
21
24
  require "active_record/connection_adapters/sqlserver/schema_creation"
@@ -39,7 +42,8 @@ module ActiveRecord
39
42
  SQLServer::Showplan,
40
43
  SQLServer::SchemaStatements,
41
44
  SQLServer::DatabaseLimits,
42
- SQLServer::DatabaseTasks
45
+ SQLServer::DatabaseTasks,
46
+ SQLServer::Savepoints
43
47
 
44
48
  ADAPTER_NAME = "SQLServer".freeze
45
49
 
@@ -77,93 +81,38 @@ module ActiveRecord
77
81
  end
78
82
 
79
83
  def new_client(config)
80
- case config[:mode]
81
- when :dblib
82
- require "tiny_tds"
83
- dblib_connect(config)
84
+ TinyTds::Client.new(config)
85
+ rescue TinyTds::Error => error
86
+ if error.message.match(/database .* does not exist/i)
87
+ raise ActiveRecord::NoDatabaseError
84
88
  else
85
- raise ArgumentError, "Unknown connection mode in #{config.inspect}."
89
+ raise
86
90
  end
87
91
  end
88
92
 
89
- def dblib_connect(config)
90
- TinyTds::Client.new(
91
- dataserver: config[:dataserver],
92
- host: config[:host],
93
- port: config[:port],
94
- username: config[:username],
95
- password: config[:password],
96
- database: config[:database],
97
- tds_version: config[:tds_version] || "7.3",
98
- appname: config_appname(config),
99
- login_timeout: config_login_timeout(config),
100
- timeout: config_timeout(config),
101
- encoding: config_encoding(config),
102
- azure: config[:azure],
103
- contained: config[:contained]
104
- ).tap do |client|
105
- if config[:azure]
106
- client.execute("SET ANSI_NULLS ON").do
107
- client.execute("SET ANSI_NULL_DFLT_ON ON").do
108
- client.execute("SET ANSI_PADDING ON").do
109
- client.execute("SET ANSI_WARNINGS ON").do
110
- else
111
- client.execute("SET ANSI_DEFAULTS ON").do
112
- end
113
- client.execute("SET QUOTED_IDENTIFIER ON").do
114
- client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
115
- client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
116
- client.execute("SET TEXTSIZE 2147483647").do
117
- client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
118
- end
119
- rescue TinyTds::Error => e
120
- raise ActiveRecord::NoDatabaseError if e.message.match(/database .* does not exist/i)
121
- raise e
122
- end
123
-
124
- def config_appname(config)
125
- if instance_methods.include?(:configure_application_name)
126
- ActiveSupport::Deprecation.warn <<~MSG.squish
127
- Configuring the application name used by TinyTDS by overriding the
128
- `ActiveRecord::ConnectionAdapters::SQLServerAdapter#configure_application_name`
129
- instance method is no longer supported. The application name should configured
130
- using the `appname` setting in the `database.yml` file instead. Consult the
131
- README for further information."
132
- MSG
133
- end
134
-
135
- config[:appname] || rails_application_name
136
- end
137
-
138
93
  def rails_application_name
139
94
  Rails.application.class.name.split("::").first
140
95
  rescue
141
96
  nil # Might not be in a Rails context so we fallback to `nil`.
142
97
  end
98
+ end
143
99
 
144
- def config_login_timeout(config)
145
- config[:login_timeout].present? ? config[:login_timeout].to_i : nil
146
- end
147
-
148
- def config_timeout(config)
149
- config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
150
- end
100
+ def initialize(...)
101
+ super
151
102
 
152
- def config_encoding(config)
153
- config[:encoding].present? ? config[:encoding] : nil
154
- end
155
- end
103
+ @config[:tds_version] = "7.3" unless @config[:tds_version]
104
+ @config[:appname] = self.class.rails_application_name unless @config[:appname]
105
+ @config[:login_timeout] = @config[:login_timeout].present? ? @config[:login_timeout].to_i : nil
106
+ @config[:timeout] = @config[:timeout].present? ? @config[:timeout].to_i / 1000 : nil
107
+ @config[:encoding] = @config[:encoding].present? ? @config[:encoding] : nil
156
108
 
157
- def initialize(connection, logger, _connection_options, config)
158
- super(connection, logger, config)
159
- @connection_options = config
160
- perform_connection_configuration
109
+ @connection_parameters ||= @config
161
110
  end
162
111
 
163
112
  # === Abstract Adapter ========================================== #
164
113
 
165
114
  def arel_visitor
166
- Arel::Visitors::SQLServer.new self
115
+ Arel::Visitors::SQLServer.new(self)
167
116
  end
168
117
 
169
118
  def valid_type?(type)
@@ -171,13 +120,7 @@ module ActiveRecord
171
120
  end
172
121
 
173
122
  def schema_creation
174
- SQLServer::SchemaCreation.new self
175
- end
176
-
177
- def self.database_exists?(config)
178
- !!ActiveRecord::Base.sqlserver_connection(config)
179
- rescue ActiveRecord::NoDatabaseError
180
- false
123
+ SQLServer::SchemaCreation.new(self)
181
124
  end
182
125
 
183
126
  def supports_ddl_transactions?
@@ -229,7 +172,7 @@ module ActiveRecord
229
172
  end
230
173
 
231
174
  def supports_json?
232
- @version_year >= 2016
175
+ version_year >= 2016
233
176
  end
234
177
 
235
178
  def supports_comments?
@@ -248,12 +191,16 @@ module ActiveRecord
248
191
  true
249
192
  end
250
193
 
194
+ def supports_common_table_expressions?
195
+ true
196
+ end
197
+
251
198
  def supports_lazy_transactions?
252
199
  true
253
200
  end
254
201
 
255
202
  def supports_in_memory_oltp?
256
- @version_year >= 2014
203
+ version_year >= 2014
257
204
  end
258
205
 
259
206
  def supports_insert_returning?
@@ -272,50 +219,52 @@ module ActiveRecord
272
219
  false
273
220
  end
274
221
 
222
+ def return_value_after_insert?(column) # :nodoc:
223
+ column.is_primary? || column.is_identity?
224
+ end
225
+
275
226
  def disable_referential_integrity
276
227
  tables = tables_with_referential_integrity
277
- tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
228
+ tables.each { |t| execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
278
229
  yield
279
230
  ensure
280
- tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} CHECK CONSTRAINT ALL" }
231
+ tables.each { |t| execute "ALTER TABLE #{quote_table_name(t)} CHECK CONSTRAINT ALL" }
281
232
  end
282
233
 
283
234
  # === Abstract Adapter (Connection Management) ================== #
284
235
 
285
236
  def active?
286
- return false unless @connection
287
-
288
- raw_connection_do "SELECT 1"
289
- true
237
+ @raw_connection&.active?
290
238
  rescue *connection_errors
291
239
  false
292
240
  end
293
241
 
294
- def reconnect!
295
- super
296
- disconnect!
242
+ def reconnect
243
+ @raw_connection&.close rescue nil
244
+ @raw_connection = nil
245
+ @spid = nil
246
+ @collation = nil
247
+
297
248
  connect
298
249
  end
299
250
 
300
251
  def disconnect!
301
252
  super
302
- case @connection_options[:mode]
303
- when :dblib
304
- @connection.close rescue nil
305
- end
306
- @connection = nil
253
+
254
+ @raw_connection&.close rescue nil
255
+ @raw_connection = nil
307
256
  @spid = nil
308
257
  @collation = nil
309
258
  end
310
259
 
311
- def clear_cache!
260
+ def clear_cache!(...)
312
261
  @view_information = nil
313
262
  super
314
263
  end
315
264
 
316
265
  def reset!
317
266
  reset_transaction
318
- do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
267
+ execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
319
268
  end
320
269
 
321
270
  # === Abstract Adapter (Misc Support) =========================== #
@@ -356,7 +305,7 @@ module ActiveRecord
356
305
  end
357
306
 
358
307
  def database_prefix
359
- @connection_options[:database_prefix]
308
+ @connection_parameters[:database_prefix]
360
309
  end
361
310
 
362
311
  def database_prefix_identifier(name)
@@ -372,7 +321,7 @@ module ActiveRecord
372
321
  end
373
322
 
374
323
  def inspect
375
- "#<#{self.class} version: #{version}, mode: #{@connection_options[:mode]}, azure: #{sqlserver_azure?.inspect}>"
324
+ "#<#{self.class} version: #{version}, azure: #{sqlserver_azure?.inspect}>"
376
325
  end
377
326
 
378
327
  def combine_bind_parameters(from_clause: [], join_clause: [], where_clause: [], having_clause: [], limit: nil, offset: nil)
@@ -386,6 +335,12 @@ module ActiveRecord
386
335
  version_year
387
336
  end
388
337
 
338
+ def check_version # :nodoc:
339
+ if schema_cache.database_version < 2012
340
+ raise "Your version of SQL Server (#{database_version}) is too old. SQL Server Active Record supports 2012 or higher."
341
+ end
342
+ end
343
+
389
344
  class << self
390
345
  protected
391
346
 
@@ -513,7 +468,7 @@ module ActiveRecord
513
468
  # === SQLServer Specific (Connection Management) ================ #
514
469
 
515
470
  def connection_errors
516
- @connection_errors ||= [].tap do |errors|
471
+ @raw_connection_errors ||= [].tap do |errors|
517
472
  errors << TinyTds::Error if defined?(TinyTds::Error)
518
473
  end
519
474
  end
@@ -534,32 +489,44 @@ module ActiveRecord
534
489
  end
535
490
 
536
491
  def version_year
537
- return 2016 if sqlserver_version =~ /vNext/
538
-
539
- /SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
540
- rescue StandardError
541
- 2016
492
+ @version_year ||= begin
493
+ if sqlserver_version =~ /vNext/
494
+ 2016
495
+ else
496
+ /SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
497
+ end
498
+ rescue StandardError
499
+ 2016
500
+ end
542
501
  end
543
502
 
544
503
  def sqlserver_version
545
- @sqlserver_version ||= _raw_select("SELECT @@version", fetch: :rows).first.first.to_s
504
+ @sqlserver_version ||= _raw_select("SELECT @@version", @raw_connection, fetch: :rows).first.first.to_s
546
505
  end
547
506
 
548
507
  private
549
508
 
550
509
  def connect
551
- @connection = self.class.new_client(@connection_options)
552
- perform_connection_configuration
510
+ @raw_connection = self.class.new_client(@connection_parameters)
553
511
  end
554
512
 
555
- def perform_connection_configuration
556
- configure_connection_defaults
557
- configure_connection if self.respond_to?(:configure_connection)
558
- end
513
+ def configure_connection
514
+ if @config[:azure]
515
+ @raw_connection.execute("SET ANSI_NULLS ON").do
516
+ @raw_connection.execute("SET ANSI_NULL_DFLT_ON ON").do
517
+ @raw_connection.execute("SET ANSI_PADDING ON").do
518
+ @raw_connection.execute("SET ANSI_WARNINGS ON").do
519
+ else
520
+ @raw_connection.execute("SET ANSI_DEFAULTS ON").do
521
+ end
522
+
523
+ @raw_connection.execute("SET QUOTED_IDENTIFIER ON").do
524
+ @raw_connection.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
525
+ @raw_connection.execute("SET IMPLICIT_TRANSACTIONS OFF").do
526
+ @raw_connection.execute("SET TEXTSIZE 2147483647").do
527
+ @raw_connection.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
559
528
 
560
- def configure_connection_defaults
561
- @spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
562
- @version_year = version_year
529
+ @spid = _raw_select("SELECT @@SPID", @raw_connection, fetch: :rows).first.first
563
530
 
564
531
  initialize_dateformatter
565
532
  use_database
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  def is_identity?
18
18
  is_identity
19
19
  end
20
+ alias_method :auto_incremented_by_db?, :is_identity?
20
21
 
21
22
  def is_primary?
22
23
  is_primary