activerecord-sqlserver-adapter 6.0.1 → 6.1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +26 -0
  3. data/CHANGELOG.md +29 -46
  4. data/README.md +32 -3
  5. data/RUNNING_UNIT_TESTS.md +1 -1
  6. data/VERSION +1 -1
  7. data/activerecord-sqlserver-adapter.gemspec +1 -1
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +2 -0
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +5 -10
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +9 -2
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -0
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +2 -0
  13. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  14. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +28 -16
  15. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +8 -7
  16. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -1
  17. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +9 -3
  18. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +31 -9
  19. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
  20. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +0 -1
  21. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +2 -2
  22. data/lib/active_record/connection_adapters/sqlserver/type.rb +1 -0
  23. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -1
  24. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  25. data/lib/active_record/connection_adapters/sqlserver/utils.rb +1 -1
  26. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +100 -69
  27. data/lib/active_record/connection_adapters/sqlserver_column.rb +75 -19
  28. data/lib/active_record/sqlserver_base.rb +9 -15
  29. data/lib/active_record/tasks/sqlserver_database_tasks.rb +17 -14
  30. data/lib/arel/visitors/sqlserver.rb +125 -40
  31. data/test/cases/adapter_test_sqlserver.rb +50 -16
  32. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  33. data/test/cases/coerced_tests.rb +611 -78
  34. data/test/cases/column_test_sqlserver.rb +9 -2
  35. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  36. data/test/cases/execute_procedure_test_sqlserver.rb +9 -0
  37. data/test/cases/fetch_test_sqlserver.rb +18 -0
  38. data/test/cases/in_clause_test_sqlserver.rb +27 -0
  39. data/test/cases/lateral_test_sqlserver.rb +35 -0
  40. data/test/cases/migration_test_sqlserver.rb +51 -0
  41. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  42. data/test/cases/order_test_sqlserver.rb +7 -0
  43. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  44. data/test/cases/rake_test_sqlserver.rb +38 -2
  45. data/test/cases/schema_dumper_test_sqlserver.rb +14 -3
  46. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  47. data/test/models/sqlserver/composite_pk.rb +9 -0
  48. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  49. data/test/schema/sqlserver_specific_schema.rb +25 -0
  50. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  51. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  52. data/test/support/sql_counter_sqlserver.rb +14 -12
  53. metadata +29 -9
  54. data/.travis.yml +0 -23
  55. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -28
@@ -3,9 +3,13 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
- class SchemaCreation < AbstractAdapter::SchemaCreation
6
+ class SchemaCreation < SchemaCreation
7
7
  private
8
8
 
9
+ def supports_index_using?
10
+ false
11
+ end
12
+
9
13
  def visit_TableDefinition(o)
10
14
  if_not_exists = o.if_not_exists
11
15
 
@@ -29,11 +33,28 @@ module ActiveRecord
29
33
  sql
30
34
  end
31
35
 
36
+ def visit_CreateIndexDefinition(o)
37
+ if_not_exists = o.if_not_exists
38
+
39
+ o.if_not_exists = false
40
+
41
+ sql = super
42
+
43
+ if if_not_exists
44
+ sql = "IF NOT EXISTS (SELECT name FROM sysindexes WHERE name = '#{o.index.name}') #{sql}"
45
+ end
46
+
47
+ sql
48
+ end
49
+
32
50
  def add_column_options!(sql, options)
33
51
  sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
34
52
  if options[:null] == false
35
53
  sql << " NOT NULL"
36
54
  end
55
+ if options[:collation].present?
56
+ sql << " COLLATE #{options[:collation]}"
57
+ end
37
58
  if options[:is_identity] == true
38
59
  sql << " IDENTITY(1,1)"
39
60
  end
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  private
16
16
 
17
17
  def explicit_primary_key_default?(column)
18
- column.is_primary? && !column.is_identity?
18
+ column.type == :integer && !column.is_identity?
19
19
  end
20
20
 
21
21
  def schema_limit(column)
@@ -27,11 +27,17 @@ module ActiveRecord
27
27
  def schema_collation(column)
28
28
  return unless column.collation
29
29
 
30
- column.collation if column.collation != @connection.collation
30
+ # use inspect to ensure collation is dumped as string. Without this it's dumped as
31
+ # a constant ('collation: SQL_Latin1_General_CP1_CI_AS')
32
+ collation = column.collation.inspect
33
+ # use inspect to ensure string comparison
34
+ default_collation = @connection.collation.inspect
35
+
36
+ collation if collation != default_collation
31
37
  end
32
38
 
33
39
  def default_primary_key?(column)
34
- super && column.is_primary? && column.is_identity?
40
+ super && column.is_identity?
35
41
  end
36
42
  end
37
43
  end
@@ -27,7 +27,7 @@ module ActiveRecord
27
27
  end
28
28
  end
29
29
  if options[:if_exists] && @version_year < 2016
30
- execute "IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #{quote(table_name)}) DROP TABLE #{quote_table_name(table_name)}"
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
33
33
  end
@@ -51,7 +51,7 @@ module ActiveRecord
51
51
  index[:index_keys].split(",").each do |column|
52
52
  column.strip!
53
53
 
54
- if column.ends_with?("(-)")
54
+ if column.end_with?("(-)")
55
55
  column.gsub! "(-)", ""
56
56
  orders[column] = :desc
57
57
  end
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  end
85
85
 
86
86
  def new_column(name, default, sql_type_metadata, null, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
87
- SQLServerColumn.new(
87
+ SQLServer::Column.new(
88
88
  name,
89
89
  default,
90
90
  sql_type_metadata,
@@ -130,8 +130,9 @@ module ActiveRecord
130
130
  rename_table_indexes(table_name, new_name)
131
131
  end
132
132
 
133
- def remove_column(table_name, column_name, type = nil, options = {})
133
+ def remove_column(table_name, column_name, type = nil, **options)
134
134
  raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_name.is_a? Array
135
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
135
136
 
136
137
  remove_check_constraints(table_name, column_name)
137
138
  remove_default_constraint(table_name, column_name)
@@ -156,6 +157,7 @@ module ActiveRecord
156
157
  end
157
158
  sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(options[:default], column_object)} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
158
159
  alter_command = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, limit: options[:limit], precision: options[:precision], scale: options[:scale])}"
160
+ alter_command += " COLLATE #{options[:collation]}" if options[:collation].present?
159
161
  alter_command += " NOT NULL" if !options[:null].nil? && options[:null] == false
160
162
  sql_commands << alter_command
161
163
  if without_constraints
@@ -190,7 +192,7 @@ module ActiveRecord
190
192
  end
191
193
 
192
194
  def rename_index(table_name, old_name, new_name)
193
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" if new_name.length > allowed_index_name_length
195
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters" if new_name.length > index_name_length
194
196
 
195
197
  identifier = SQLServer::Utils.extract_identifiers("#{table_name}.#{old_name}")
196
198
  execute_procedure :sp_rename, identifier.quoted, new_name, "INDEX"
@@ -281,14 +283,33 @@ module ActiveRecord
281
283
  SQLServer::SchemaDumper.create(self, options)
282
284
  end
283
285
 
286
+ def create_schema(schema_name, authorization = nil)
287
+ sql = "CREATE SCHEMA [#{schema_name}]"
288
+ sql += " AUTHORIZATION [#{authorization}]" if authorization
289
+
290
+ execute sql
291
+ end
292
+
293
+ def change_table_schema(schema_name, table_name)
294
+ execute "ALTER SCHEMA [#{schema_name}] TRANSFER [#{table_name}]"
295
+ end
296
+
297
+ def drop_schema(schema_name)
298
+ execute "DROP SCHEMA [#{schema_name}]"
299
+ end
300
+
284
301
  private
285
302
 
286
303
  def data_source_sql(name = nil, type: nil)
287
304
  scope = quoted_scope name, type: type
288
- table_name = lowercase_schema_reflection_sql "TABLE_NAME"
305
+
306
+ table_name = lowercase_schema_reflection_sql 'TABLE_NAME'
307
+ database = scope[:database].present? ? "#{scope[:database]}." : ""
308
+ table_catalog = scope[:database].present? ? quote(scope[:database]) : "DB_NAME()"
309
+
289
310
  sql = "SELECT #{table_name}"
290
- sql += " FROM INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
291
- sql += " WHERE TABLE_CATALOG = DB_NAME()"
311
+ sql += " FROM #{database}INFORMATION_SCHEMA.TABLES WITH (NOLOCK)"
312
+ sql += " WHERE TABLE_CATALOG = #{table_catalog}"
292
313
  sql += " AND TABLE_SCHEMA = #{quote(scope[:schema])}"
293
314
  sql += " AND TABLE_NAME = #{quote(scope[:name])}" if scope[:name]
294
315
  sql += " AND TABLE_TYPE = #{quote(scope[:type])}" if scope[:type]
@@ -299,6 +320,7 @@ module ActiveRecord
299
320
  def quoted_scope(name = nil, type: nil)
300
321
  identifier = SQLServer::Utils.extract_identifiers(name)
301
322
  {}.tap do |scope|
323
+ scope[:database] = identifier.database if identifier.database
302
324
  scope[:schema] = identifier.schema || "dbo"
303
325
  scope[:name] = identifier.object if identifier.object
304
326
  scope[:type] = type if type
@@ -310,7 +332,7 @@ module ActiveRecord
310
332
  def initialize_native_database_types
311
333
  {
312
334
  primary_key: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY",
313
- primary_key_nonclustered: "int NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
335
+ primary_key_nonclustered: "bigint NOT NULL IDENTITY(1,1) PRIMARY KEY NONCLUSTERED",
314
336
  integer: { name: "int", limit: 4 },
315
337
  bigint: { name: "bigint" },
316
338
  boolean: { name: "bit" },
@@ -3,16 +3,45 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
- class SqlTypeMetadata < ActiveRecord::ConnectionAdapters::SqlTypeMetadata
7
- def initialize(**kwargs)
8
- @sqlserver_options = kwargs.extract!(:sqlserver_options)
9
- super(**kwargs)
6
+ class TypeMetadata < DelegateClass(SqlTypeMetadata)
7
+ undef to_yaml if method_defined?(:to_yaml)
8
+
9
+ include Deduplicable
10
+
11
+ attr_reader :is_identity, :is_primary, :table_name, :ordinal_position
12
+
13
+ def initialize(type_metadata, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil)
14
+ super(type_metadata)
15
+ @is_identity = is_identity
16
+ @is_primary = is_primary
17
+ @table_name = table_name
18
+ @ordinal_position = ordinal_position
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(TypeMetadata) &&
23
+ __getobj__ == other.__getobj__ &&
24
+ is_identity == other.is_identity &&
25
+ is_primary == other.is_primary &&
26
+ table_name == other.table_name &&
27
+ ordinal_position == other.ordinal_position
28
+ end
29
+ alias eql? ==
30
+
31
+ def hash
32
+ TypeMetadata.hash ^
33
+ __getobj__.hash ^
34
+ is_identity.hash ^
35
+ is_primary.hash ^
36
+ table_name.hash ^
37
+ ordinal_position.hash
10
38
  end
11
39
 
12
- protected
40
+ private
13
41
 
14
- def attributes_for_hash
15
- super + [@sqlserver_options]
42
+ def deduplicated
43
+ __setobj__(__getobj__.deduplicate)
44
+ super
16
45
  end
17
46
  end
18
47
  end
@@ -9,7 +9,6 @@ module ActiveRecord
9
9
  options[:is_identity] = true unless options.key?(:default)
10
10
  elsif type == :uuid
11
11
  options[:default] = options.fetch(:default, "NEWID()")
12
- options[:primary_key] = true
13
12
  end
14
13
  super
15
14
  end
@@ -31,9 +31,9 @@ module ActiveRecord
31
31
  module SQLServerRealTransaction
32
32
  attr_reader :starting_isolation_level
33
33
 
34
- def initialize(connection, options, **args)
34
+ def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
35
35
  @connection = connection
36
- @starting_isolation_level = current_isolation_level if options[:isolation]
36
+ @starting_isolation_level = current_isolation_level if isolation
37
37
  super
38
38
  end
39
39
 
@@ -11,6 +11,7 @@ require "active_record/connection_adapters/sqlserver/type/small_integer"
11
11
  require "active_record/connection_adapters/sqlserver/type/tiny_integer"
12
12
  require "active_record/connection_adapters/sqlserver/type/boolean"
13
13
  require "active_record/connection_adapters/sqlserver/type/decimal"
14
+ require "active_record/connection_adapters/sqlserver/type/decimal_without_scale"
14
15
  require "active_record/connection_adapters/sqlserver/type/money"
15
16
  require "active_record/connection_adapters/sqlserver/type/small_money"
16
17
  # Approximate Numerics
@@ -10,7 +10,8 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def serialize(value)
13
- return unless value.present?
13
+ value = super
14
+ return value unless value.acts_like?(:date)
14
15
 
15
16
  date = super(value).to_s(:_sqlserver_dateformat)
16
17
  Data.new date, self
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module SQLServer
6
+ module Type
7
+ class DecimalWithoutScale < ActiveRecord::Type::DecimalWithoutScale
8
+ def sqlserver_type
9
+ "decimal".yield_self do |type|
10
+ type += "(#{precision.to_i},0)" if precision
11
+ type
12
+ end
13
+ end
14
+
15
+ def type_cast_for_schema(value)
16
+ value.is_a?(BigDecimal) ? value.to_s : value.inspect
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -92,7 +92,7 @@ module ActiveRecord
92
92
  @schema = @parts.first
93
93
  end
94
94
  rest = scanner.rest
95
- rest = rest.starts_with?(".") ? rest[1..-1] : rest[0..-1]
95
+ rest = rest.start_with?(".") ? rest[1..-1] : rest[0..-1]
96
96
  @object = unquote(rest)
97
97
  @parts << @object
98
98
  end
@@ -10,7 +10,6 @@ require "active_record/connection_adapters/sqlserver/core_ext/explain"
10
10
  require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber"
11
11
  require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
12
12
  require "active_record/connection_adapters/sqlserver/core_ext/finder_methods"
13
- require "active_record/connection_adapters/sqlserver/core_ext/query_methods"
14
13
  require "active_record/connection_adapters/sqlserver/core_ext/preloader"
15
14
  require "active_record/connection_adapters/sqlserver/version"
16
15
  require "active_record/connection_adapters/sqlserver/type"
@@ -59,13 +58,73 @@ module ActiveRecord
59
58
  self.use_output_inserted = true
60
59
  self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
61
60
 
62
- def initialize(connection, logger = nil, config = {})
61
+ class << self
62
+ def new_client(config)
63
+ case config[:mode]
64
+ when :dblib
65
+ require "tiny_tds"
66
+ dblib_connect(config)
67
+ else
68
+ raise ArgumentError, "Unknown connection mode in #{config.inspect}."
69
+ end
70
+ end
71
+
72
+ def dblib_connect(config)
73
+ TinyTds::Client.new(
74
+ dataserver: config[:dataserver],
75
+ host: config[:host],
76
+ port: config[:port],
77
+ username: config[:username],
78
+ password: config[:password],
79
+ database: config[:database],
80
+ tds_version: config[:tds_version] || "7.3",
81
+ appname: config_appname(config),
82
+ login_timeout: config_login_timeout(config),
83
+ timeout: config_timeout(config),
84
+ encoding: config_encoding(config),
85
+ azure: config[:azure],
86
+ contained: config[:contained]
87
+ ).tap do |client|
88
+ if config[:azure]
89
+ client.execute("SET ANSI_NULLS ON").do
90
+ client.execute("SET ANSI_NULL_DFLT_ON ON").do
91
+ client.execute("SET ANSI_PADDING ON").do
92
+ client.execute("SET ANSI_WARNINGS ON").do
93
+ else
94
+ client.execute("SET ANSI_DEFAULTS ON").do
95
+ end
96
+ client.execute("SET QUOTED_IDENTIFIER ON").do
97
+ client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
98
+ client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
99
+ client.execute("SET TEXTSIZE 2147483647").do
100
+ client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
101
+ end
102
+ rescue TinyTds::Error => e
103
+ raise ActiveRecord::NoDatabaseError if e.message.match(/database .* does not exist/i)
104
+ raise e
105
+ end
106
+
107
+ def config_appname(config)
108
+ config[:appname] || configure_application_name || Rails.application.class.name.split("::").first rescue nil
109
+ end
110
+
111
+ def config_login_timeout(config)
112
+ config[:login_timeout].present? ? config[:login_timeout].to_i : nil
113
+ end
114
+
115
+ def config_timeout(config)
116
+ config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
117
+ end
118
+
119
+ def config_encoding(config)
120
+ config[:encoding].present? ? config[:encoding] : nil
121
+ end
122
+ end
123
+
124
+ def initialize(connection, logger, _connection_options, config)
63
125
  super(connection, logger, config)
64
- # Our Responsibility
65
126
  @connection_options = config
66
- connect
67
- initialize_dateformatter
68
- use_database
127
+ configure_connection
69
128
  end
70
129
 
71
130
  # === Abstract Adapter ========================================== #
@@ -152,6 +211,10 @@ module ActiveRecord
152
211
  true
153
212
  end
154
213
 
214
+ def supports_optimizer_hints?
215
+ true
216
+ end
217
+
155
218
  def supports_lazy_transactions?
156
219
  true
157
220
  end
@@ -222,6 +285,14 @@ module ActiveRecord
222
285
  do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
223
286
  end
224
287
 
288
+ def configure_connection
289
+ @spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
290
+ @version_year = version_year
291
+
292
+ initialize_dateformatter
293
+ use_database
294
+ end
295
+
225
296
  # === Abstract Adapter (Misc Support) =========================== #
226
297
 
227
298
  def tables_with_referential_integrity
@@ -296,6 +367,7 @@ module ActiveRecord
296
367
 
297
368
  def initialize_type_map(m = type_map)
298
369
  m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
370
+
299
371
  # Exact Numerics
300
372
  register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
301
373
  m.alias_type "bigint", "bigint(8)"
@@ -308,16 +380,22 @@ module ActiveRecord
308
380
  m.alias_type "tinyint", "tinyint(1)"
309
381
  m.register_type "bit", SQLServer::Type::Boolean.new
310
382
  m.register_type %r{\Adecimal}i do |sql_type|
311
- scale = extract_scale(sql_type)
383
+ scale = extract_scale(sql_type)
312
384
  precision = extract_precision(sql_type)
313
- SQLServer::Type::Decimal.new precision: precision, scale: scale
385
+ if scale == 0
386
+ SQLServer::Type::DecimalWithoutScale.new(precision: precision)
387
+ else
388
+ SQLServer::Type::Decimal.new(precision: precision, scale: scale)
389
+ end
314
390
  end
315
391
  m.alias_type %r{\Anumeric}i, "decimal"
316
392
  m.register_type "money", SQLServer::Type::Money.new
317
393
  m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
394
+
318
395
  # Approximate Numerics
319
396
  m.register_type "float", SQLServer::Type::Float.new
320
397
  m.register_type "real", SQLServer::Type::Real.new
398
+
321
399
  # Date and Time
322
400
  m.register_type "date", SQLServer::Type::Date.new
323
401
  m.register_type %r{\Adatetime} do |sql_type|
@@ -337,11 +415,13 @@ module ActiveRecord
337
415
  precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
338
416
  SQLServer::Type::Time.new precision: precision
339
417
  end
418
+
340
419
  # Character Strings
341
420
  register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
342
421
  register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
343
422
  m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
344
423
  m.register_type "text", SQLServer::Type::Text.new
424
+
345
425
  # Unicode Character Strings
346
426
  register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
347
427
  register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
@@ -349,10 +429,12 @@ module ActiveRecord
349
429
  m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
350
430
  m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
351
431
  m.register_type "ntext", SQLServer::Type::UnicodeText.new
432
+
352
433
  # Binary Strings
353
434
  register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
354
435
  register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
355
436
  m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
437
+
356
438
  # Other Data Types
357
439
  m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
358
440
  m.register_type "timestamp", SQLServer::Type::Timestamp.new
@@ -360,6 +442,8 @@ module ActiveRecord
360
442
 
361
443
  def translate_exception(e, message:, sql:, binds:)
362
444
  case message
445
+ when /(SQL Server client is not connected)|(failed to execute statement)/i
446
+ ConnectionNotEstablished.new(message)
363
447
  when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
364
448
  RecordNotUnique.new(message, sql: sql, binds: binds)
365
449
  when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
@@ -393,78 +477,18 @@ module ActiveRecord
393
477
 
394
478
  # === SQLServer Specific (Connection Management) ================ #
395
479
 
396
- def connect
397
- config = @connection_options
398
- @connection = case config[:mode]
399
- when :dblib
400
- dblib_connect(config)
401
- end
402
- @spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
403
- @version_year = version_year
404
- configure_connection
405
- end
406
-
407
480
  def connection_errors
408
481
  @connection_errors ||= [].tap do |errors|
409
482
  errors << TinyTds::Error if defined?(TinyTds::Error)
410
483
  end
411
484
  end
412
485
 
413
- def dblib_connect(config)
414
- TinyTds::Client.new(
415
- dataserver: config[:dataserver],
416
- host: config[:host],
417
- port: config[:port],
418
- username: config[:username],
419
- password: config[:password],
420
- database: config[:database],
421
- tds_version: config[:tds_version] || "7.3",
422
- appname: config_appname(config),
423
- login_timeout: config_login_timeout(config),
424
- timeout: config_timeout(config),
425
- encoding: config_encoding(config),
426
- azure: config[:azure],
427
- contained: config[:contained]
428
- ).tap do |client|
429
- if config[:azure]
430
- client.execute("SET ANSI_NULLS ON").do
431
- client.execute("SET ANSI_NULL_DFLT_ON ON").do
432
- client.execute("SET ANSI_PADDING ON").do
433
- client.execute("SET ANSI_WARNINGS ON").do
434
- else
435
- client.execute("SET ANSI_DEFAULTS ON").do
436
- end
437
- client.execute("SET QUOTED_IDENTIFIER ON").do
438
- client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
439
- client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
440
- client.execute("SET TEXTSIZE 2147483647").do
441
- client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
442
- end
443
- end
444
-
445
- def config_appname(config)
446
- config[:appname] || configure_application_name || Rails.application.class.name.split("::").first rescue nil
447
- end
448
-
449
- def config_login_timeout(config)
450
- config[:login_timeout].present? ? config[:login_timeout].to_i : nil
451
- end
452
-
453
- def config_timeout(config)
454
- config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
455
- end
456
-
457
- def config_encoding(config)
458
- config[:encoding].present? ? config[:encoding] : nil
459
- end
460
-
461
- def configure_connection; end
462
-
463
486
  def configure_application_name; end
464
487
 
465
488
  def initialize_dateformatter
466
489
  @database_dateformat = user_options_dateformat
467
490
  a, b, c = @database_dateformat.each_char.to_a
491
+
468
492
  [a, b, c].each { |f| f.upcase! if f == "y" }
469
493
  dateformat = "%#{a}-%#{b}-%#{c}"
470
494
  ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
@@ -487,6 +511,13 @@ module ActiveRecord
487
511
  def sqlserver_version
488
512
  @sqlserver_version ||= _raw_select("SELECT @@version", fetch: :rows).first.first.to_s
489
513
  end
514
+
515
+ private
516
+
517
+ def connect
518
+ @connection = self.class.new_client(@connection_options)
519
+ configure_connection
520
+ end
490
521
  end
491
522
  end
492
523
  end