activerecord-sqlserver-adapter 8.0.10 → 8.1.0

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +1 -1
  3. data/.github/workflows/ci.yml +34 -3
  4. data/CHANGELOG.md +14 -68
  5. data/Dockerfile.ci +1 -1
  6. data/Gemfile +7 -9
  7. data/Guardfile +2 -2
  8. data/README.md +33 -13
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/activerecord-sqlserver-adapter.gemspec +15 -16
  12. data/compose.ci.yaml +8 -1
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -1
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +1 -2
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +1 -1
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +4 -4
  17. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +118 -83
  18. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +3 -4
  19. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +7 -7
  20. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +24 -12
  21. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +17 -8
  22. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +162 -156
  23. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
  24. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +5 -5
  25. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +2 -7
  26. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +3 -1
  27. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +3 -3
  28. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +3 -3
  29. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +3 -4
  30. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +1 -1
  31. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +4 -6
  32. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +1 -1
  33. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +0 -2
  34. data/lib/active_record/connection_adapters/sqlserver/utils.rb +10 -12
  35. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +118 -66
  36. data/lib/active_record/connection_adapters/sqlserver_column.rb +17 -9
  37. data/lib/active_record/tasks/sqlserver_database_tasks.rb +5 -5
  38. data/lib/arel/visitors/sqlserver.rb +55 -26
  39. data/test/cases/active_schema_test_sqlserver.rb +45 -23
  40. data/test/cases/adapter_test_sqlserver.rb +72 -59
  41. data/test/cases/coerced_tests.rb +365 -161
  42. data/test/cases/column_test_sqlserver.rb +328 -316
  43. data/test/cases/connection_test_sqlserver.rb +15 -11
  44. data/test/cases/enum_test_sqlserver.rb +8 -9
  45. data/test/cases/execute_procedure_test_sqlserver.rb +1 -1
  46. data/test/cases/fetch_test_sqlserver.rb +1 -1
  47. data/test/cases/helper_sqlserver.rb +7 -3
  48. data/test/cases/index_test_sqlserver.rb +8 -6
  49. data/test/cases/insert_all_test_sqlserver.rb +3 -28
  50. data/test/cases/json_test_sqlserver.rb +8 -8
  51. data/test/cases/lateral_test_sqlserver.rb +2 -2
  52. data/test/cases/migration_test_sqlserver.rb +12 -12
  53. data/test/cases/pessimistic_locking_test_sqlserver.rb +6 -6
  54. data/test/cases/primary_keys_test_sqlserver.rb +4 -4
  55. data/test/cases/rake_test_sqlserver.rb +15 -7
  56. data/test/cases/schema_dumper_test_sqlserver.rb +109 -113
  57. data/test/cases/schema_test_sqlserver.rb +7 -7
  58. data/test/cases/transaction_test_sqlserver.rb +6 -8
  59. data/test/cases/trigger_test_sqlserver.rb +1 -1
  60. data/test/cases/utils_test_sqlserver.rb +3 -3
  61. data/test/cases/view_test_sqlserver.rb +12 -8
  62. data/test/cases/virtual_column_test_sqlserver.rb +113 -0
  63. data/test/migrations/create_clients_and_change_column_collation.rb +2 -2
  64. data/test/models/sqlserver/edge_schema.rb +2 -2
  65. data/test/schema/sqlserver_specific_schema.rb +49 -37
  66. data/test/support/coerceable_test_sqlserver.rb +10 -10
  67. data/test/support/connection_reflection.rb +0 -5
  68. data/test/support/core_ext/backtrace_cleaner.rb +36 -0
  69. data/test/support/query_assertions.rb +6 -6
  70. data/test/support/rake_helpers.rb +6 -10
  71. metadata +12 -107
@@ -11,13 +11,12 @@ module ActiveRecord
11
11
  "datetime"
12
12
  end
13
13
 
14
- def serialize(value)
14
+ def serialize(_value)
15
15
  value = super
16
16
  return value unless value.acts_like?(:time)
17
17
 
18
18
  datetime = "#{value.to_formatted_s(:_sqlserver_datetime)}.#{quote_fractional(value)}"
19
-
20
- Data.new datetime, self
19
+ Data.new(datetime, self)
21
20
  end
22
21
 
23
22
  def deserialize(value)
@@ -37,7 +36,7 @@ module ActiveRecord
37
36
  def fast_string_to_time(string)
38
37
  time = ActiveSupport::TimeZone["UTC"].strptime(string, fast_string_to_time_format)
39
38
  new_time(time.year, time.month, time.day, time.hour,
40
- time.min, time.sec, Rational(time.nsec, 1_000))
39
+ time.min, time.sec, Rational(time.nsec, 1_000))
41
40
  rescue ArgumentError
42
41
  super
43
42
  end
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  def apply_seconds_precision(value)
23
- value.change usec: 0 if value
23
+ value&.change(usec: 0)
24
24
  end
25
25
  end
26
26
  end
@@ -7,13 +7,12 @@ module ActiveRecord
7
7
  class Time < ActiveRecord::Type::Time
8
8
  include TimeValueFractional2
9
9
 
10
- def serialize(value)
10
+ def serialize(_value)
11
11
  value = super
12
12
  return value unless value.acts_like?(:time)
13
13
 
14
14
  time = "#{value.to_formatted_s(:_sqlserver_time)}.#{quote_fractional(value)}"
15
-
16
- Data.new time, self
15
+ Data.new(time, self)
17
16
  end
18
17
 
19
18
  def deserialize(value)
@@ -34,12 +33,11 @@ module ActiveRecord
34
33
 
35
34
  private
36
35
 
37
- def cast_value(value)
36
+ def cast_value(_value)
38
37
  value = super
39
-
40
38
  return value unless value.is_a?(::Time)
41
39
 
42
- value = value.change(year: 2000, month: 01, day: 01)
40
+ value = value.change(year: 2000, month: 0o1, day: 0o1)
43
41
  apply_seconds_precision(value)
44
42
  end
45
43
 
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
 
58
58
  def seconds_precision(value)
59
59
  seconds = super
60
- seconds > fractional_max ? fractional_scale_max : seconds
60
+ (seconds > fractional_max) ? fractional_scale_max : seconds
61
61
  end
62
62
 
63
63
  def fractional_property
@@ -7,8 +7,6 @@ module ActiveRecord
7
7
  class Uuid < String
8
8
  ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
9
9
 
10
- alias_method :serialize, :deserialize
11
-
12
10
  def type
13
11
  :uuid
14
12
  end
@@ -10,8 +10,8 @@ module ActiveRecord
10
10
  # Inspired from Rails PostgreSQL::Name adapter object in their own Utils.
11
11
  class Name
12
12
  UNQUOTED_SCANNER = /\]?\./
13
- QUOTED_SCANNER = /\A\[.*?\]\./
14
- QUOTED_CHECKER = /\A\[/
13
+ QUOTED_SCANNER = /\A\[.*?\]\./
14
+ QUOTED_CHECKER = /\A\[/
15
15
 
16
16
  attr_reader :server, :database, :schema, :object
17
17
  attr_reader :raw_name
@@ -38,7 +38,7 @@ module ActiveRecord
38
38
  end
39
39
 
40
40
  def fully_qualified_database_quoted
41
- [server_quoted, database_quoted].compact.join('.')
41
+ [server_quoted, database_quoted].compact.join(".")
42
42
  end
43
43
 
44
44
  def fully_qualified?
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def quoted
68
- parts.map { |p| quote(p) if p }.join('.')
68
+ parts.map { |p| quote(p) if p }.join(".")
69
69
  end
70
70
 
71
71
  def quoted_raw
@@ -107,32 +107,30 @@ module ActiveRecord
107
107
  @schema = @parts.first
108
108
  end
109
109
  rest = scanner.rest
110
- rest = rest.start_with?(".") ? rest[1..-1] : rest[0..-1]
110
+ rest = rest.start_with?(".") ? rest[1..] : rest[0..]
111
111
  @object = unquote(rest)
112
112
  @parts << @object
113
113
  end
114
114
 
115
115
  def quote(part)
116
- part =~ /\A\[.*\]\z/ ? part : "[#{part.to_s.gsub(']', ']]')}]"
116
+ /\A\[.*\]\z/.match?(part) ? part : "[#{part.to_s.gsub("]", "]]")}]"
117
117
  end
118
118
 
119
119
  def unquote(part)
120
- if part && part.start_with?("[")
120
+ if part&.start_with?("[")
121
121
  part[1..-2]
122
122
  else
123
123
  part
124
124
  end
125
125
  end
126
126
 
127
- def parts
128
- @parts
129
- end
127
+ attr_reader :parts
130
128
  end
131
129
 
132
130
  extend self
133
131
 
134
132
  def quote_string(s)
135
- s.to_s.gsub(/\'/, "''")
133
+ s.to_s.gsub("'", "''")
136
134
  end
137
135
 
138
136
  def quote_string_single(s)
@@ -148,7 +146,7 @@ module ActiveRecord
148
146
  end
149
147
 
150
148
  def unquote_string(s)
151
- s.to_s.gsub(/\'\'/, "'")
149
+ s.to_s.gsub("''", "'")
152
150
  end
153
151
 
154
152
  def extract_identifiers(name)
@@ -36,16 +36,16 @@ module ActiveRecord
36
36
  register "sqlserver", "ActiveRecord::ConnectionAdapters::SQLServerAdapter", "active_record/connection_adapters/sqlserver_adapter"
37
37
 
38
38
  class SQLServerAdapter < AbstractAdapter
39
- include SQLServer::Version,
40
- SQLServer::Quoting,
41
- SQLServer::DatabaseStatements,
42
- SQLServer::Showplan,
43
- SQLServer::SchemaStatements,
44
- SQLServer::DatabaseLimits,
45
- SQLServer::DatabaseTasks,
46
- SQLServer::Savepoints
39
+ include SQLServer::Savepoints
40
+ include SQLServer::DatabaseTasks
41
+ include SQLServer::DatabaseLimits
42
+ include SQLServer::SchemaStatements
43
+ include SQLServer::Showplan
44
+ include SQLServer::DatabaseStatements
45
+ include SQLServer::Quoting
46
+ include SQLServer::Version
47
47
 
48
- ADAPTER_NAME = "SQLServer".freeze
48
+ ADAPTER_NAME = "SQLServer"
49
49
 
50
50
  # Default precision for 'time' (See https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql)
51
51
  DEFAULT_TIME_PRECISION = 7
@@ -62,18 +62,52 @@ module ActiveRecord
62
62
  self.use_output_inserted = true
63
63
  self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
64
64
 
65
+ NATIVE_DATABASE_TYPES = {
66
+ primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
67
+ primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
68
+ integer: {name: "int", limit: 4},
69
+ bigint: {name: "bigint"},
70
+ boolean: {name: "bit"},
71
+ decimal: {name: "decimal"},
72
+ money: {name: "money"},
73
+ smallmoney: {name: "smallmoney"},
74
+ float: {name: "float"},
75
+ real: {name: "real"},
76
+ date: {name: "date"},
77
+ datetime: {name: "datetime"},
78
+ datetime2: {name: "datetime2"},
79
+ datetimeoffset: {name: "datetimeoffset"},
80
+ smalldatetime: {name: "smalldatetime"},
81
+ timestamp: {name: "datetime2(6)"},
82
+ time: {name: "time"},
83
+ char: {name: "char"},
84
+ varchar: {name: "varchar", limit: 8000},
85
+ varchar_max: {name: "varchar(max)"},
86
+ text_basic: {name: "text"},
87
+ nchar: {name: "nchar"},
88
+ string: {name: "nvarchar", limit: 4000},
89
+ text: {name: "nvarchar(max)"},
90
+ ntext: {name: "ntext"},
91
+ binary_basic: {name: "binary"},
92
+ varbinary: {name: "varbinary", limit: 8000},
93
+ binary: {name: "varbinary(max)"},
94
+ uuid: {name: "uniqueidentifier"},
95
+ ss_timestamp: {name: "timestamp"},
96
+ json: {name: "nvarchar(max)"}
97
+ }
98
+
65
99
  class << self
66
100
  def dbconsole(config, options = {})
67
101
  sqlserver_config = config.configuration_hash
68
102
  args = []
69
103
 
70
- args += ["-d", "#{config.database}"] if config.database
71
- args += ["-U", "#{sqlserver_config[:username]}"] if sqlserver_config[:username]
72
- args += ["-P", "#{sqlserver_config[:password]}"] if sqlserver_config[:password]
104
+ args += ["-d", config.database.to_s] if config.database
105
+ args += ["-U", sqlserver_config[:username].to_s] if sqlserver_config[:username]
106
+ args += ["-P", sqlserver_config[:password].to_s] if sqlserver_config[:password]
73
107
 
74
108
  if sqlserver_config[:host]
75
- host_arg = +"tcp:#{sqlserver_config[:host]}"
76
- host_arg << ",#{sqlserver_config[:port]}" if sqlserver_config[:port]
109
+ host_arg = "tcp:#{sqlserver_config[:host]}"
110
+ host_arg += ",#{sqlserver_config[:port]}" if sqlserver_config[:port]
77
111
  args += ["-S", host_arg]
78
112
  end
79
113
 
@@ -83,7 +117,7 @@ module ActiveRecord
83
117
  def new_client(config)
84
118
  TinyTds::Client.new(config)
85
119
  rescue TinyTds::Error => error
86
- if error.message.match(/database .* does not exist/i)
120
+ if /database .* does not exist/i.match?(error.message)
87
121
  raise ActiveRecord::NoDatabaseError
88
122
  else
89
123
  raise
@@ -95,6 +129,10 @@ module ActiveRecord
95
129
  rescue
96
130
  nil # Might not be in a Rails context so we fallback to `nil`.
97
131
  end
132
+
133
+ def native_database_types # :nodoc:
134
+ NATIVE_DATABASE_TYPES
135
+ end
98
136
  end
99
137
 
100
138
  def initialize(...)
@@ -143,6 +181,10 @@ module ActiveRecord
143
181
  true
144
182
  end
145
183
 
184
+ def supports_index_include?
185
+ true
186
+ end
187
+
146
188
  def supports_expression_index?
147
189
  false
148
190
  end
@@ -223,6 +265,10 @@ module ActiveRecord
223
265
  false
224
266
  end
225
267
 
268
+ def supports_virtual_columns?
269
+ true
270
+ end
271
+
226
272
  def return_value_after_insert?(column) # :nodoc:
227
273
  column.is_primary? || column.is_identity?
228
274
  end
@@ -238,13 +284,20 @@ module ActiveRecord
238
284
  # === Abstract Adapter (Connection Management) ================== #
239
285
 
240
286
  def active?
241
- @raw_connection&.active?
287
+ if @raw_connection&.active?
288
+ verified!
289
+ true
290
+ end
242
291
  rescue *connection_errors
243
292
  false
244
293
  end
245
294
 
246
295
  def reconnect
247
- @raw_connection&.close rescue nil
296
+ begin
297
+ @raw_connection&.close
298
+ rescue
299
+ nil
300
+ end
248
301
  @raw_connection = nil
249
302
  @spid = nil
250
303
  @collation = nil
@@ -255,7 +308,11 @@ module ActiveRecord
255
308
  def disconnect!
256
309
  super
257
310
 
258
- @raw_connection&.close rescue nil
311
+ begin
312
+ @raw_connection&.close
313
+ rescue
314
+ nil
315
+ end
259
316
  @raw_connection = nil
260
317
  @spid = nil
261
318
  @collation = nil
@@ -324,13 +381,6 @@ module ActiveRecord
324
381
  self.class::VERSION
325
382
  end
326
383
 
327
- def combine_bind_parameters(from_clause: [], join_clause: [], where_clause: [], having_clause: [], limit: nil, offset: nil)
328
- result = from_clause + join_clause + where_clause + having_clause
329
- result << offset if offset
330
- result << limit if limit
331
- result
332
- end
333
-
334
384
  def get_database_version # :nodoc:
335
385
  version_year
336
386
  end
@@ -345,21 +395,21 @@ module ActiveRecord
345
395
  protected
346
396
 
347
397
  def initialize_type_map(m)
348
- m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
398
+ m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
349
399
 
350
400
  # Exact Numerics
351
- register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
352
- m.alias_type "bigint", "bigint(8)"
353
- register_class_with_limit m, "int(4)", SQLServer::Type::Integer
354
- m.alias_type "integer", "int(4)"
355
- m.alias_type "int", "int(4)"
356
- register_class_with_limit m, "smallint(2)", SQLServer::Type::SmallInteger
357
- m.alias_type "smallint", "smallint(2)"
358
- register_class_with_limit m, "tinyint(1)", SQLServer::Type::TinyInteger
359
- m.alias_type "tinyint", "tinyint(1)"
360
- m.register_type "bit", SQLServer::Type::Boolean.new
361
- m.register_type %r{\Adecimal}i do |sql_type|
362
- scale = extract_scale(sql_type)
401
+ register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
402
+ m.alias_type "bigint", "bigint(8)"
403
+ register_class_with_limit m, "int(4)", SQLServer::Type::Integer
404
+ m.alias_type "integer", "int(4)"
405
+ m.alias_type "int", "int(4)"
406
+ register_class_with_limit m, "smallint(2)", SQLServer::Type::SmallInteger
407
+ m.alias_type "smallint", "smallint(2)"
408
+ register_class_with_limit m, "tinyint(1)", SQLServer::Type::TinyInteger
409
+ m.alias_type "tinyint", "tinyint(1)"
410
+ m.register_type "bit", SQLServer::Type::Boolean.new
411
+ m.register_type %r{\Adecimal}i do |sql_type|
412
+ scale = extract_scale(sql_type)
363
413
  precision = extract_precision(sql_type)
364
414
  if scale == 0
365
415
  SQLServer::Type::DecimalWithoutScale.new(precision: precision)
@@ -367,17 +417,17 @@ module ActiveRecord
367
417
  SQLServer::Type::Decimal.new(precision: precision, scale: scale)
368
418
  end
369
419
  end
370
- m.alias_type %r{\Anumeric}i, "decimal"
371
- m.register_type "money", SQLServer::Type::Money.new
372
- m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
420
+ m.alias_type %r{\Anumeric}i, "decimal"
421
+ m.register_type "money", SQLServer::Type::Money.new
422
+ m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
373
423
 
374
424
  # Approximate Numerics
375
- m.register_type "float", SQLServer::Type::Float.new
376
- m.register_type "real", SQLServer::Type::Real.new
425
+ m.register_type "float", SQLServer::Type::Float.new
426
+ m.register_type "real", SQLServer::Type::Real.new
377
427
 
378
428
  # Date and Time
379
- m.register_type "date", SQLServer::Type::Date.new
380
- m.register_type %r{\Adatetime} do |sql_type|
429
+ m.register_type "date", SQLServer::Type::Date.new
430
+ m.register_type %r{\Adatetime} do |sql_type|
381
431
  precision = extract_precision(sql_type)
382
432
  if precision
383
433
  SQLServer::Type::DateTime2.new precision: precision
@@ -389,34 +439,34 @@ module ActiveRecord
389
439
  precision = extract_precision(sql_type)
390
440
  SQLServer::Type::DateTimeOffset.new precision: precision
391
441
  end
392
- m.register_type "smalldatetime", SQLServer::Type::SmallDateTime.new
393
- m.register_type %r{\Atime}i do |sql_type|
442
+ m.register_type "smalldatetime", SQLServer::Type::SmallDateTime.new
443
+ m.register_type %r{\Atime}i do |sql_type|
394
444
  precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
395
445
  SQLServer::Type::Time.new precision: precision
396
446
  end
397
447
 
398
448
  # Character Strings
399
- register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
400
- register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
401
- m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
402
- m.register_type "text", SQLServer::Type::Text.new
449
+ register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
450
+ register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
451
+ m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
452
+ m.register_type "text", SQLServer::Type::Text.new
403
453
 
404
454
  # Unicode Character Strings
405
- register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
406
- register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
407
- m.alias_type "string", "nvarchar(4000)"
408
- m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
409
- m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
410
- m.register_type "ntext", SQLServer::Type::UnicodeText.new
455
+ register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
456
+ register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
457
+ m.alias_type "string", "nvarchar(4000)"
458
+ m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
459
+ m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
460
+ m.register_type "ntext", SQLServer::Type::UnicodeText.new
411
461
 
412
462
  # Binary Strings
413
- register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
414
- register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
415
- m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
463
+ register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
464
+ register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
465
+ m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
416
466
 
417
467
  # Other Data Types
418
- m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
419
- m.register_type "timestamp", SQLServer::Type::Timestamp.new
468
+ m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
469
+ m.register_type "timestamp", SQLServer::Type::Timestamp.new
420
470
  end
421
471
  end
422
472
 
@@ -452,6 +502,8 @@ module ActiveRecord
452
502
  NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
453
503
  when /Arithmetic overflow error/
454
504
  RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
505
+ when /statement conflicted with the CHECK constraint/
506
+ CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
455
507
  else
456
508
  super
457
509
  end
@@ -471,10 +523,10 @@ module ActiveRecord
471
523
 
472
524
  [a, b, c].each { |f| f.upcase! if f == "y" }
473
525
  dateformat = "%#{a}-%#{b}-%#{c}"
474
- ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
475
- ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
476
- ::Time::DATE_FORMATS[:_sqlserver_time] = "%H:%M:%S"
477
- ::Time::DATE_FORMATS[:_sqlserver_datetime] = "#{dateformat} %H:%M:%S"
526
+ ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
527
+ ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
528
+ ::Time::DATE_FORMATS[:_sqlserver_time] = "%H:%M:%S"
529
+ ::Time::DATE_FORMATS[:_sqlserver_datetime] = "#{dateformat} %H:%M:%S"
478
530
  ::Time::DATE_FORMATS[:_sqlserver_datetimeoffset] = lambda { |time|
479
531
  time.strftime "#{dateformat} %H:%M:%S.%9N #{time.formatted_offset}"
480
532
  }
@@ -6,12 +6,13 @@ module ActiveRecord
6
6
  class Column < ConnectionAdapters::Column
7
7
  delegate :is_identity, :is_primary, :table_name, :ordinal_position, to: :sql_type_metadata
8
8
 
9
- def initialize(*, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil, **)
9
+ def initialize(*, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil, generated_type: nil, **)
10
10
  super
11
11
  @is_identity = is_identity
12
12
  @is_primary = is_primary
13
13
  @table_name = table_name
14
14
  @ordinal_position = ordinal_position
15
+ @generated_type = generated_type
15
16
  end
16
17
 
17
18
  def is_identity?
@@ -28,7 +29,19 @@ module ActiveRecord
28
29
  end
29
30
 
30
31
  def case_sensitive?
31
- collation && collation.match(/_CS/)
32
+ collation&.match(/_CS/)
33
+ end
34
+
35
+ def virtual?
36
+ @generated_type.present?
37
+ end
38
+
39
+ def virtual_stored?
40
+ @generated_type == :stored
41
+ end
42
+
43
+ def has_default?
44
+ super && !virtual?
32
45
  end
33
46
 
34
47
  def init_with(coder)
@@ -55,15 +68,10 @@ module ActiveRecord
55
68
  table_name == other.table_name &&
56
69
  ordinal_position == other.ordinal_position
57
70
  end
58
- alias :eql? :==
71
+ alias_method :eql?, :==
59
72
 
60
73
  def hash
61
- Column.hash ^
62
- super.hash ^
63
- is_identity?.hash ^
64
- is_primary?.hash ^
65
- table_name.hash ^
66
- ordinal_position.hash
74
+ [Column, super, is_identity?, is_primary?, table_name, ordinal_position].hash
67
75
  end
68
76
 
69
77
  private
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  end
29
29
  establish_connection(configuration)
30
30
  rescue ActiveRecord::StatementInvalid => e
31
- if /database .* already exists/i === e.message
31
+ if /database .* already exists/i.match?(e.message)
32
32
  raise DatabaseAlreadyExists
33
33
  else
34
34
  raise
@@ -68,7 +68,7 @@ module ActiveRecord
68
68
  "-D #{Shellwords.escape(configuration_hash[:database])}",
69
69
  "-U #{Shellwords.escape(configuration_hash[:username])}",
70
70
  "-P #{Shellwords.escape(configuration_hash[:password])}",
71
- "-o #{Shellwords.escape(filename)}",
71
+ "-o #{Shellwords.escape(filename)}"
72
72
  ]
73
73
  table_args = connection.tables.map { |t| Shellwords.escape(t) }
74
74
  command.concat(table_args)
@@ -79,8 +79,8 @@ module ActiveRecord
79
79
  dump = File.read(filename)
80
80
  dump.gsub!(/^USE .*$\nGO\n/, "") # Strip db USE statements
81
81
  dump.gsub!(/^GO\n/, "") # Strip db GO statements
82
- dump.gsub!(/nvarchar\(8000\)/, "nvarchar(4000)") # Fix nvarchar(8000) column defs
83
- dump.gsub!(/nvarchar\(-1\)/, "nvarchar(max)") # Fix nvarchar(-1) column defs
82
+ dump.gsub!("nvarchar(8000)", "nvarchar(4000)") # Fix nvarchar(8000) column defs
83
+ dump.gsub!("nvarchar(-1)", "nvarchar(max)") # Fix nvarchar(-1) column defs
84
84
  dump.gsub!(/text\(\d+\)/, "text") # Fix text(16) column defs
85
85
  File.open(filename, "w") { |file| file.puts dump }
86
86
  end
@@ -124,7 +124,7 @@ module ActiveRecord
124
124
  def configuration_host_ip(configuration)
125
125
  return nil unless configuration.host
126
126
 
127
- Socket::getaddrinfo(configuration.host, "echo", Socket::AF_INET)[0][3]
127
+ Socket.getaddrinfo(configuration.host, "echo", Socket::AF_INET)[0][3]
128
128
  end
129
129
 
130
130
  def local_ipaddr?(host_ip)