activerecord-sqlserver-adapter 6.0.0.rc1 → 6.1.0.0.rc1

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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +26 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +29 -0
  5. data/CHANGELOG.md +18 -23
  6. data/Gemfile +11 -5
  7. data/Guardfile +9 -8
  8. data/README.md +32 -3
  9. data/RUNNING_UNIT_TESTS.md +1 -1
  10. data/Rakefile +12 -16
  11. data/VERSION +1 -1
  12. data/activerecord-sqlserver-adapter.gemspec +4 -4
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -4
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +1 -4
  15. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +3 -13
  16. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +8 -5
  17. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +2 -3
  18. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +2 -3
  19. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +0 -4
  20. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +58 -43
  21. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +7 -12
  22. data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -3
  23. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +15 -15
  24. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +22 -3
  25. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +16 -10
  26. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +131 -105
  27. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +6 -8
  28. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +2 -2
  29. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
  30. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +25 -7
  31. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +1 -5
  32. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +6 -10
  33. data/lib/active_record/connection_adapters/sqlserver/type.rb +36 -35
  34. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +0 -2
  35. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +0 -2
  36. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +0 -2
  37. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +2 -2
  38. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +0 -2
  39. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +2 -3
  40. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +2 -3
  41. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +0 -2
  42. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +0 -2
  43. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +0 -2
  44. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +0 -2
  46. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +0 -2
  47. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +0 -1
  48. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +0 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +0 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +0 -2
  51. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +0 -2
  52. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +0 -2
  53. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +0 -2
  54. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +0 -2
  55. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +2 -3
  56. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +6 -9
  57. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +0 -2
  58. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +0 -2
  59. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +1 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +0 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +0 -2
  62. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +0 -2
  63. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +0 -2
  64. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +1 -2
  65. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +1 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +0 -2
  67. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +1 -3
  68. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +0 -2
  69. data/lib/active_record/connection_adapters/sqlserver/utils.rb +8 -11
  70. data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -2
  71. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +170 -136
  72. data/lib/active_record/connection_adapters/sqlserver_column.rb +16 -1
  73. data/lib/active_record/sqlserver_base.rb +9 -15
  74. data/lib/active_record/tasks/sqlserver_database_tasks.rb +36 -39
  75. data/lib/activerecord-sqlserver-adapter.rb +1 -1
  76. data/lib/arel/visitors/sqlserver.rb +126 -50
  77. data/lib/arel_sqlserver.rb +2 -2
  78. data/test/cases/adapter_test_sqlserver.rb +203 -190
  79. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  80. data/test/cases/change_column_null_test_sqlserver.rb +12 -12
  81. data/test/cases/coerced_tests.rb +656 -318
  82. data/test/cases/column_test_sqlserver.rb +285 -284
  83. data/test/cases/connection_test_sqlserver.rb +15 -20
  84. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  85. data/test/cases/execute_procedure_test_sqlserver.rb +26 -19
  86. data/test/cases/fetch_test_sqlserver.rb +14 -22
  87. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +12 -18
  88. data/test/cases/helper_sqlserver.rb +13 -15
  89. data/test/cases/in_clause_test_sqlserver.rb +36 -9
  90. data/test/cases/index_test_sqlserver.rb +13 -15
  91. data/test/cases/json_test_sqlserver.rb +23 -25
  92. data/test/cases/lateral_test_sqlserver.rb +35 -0
  93. data/test/cases/migration_test_sqlserver.rb +71 -26
  94. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  95. data/test/cases/order_test_sqlserver.rb +57 -53
  96. data/test/cases/pessimistic_locking_test_sqlserver.rb +25 -33
  97. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  98. data/test/cases/rake_test_sqlserver.rb +33 -46
  99. data/test/cases/schema_dumper_test_sqlserver.rb +121 -108
  100. data/test/cases/schema_test_sqlserver.rb +18 -26
  101. data/test/cases/scratchpad_test_sqlserver.rb +2 -4
  102. data/test/cases/showplan_test_sqlserver.rb +24 -33
  103. data/test/cases/specific_schema_test_sqlserver.rb +66 -65
  104. data/test/cases/transaction_test_sqlserver.rb +16 -19
  105. data/test/cases/trigger_test_sqlserver.rb +12 -12
  106. data/test/cases/utils_test_sqlserver.rb +68 -70
  107. data/test/cases/uuid_test_sqlserver.rb +11 -13
  108. data/test/debug.rb +6 -6
  109. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  110. data/test/migrations/create_clients_and_change_column_null.rb +1 -1
  111. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +2 -4
  112. data/test/models/sqlserver/booking.rb +1 -1
  113. data/test/models/sqlserver/customers_view.rb +1 -1
  114. data/test/models/sqlserver/dollar_table_name.rb +1 -1
  115. data/test/models/sqlserver/edge_schema.rb +1 -3
  116. data/test/models/sqlserver/fk_has_fk.rb +1 -1
  117. data/test/models/sqlserver/fk_has_pk.rb +1 -1
  118. data/test/models/sqlserver/natural_pk_data.rb +2 -2
  119. data/test/models/sqlserver/natural_pk_int_data.rb +1 -1
  120. data/test/models/sqlserver/no_pk_data.rb +1 -1
  121. data/test/models/sqlserver/object_default.rb +1 -1
  122. data/test/models/sqlserver/quoted_table.rb +2 -2
  123. data/test/models/sqlserver/quoted_view_1.rb +1 -1
  124. data/test/models/sqlserver/quoted_view_2.rb +1 -1
  125. data/test/models/sqlserver/sst_memory.rb +1 -1
  126. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  127. data/test/models/sqlserver/string_default.rb +1 -1
  128. data/test/models/sqlserver/string_defaults_big_view.rb +1 -1
  129. data/test/models/sqlserver/string_defaults_view.rb +1 -1
  130. data/test/models/sqlserver/tinyint_pk.rb +1 -1
  131. data/test/models/sqlserver/trigger.rb +2 -2
  132. data/test/models/sqlserver/trigger_history.rb +1 -1
  133. data/test/models/sqlserver/upper.rb +1 -1
  134. data/test/models/sqlserver/uppered.rb +1 -1
  135. data/test/models/sqlserver/uuid.rb +1 -1
  136. data/test/schema/sqlserver_specific_schema.rb +36 -21
  137. data/test/support/coerceable_test_sqlserver.rb +1 -4
  138. data/test/support/connection_reflection.rb +1 -2
  139. data/test/support/core_ext/query_cache.rb +1 -1
  140. data/test/support/load_schema_sqlserver.rb +3 -5
  141. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic.dump +0 -0
  142. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_0_topic_associations.dump +0 -0
  143. data/test/support/minitest_sqlserver.rb +1 -1
  144. data/test/support/paths_sqlserver.rb +9 -11
  145. data/test/support/rake_helpers.rb +12 -10
  146. data/test/support/sql_counter_sqlserver.rb +14 -16
  147. data/test/support/test_in_memory_oltp.rb +7 -7
  148. metadata +31 -11
  149. data/.travis.yml +0 -23
@@ -5,7 +5,6 @@ module ActiveRecord
5
5
  module SQLServer
6
6
  module Type
7
7
  class Varchar < Char
8
-
9
8
  def initialize(**args)
10
9
  super
11
10
  @limit = 8000 if @limit.to_i == 0
@@ -16,12 +15,11 @@ module ActiveRecord
16
15
  end
17
16
 
18
17
  def sqlserver_type
19
- 'varchar'.yield_self do |type|
18
+ "varchar".yield_self do |type|
20
19
  type += "(#{limit})" if limit
21
20
  type
22
21
  end
23
22
  end
24
-
25
23
  end
26
24
  end
27
25
  end
@@ -5,7 +5,6 @@ module ActiveRecord
5
5
  module SQLServer
6
6
  module Type
7
7
  class VarcharMax < Varchar
8
-
9
8
  def initialize(**args)
10
9
  super
11
10
  @limit = 2_147_483_647
@@ -18,7 +17,6 @@ module ActiveRecord
18
17
  def sqlserver_type
19
18
  "varchar(max)"
20
19
  end
21
-
22
20
  end
23
21
  end
24
22
  end
@@ -1,19 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'strscan'
3
+ require "strscan"
4
4
 
5
5
  module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module SQLServer
8
8
  module Utils
9
-
10
- QUOTED_STRING_PREFIX = 'N'
9
+ QUOTED_STRING_PREFIX = "N"
11
10
 
12
11
  # Value object to return identifiers from SQL Server names http://bit.ly/1CZ3EiL
13
12
  # Inspiried from Rails PostgreSQL::Name adapter object in their own Utils.
14
13
  #
15
14
  class Name
16
-
17
15
  SEPARATOR = "."
18
16
  UNQUOTED_SCANNER = /\]?\./
19
17
  QUOTED_SCANNER = /\A\[.*?\]\./
@@ -56,15 +54,15 @@ module ActiveRecord
56
54
  end
57
55
 
58
56
  def quoted
59
- parts.map{ |p| quote(p) if p }.join SEPARATOR
57
+ parts.map { |p| quote(p) if p }.join SEPARATOR
60
58
  end
61
59
 
62
60
  def quoted_raw
63
61
  quote @raw_name
64
62
  end
65
63
 
66
- def ==(o)
67
- o.class == self.class && o.parts == parts
64
+ def ==(other)
65
+ other.class == self.class && other.parts == parts
68
66
  end
69
67
  alias_method :eql?, :==
70
68
 
@@ -77,6 +75,7 @@ module ActiveRecord
77
75
  def parse_raw_name
78
76
  @parts = []
79
77
  return if raw_name.blank?
78
+
80
79
  scanner = StringScanner.new(raw_name)
81
80
  matched = scanner.exist?(QUOTED_CHECKER) ? scanner.scan_until(QUOTED_SCANNER) : scanner.scan_until(UNQUOTED_SCANNER)
82
81
  while matched
@@ -93,7 +92,7 @@ module ActiveRecord
93
92
  @schema = @parts.first
94
93
  end
95
94
  rest = scanner.rest
96
- rest = rest.starts_with?('.') ? rest[1..-1] : rest[0..-1]
95
+ rest = rest.start_with?(".") ? rest[1..-1] : rest[0..-1]
97
96
  @object = unquote(rest)
98
97
  @parts << @object
99
98
  end
@@ -103,7 +102,7 @@ module ActiveRecord
103
102
  end
104
103
 
105
104
  def unquote(part)
106
- if part && part.start_with?('[')
105
+ if part && part.start_with?("[")
107
106
  part[1..-2]
108
107
  else
109
108
  part
@@ -113,7 +112,6 @@ module ActiveRecord
113
112
  def parts
114
113
  @parts
115
114
  end
116
-
117
115
  end
118
116
 
119
117
  extend self
@@ -141,7 +139,6 @@ module ActiveRecord
141
139
  def extract_identifiers(name)
142
140
  SQLServer::Utils::Name.new(name)
143
141
  end
144
-
145
142
  end
146
143
  end
147
144
  end
@@ -4,9 +4,7 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLServer
6
6
  module Version
7
-
8
7
  VERSION = File.read(File.expand_path("../../../../../VERSION", __FILE__)).chomp
9
-
10
8
  end
11
9
  end
12
10
  end
@@ -1,40 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
- require 'active_record'
5
- require 'arel_sqlserver'
6
- require 'active_record/connection_adapters/abstract_adapter'
7
- require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
8
- require 'active_record/connection_adapters/sqlserver/core_ext/calculations'
9
- require 'active_record/connection_adapters/sqlserver/core_ext/explain'
10
- require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber'
11
- require 'active_record/connection_adapters/sqlserver/core_ext/attribute_methods'
12
- require 'active_record/connection_adapters/sqlserver/core_ext/finder_methods'
13
- require 'active_record/connection_adapters/sqlserver/core_ext/query_methods'
14
- require 'active_record/connection_adapters/sqlserver/core_ext/preloader'
15
- require 'active_record/connection_adapters/sqlserver/version'
16
- require 'active_record/connection_adapters/sqlserver/type'
17
- require 'active_record/connection_adapters/sqlserver/database_limits'
18
- require 'active_record/connection_adapters/sqlserver/database_statements'
19
- require 'active_record/connection_adapters/sqlserver/database_tasks'
20
- require 'active_record/connection_adapters/sqlserver/transaction'
21
- require 'active_record/connection_adapters/sqlserver/errors'
22
- require 'active_record/connection_adapters/sqlserver/schema_creation'
23
- require 'active_record/connection_adapters/sqlserver/schema_dumper'
24
- require 'active_record/connection_adapters/sqlserver/schema_statements'
25
- require 'active_record/connection_adapters/sqlserver/sql_type_metadata'
26
- require 'active_record/connection_adapters/sqlserver/showplan'
27
- require 'active_record/connection_adapters/sqlserver/table_definition'
28
- require 'active_record/connection_adapters/sqlserver/quoting'
29
- require 'active_record/connection_adapters/sqlserver/utils'
30
- require 'active_record/sqlserver_base'
31
- require 'active_record/connection_adapters/sqlserver_column'
32
- require 'active_record/tasks/sqlserver_database_tasks'
3
+ require "base64"
4
+ require "active_record"
5
+ require "arel_sqlserver"
6
+ require "active_record/connection_adapters/abstract_adapter"
7
+ require "active_record/connection_adapters/sqlserver/core_ext/active_record"
8
+ require "active_record/connection_adapters/sqlserver/core_ext/calculations"
9
+ require "active_record/connection_adapters/sqlserver/core_ext/explain"
10
+ require "active_record/connection_adapters/sqlserver/core_ext/explain_subscriber"
11
+ require "active_record/connection_adapters/sqlserver/core_ext/attribute_methods"
12
+ require "active_record/connection_adapters/sqlserver/core_ext/finder_methods"
13
+ require "active_record/connection_adapters/sqlserver/core_ext/query_methods"
14
+ require "active_record/connection_adapters/sqlserver/core_ext/preloader"
15
+ require "active_record/connection_adapters/sqlserver/version"
16
+ require "active_record/connection_adapters/sqlserver/type"
17
+ require "active_record/connection_adapters/sqlserver/database_limits"
18
+ require "active_record/connection_adapters/sqlserver/database_statements"
19
+ require "active_record/connection_adapters/sqlserver/database_tasks"
20
+ require "active_record/connection_adapters/sqlserver/transaction"
21
+ require "active_record/connection_adapters/sqlserver/errors"
22
+ require "active_record/connection_adapters/sqlserver/schema_creation"
23
+ require "active_record/connection_adapters/sqlserver/schema_dumper"
24
+ require "active_record/connection_adapters/sqlserver/schema_statements"
25
+ require "active_record/connection_adapters/sqlserver/sql_type_metadata"
26
+ require "active_record/connection_adapters/sqlserver/showplan"
27
+ require "active_record/connection_adapters/sqlserver/table_definition"
28
+ require "active_record/connection_adapters/sqlserver/quoting"
29
+ require "active_record/connection_adapters/sqlserver/utils"
30
+ require "active_record/sqlserver_base"
31
+ require "active_record/connection_adapters/sqlserver_column"
32
+ require "active_record/tasks/sqlserver_database_tasks"
33
33
 
34
34
  module ActiveRecord
35
35
  module ConnectionAdapters
36
36
  class SQLServerAdapter < AbstractAdapter
37
-
38
37
  include SQLServer::Version,
39
38
  SQLServer::Quoting,
40
39
  SQLServer::DatabaseStatements,
@@ -43,7 +42,7 @@ module ActiveRecord
43
42
  SQLServer::DatabaseLimits,
44
43
  SQLServer::DatabaseTasks
45
44
 
46
- ADAPTER_NAME = 'SQLServer'.freeze
45
+ ADAPTER_NAME = "SQLServer".freeze
47
46
 
48
47
  # Default precision for 'time' (See https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql)
49
48
  DEFAULT_TIME_PRECISION = 7
@@ -56,17 +55,77 @@ module ActiveRecord
56
55
  cattr_accessor :showplan_option, instance_accessor: false
57
56
  cattr_accessor :lowercase_schema_reflection
58
57
 
59
- self.cs_equality_operator = 'COLLATE Latin1_General_CS_AS_WS'
58
+ self.cs_equality_operator = "COLLATE Latin1_General_CS_AS_WS"
60
59
  self.use_output_inserted = true
61
60
  self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
62
61
 
63
- def initialize(connection, logger = nil, config = {})
62
+ class << self
63
+ def new_client(config)
64
+ case config[:mode]
65
+ when :dblib
66
+ require "tiny_tds"
67
+ dblib_connect(config)
68
+ else
69
+ raise ArgumentError, "Unknown connection mode in #{config.inspect}."
70
+ end
71
+ end
72
+
73
+ def dblib_connect(config)
74
+ TinyTds::Client.new(
75
+ dataserver: config[:dataserver],
76
+ host: config[:host],
77
+ port: config[:port],
78
+ username: config[:username],
79
+ password: config[:password],
80
+ database: config[:database],
81
+ tds_version: config[:tds_version] || "7.3",
82
+ appname: config_appname(config),
83
+ login_timeout: config_login_timeout(config),
84
+ timeout: config_timeout(config),
85
+ encoding: config_encoding(config),
86
+ azure: config[:azure],
87
+ contained: config[:contained]
88
+ ).tap do |client|
89
+ if config[:azure]
90
+ client.execute("SET ANSI_NULLS ON").do
91
+ client.execute("SET ANSI_NULL_DFLT_ON ON").do
92
+ client.execute("SET ANSI_PADDING ON").do
93
+ client.execute("SET ANSI_WARNINGS ON").do
94
+ else
95
+ client.execute("SET ANSI_DEFAULTS ON").do
96
+ end
97
+ client.execute("SET QUOTED_IDENTIFIER ON").do
98
+ client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
99
+ client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
100
+ client.execute("SET TEXTSIZE 2147483647").do
101
+ client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
102
+ end
103
+ rescue TinyTds::Error => e
104
+ raise ActiveRecord::NoDatabaseError if e.message.match(/database .* does not exist/i)
105
+ raise e
106
+ end
107
+
108
+ def config_appname(config)
109
+ config[:appname] || configure_application_name || Rails.application.class.name.split("::").first rescue nil
110
+ end
111
+
112
+ def config_login_timeout(config)
113
+ config[:login_timeout].present? ? config[:login_timeout].to_i : nil
114
+ end
115
+
116
+ def config_timeout(config)
117
+ config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
118
+ end
119
+
120
+ def config_encoding(config)
121
+ config[:encoding].present? ? config[:encoding] : nil
122
+ end
123
+ end
124
+
125
+ def initialize(connection, logger, _connection_options, config)
64
126
  super(connection, logger, config)
65
- # Our Responsibility
66
127
  @connection_options = config
67
- connect
68
- initialize_dateformatter
69
- use_database
128
+ configure_connection
70
129
  end
71
130
 
72
131
  # === Abstract Adapter ========================================== #
@@ -153,6 +212,10 @@ module ActiveRecord
153
212
  true
154
213
  end
155
214
 
215
+ def supports_optimizer_hints?
216
+ true
217
+ end
218
+
156
219
  def supports_lazy_transactions?
157
220
  true
158
221
  end
@@ -189,7 +252,8 @@ module ActiveRecord
189
252
 
190
253
  def active?
191
254
  return false unless @connection
192
- raw_connection_do 'SELECT 1'
255
+
256
+ raw_connection_do "SELECT 1"
193
257
  true
194
258
  rescue *connection_errors
195
259
  false
@@ -219,7 +283,15 @@ module ActiveRecord
219
283
 
220
284
  def reset!
221
285
  reset_transaction
222
- do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
286
+ do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
287
+ end
288
+
289
+ def configure_connection
290
+ @spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
291
+ @version_year = version_year
292
+
293
+ initialize_dateformatter
294
+ use_database
223
295
  end
224
296
 
225
297
  # === Abstract Adapter (Misc Support) =========================== #
@@ -254,6 +326,7 @@ module ActiveRecord
254
326
 
255
327
  def database_prefix_remote_server?
256
328
  return false if database_prefix.blank?
329
+
257
330
  name = SQLServer::Utils.extract_identifiers(database_prefix)
258
331
  name.fully_qualified? && name.object.blank?
259
332
  end
@@ -294,31 +367,38 @@ module ActiveRecord
294
367
  # === Abstract Adapter (Misc Support) =========================== #
295
368
 
296
369
  def initialize_type_map(m = type_map)
297
- m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
370
+ m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
371
+
298
372
  # Exact Numerics
299
- register_class_with_limit m, 'bigint(8)', SQLServer::Type::BigInteger
300
- m.alias_type 'bigint', 'bigint(8)'
301
- register_class_with_limit m, 'int(4)', SQLServer::Type::Integer
302
- m.alias_type 'integer', 'int(4)'
303
- m.alias_type 'int', 'int(4)'
304
- register_class_with_limit m, 'smallint(2)', SQLServer::Type::SmallInteger
305
- m.alias_type 'smallint', 'smallint(2)'
306
- register_class_with_limit m, 'tinyint(1)', SQLServer::Type::TinyInteger
307
- m.alias_type 'tinyint', 'tinyint(1)'
308
- m.register_type 'bit', SQLServer::Type::Boolean.new
373
+ register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
374
+ m.alias_type "bigint", "bigint(8)"
375
+ register_class_with_limit m, "int(4)", SQLServer::Type::Integer
376
+ m.alias_type "integer", "int(4)"
377
+ m.alias_type "int", "int(4)"
378
+ register_class_with_limit m, "smallint(2)", SQLServer::Type::SmallInteger
379
+ m.alias_type "smallint", "smallint(2)"
380
+ register_class_with_limit m, "tinyint(1)", SQLServer::Type::TinyInteger
381
+ m.alias_type "tinyint", "tinyint(1)"
382
+ m.register_type "bit", SQLServer::Type::Boolean.new
309
383
  m.register_type %r{\Adecimal}i do |sql_type|
310
- scale = extract_scale(sql_type)
384
+ scale = extract_scale(sql_type)
311
385
  precision = extract_precision(sql_type)
312
- SQLServer::Type::Decimal.new precision: precision, scale: scale
386
+ if scale == 0
387
+ SQLServer::Type::DecimalWithoutScale.new(precision: precision)
388
+ else
389
+ SQLServer::Type::Decimal.new(precision: precision, scale: scale)
390
+ end
313
391
  end
314
- m.alias_type %r{\Anumeric}i, 'decimal'
315
- m.register_type 'money', SQLServer::Type::Money.new
316
- m.register_type 'smallmoney', SQLServer::Type::SmallMoney.new
392
+ m.alias_type %r{\Anumeric}i, "decimal"
393
+ m.register_type "money", SQLServer::Type::Money.new
394
+ m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
395
+
317
396
  # Approximate Numerics
318
- m.register_type 'float', SQLServer::Type::Float.new
319
- m.register_type 'real', SQLServer::Type::Real.new
397
+ m.register_type "float", SQLServer::Type::Float.new
398
+ m.register_type "real", SQLServer::Type::Real.new
399
+
320
400
  # Date and Time
321
- m.register_type 'date', SQLServer::Type::Date.new
401
+ m.register_type "date", SQLServer::Type::Date.new
322
402
  m.register_type %r{\Adatetime} do |sql_type|
323
403
  precision = extract_precision(sql_type)
324
404
  if precision
@@ -327,38 +407,44 @@ module ActiveRecord
327
407
  SQLServer::Type::DateTime.new
328
408
  end
329
409
  end
330
- m.register_type %r{\Adatetimeoffset}i do |sql_type|
410
+ m.register_type %r{\Adatetimeoffset}i do |sql_type|
331
411
  precision = extract_precision(sql_type)
332
412
  SQLServer::Type::DateTimeOffset.new precision: precision
333
413
  end
334
- m.register_type 'smalldatetime', SQLServer::Type::SmallDateTime.new
414
+ m.register_type "smalldatetime", SQLServer::Type::SmallDateTime.new
335
415
  m.register_type %r{\Atime}i do |sql_type|
336
416
  precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
337
417
  SQLServer::Type::Time.new precision: precision
338
418
  end
419
+
339
420
  # Character Strings
340
421
  register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
341
422
  register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
342
- m.register_type 'varchar(max)', SQLServer::Type::VarcharMax.new
343
- m.register_type 'text', SQLServer::Type::Text.new
423
+ m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
424
+ m.register_type "text", SQLServer::Type::Text.new
425
+
344
426
  # Unicode Character Strings
345
427
  register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
346
428
  register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
347
- m.alias_type 'string', 'nvarchar(4000)'
348
- m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
349
- m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
350
- m.register_type 'ntext', SQLServer::Type::UnicodeText.new
429
+ m.alias_type "string", "nvarchar(4000)"
430
+ m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
431
+ m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
432
+ m.register_type "ntext", SQLServer::Type::UnicodeText.new
433
+
351
434
  # Binary Strings
352
435
  register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
353
436
  register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
354
- m.register_type 'varbinary(max)', SQLServer::Type::VarbinaryMax.new
437
+ m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
438
+
355
439
  # Other Data Types
356
- m.register_type 'uniqueidentifier', SQLServer::Type::Uuid.new
357
- m.register_type 'timestamp', SQLServer::Type::Timestamp.new
440
+ m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
441
+ m.register_type "timestamp", SQLServer::Type::Timestamp.new
358
442
  end
359
443
 
360
444
  def translate_exception(e, message:, sql:, binds:)
361
445
  case message
446
+ when /(SQL Server client is not connected)|(failed to execute statement)/i
447
+ ConnectionNotEstablished.new(message)
362
448
  when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
363
449
  RecordNotUnique.new(message, sql: sql, binds: binds)
364
450
  when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
@@ -392,83 +478,23 @@ module ActiveRecord
392
478
 
393
479
  # === SQLServer Specific (Connection Management) ================ #
394
480
 
395
- def connect
396
- config = @connection_options
397
- @connection = case config[:mode]
398
- when :dblib
399
- dblib_connect(config)
400
- end
401
- @spid = _raw_select('SELECT @@SPID', fetch: :rows).first.first
402
- @version_year = version_year
403
- configure_connection
404
- end
405
-
406
481
  def connection_errors
407
482
  @connection_errors ||= [].tap do |errors|
408
483
  errors << TinyTds::Error if defined?(TinyTds::Error)
409
484
  end
410
485
  end
411
486
 
412
- def dblib_connect(config)
413
- TinyTds::Client.new(
414
- dataserver: config[:dataserver],
415
- host: config[:host],
416
- port: config[:port],
417
- username: config[:username],
418
- password: config[:password],
419
- database: config[:database],
420
- tds_version: config[:tds_version] || '7.3',
421
- appname: config_appname(config),
422
- login_timeout: config_login_timeout(config),
423
- timeout: config_timeout(config),
424
- encoding: config_encoding(config),
425
- azure: config[:azure],
426
- contained: config[:contained]
427
- ).tap do |client|
428
- if config[:azure]
429
- client.execute('SET ANSI_NULLS ON').do
430
- client.execute('SET ANSI_NULL_DFLT_ON ON').do
431
- client.execute('SET ANSI_PADDING ON').do
432
- client.execute('SET ANSI_WARNINGS ON').do
433
- else
434
- client.execute('SET ANSI_DEFAULTS ON').do
435
- end
436
- client.execute('SET QUOTED_IDENTIFIER ON').do
437
- client.execute('SET CURSOR_CLOSE_ON_COMMIT OFF').do
438
- client.execute('SET IMPLICIT_TRANSACTIONS OFF').do
439
- client.execute('SET TEXTSIZE 2147483647').do
440
- client.execute('SET CONCAT_NULL_YIELDS_NULL ON').do
441
- end
442
- end
443
-
444
- def config_appname(config)
445
- config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
446
- end
447
-
448
- def config_login_timeout(config)
449
- config[:login_timeout].present? ? config[:login_timeout].to_i : nil
450
- end
451
-
452
- def config_timeout(config)
453
- config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
454
- end
455
-
456
- def config_encoding(config)
457
- config[:encoding].present? ? config[:encoding] : nil
458
- end
459
-
460
- def configure_connection ; end
461
-
462
- def configure_application_name ; end
487
+ def configure_application_name; end
463
488
 
464
489
  def initialize_dateformatter
465
490
  @database_dateformat = user_options_dateformat
466
491
  a, b, c = @database_dateformat.each_char.to_a
467
- [a, b, c].each { |f| f.upcase! if f == 'y' }
492
+
493
+ [a, b, c].each { |f| f.upcase! if f == "y" }
468
494
  dateformat = "%#{a}-%#{b}-%#{c}"
469
495
  ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
470
496
  ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
471
- ::Time::DATE_FORMATS[:_sqlserver_time] = '%H:%M:%S'
497
+ ::Time::DATE_FORMATS[:_sqlserver_time] = "%H:%M:%S"
472
498
  ::Time::DATE_FORMATS[:_sqlserver_datetime] = "#{dateformat} %H:%M:%S"
473
499
  ::Time::DATE_FORMATS[:_sqlserver_datetimeoffset] = lambda { |time|
474
500
  time.strftime "#{dateformat} %H:%M:%S.%9N #{time.formatted_offset}"
@@ -477,13 +503,21 @@ module ActiveRecord
477
503
 
478
504
  def version_year
479
505
  return 2016 if sqlserver_version =~ /vNext/
506
+
480
507
  /SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
481
- rescue StandardError => e
508
+ rescue StandardError
482
509
  2016
483
510
  end
484
511
 
485
512
  def sqlserver_version
486
- @sqlserver_version ||= _raw_select('SELECT @@version', fetch: :rows).first.first.to_s
513
+ @sqlserver_version ||= _raw_select("SELECT @@version", fetch: :rows).first.first.to_s
514
+ end
515
+
516
+ private
517
+
518
+ def connect
519
+ @connection = self.class.new_client(@connection_options)
520
+ configure_connection
487
521
  end
488
522
  end
489
523
  end