activerecord-sqlserver-adapter 6.0.1 → 6.1.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 (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