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

Sign up to get free protection for your applications and to get access to all the features.
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