activerecord-sqlserver-adapter 5.2.1 → 7.0.0.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/issue_template.md +23 -0
  4. data/.github/workflows/ci.yml +29 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +29 -0
  7. data/CHANGELOG.md +17 -27
  8. data/{Dockerfile → Dockerfile.ci} +1 -1
  9. data/Gemfile +49 -41
  10. data/Guardfile +9 -8
  11. data/MIT-LICENSE +1 -1
  12. data/README.md +65 -42
  13. data/RUNNING_UNIT_TESTS.md +3 -0
  14. data/Rakefile +14 -16
  15. data/VERSION +1 -1
  16. data/activerecord-sqlserver-adapter.gemspec +25 -14
  17. data/appveyor.yml +22 -17
  18. data/docker-compose.ci.yml +7 -5
  19. data/guides/RELEASING.md +11 -0
  20. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -4
  21. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +5 -4
  22. data/lib/active_record/connection_adapters/sqlserver/core_ext/calculations.rb +10 -14
  23. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +12 -5
  24. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +2 -0
  25. data/lib/active_record/connection_adapters/sqlserver/core_ext/finder_methods.rb +10 -7
  26. data/lib/active_record/connection_adapters/sqlserver/core_ext/preloader.rb +30 -0
  27. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +9 -4
  28. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +117 -52
  29. data/lib/active_record/connection_adapters/sqlserver/database_tasks.rb +9 -12
  30. data/lib/active_record/connection_adapters/sqlserver/errors.rb +2 -3
  31. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +51 -14
  32. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +40 -6
  33. data/lib/active_record/connection_adapters/sqlserver/schema_dumper.rb +18 -10
  34. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +235 -167
  35. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +4 -2
  36. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +3 -1
  37. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +8 -8
  38. data/lib/active_record/connection_adapters/sqlserver/sql_type_metadata.rb +36 -7
  39. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +43 -45
  40. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +8 -10
  41. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +3 -3
  42. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +5 -4
  43. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +3 -3
  44. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +7 -4
  45. data/lib/active_record/connection_adapters/sqlserver/type/data.rb +5 -3
  46. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +7 -5
  47. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +8 -8
  48. data/lib/active_record/connection_adapters/sqlserver/type/datetime2.rb +2 -2
  49. data/lib/active_record/connection_adapters/sqlserver/type/datetimeoffset.rb +2 -2
  50. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +5 -4
  51. data/lib/active_record/connection_adapters/sqlserver/type/decimal_without_scale.rb +22 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +3 -3
  53. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +3 -3
  54. data/lib/active_record/connection_adapters/sqlserver/type/json.rb +2 -1
  55. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +4 -4
  56. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +3 -3
  57. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +3 -3
  58. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +4 -4
  59. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +3 -3
  60. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +2 -2
  61. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +3 -3
  62. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +6 -6
  63. data/lib/active_record/connection_adapters/sqlserver/type/time_value_fractional.rb +8 -9
  64. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +3 -3
  65. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +3 -3
  66. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +5 -4
  67. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +2 -2
  68. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +3 -3
  69. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +6 -5
  70. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +4 -4
  71. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +4 -3
  72. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +6 -5
  73. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +4 -4
  74. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +6 -5
  75. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +4 -4
  76. data/lib/active_record/connection_adapters/sqlserver/type.rb +38 -35
  77. data/lib/active_record/connection_adapters/sqlserver/utils.rb +26 -12
  78. data/lib/active_record/connection_adapters/sqlserver/version.rb +2 -2
  79. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +271 -180
  80. data/lib/active_record/connection_adapters/sqlserver_column.rb +76 -16
  81. data/lib/active_record/sqlserver_base.rb +11 -9
  82. data/lib/active_record/tasks/sqlserver_database_tasks.rb +38 -39
  83. data/lib/activerecord-sqlserver-adapter.rb +3 -1
  84. data/lib/arel/visitors/sqlserver.rb +177 -56
  85. data/lib/arel_sqlserver.rb +4 -2
  86. data/test/appveyor/dbsetup.ps1 +4 -4
  87. data/test/cases/active_schema_test_sqlserver.rb +55 -0
  88. data/test/cases/adapter_test_sqlserver.rb +258 -173
  89. data/test/cases/change_column_collation_test_sqlserver.rb +33 -0
  90. data/test/cases/change_column_null_test_sqlserver.rb +14 -12
  91. data/test/cases/coerced_tests.rb +1421 -397
  92. data/test/cases/column_test_sqlserver.rb +321 -315
  93. data/test/cases/connection_test_sqlserver.rb +17 -20
  94. data/test/cases/disconnected_test_sqlserver.rb +39 -0
  95. data/test/cases/eager_load_too_many_ids_test_sqlserver.rb +18 -0
  96. data/test/cases/execute_procedure_test_sqlserver.rb +28 -19
  97. data/test/cases/fetch_test_sqlserver.rb +33 -21
  98. data/test/cases/fully_qualified_identifier_test_sqlserver.rb +15 -19
  99. data/test/cases/helper_sqlserver.rb +15 -15
  100. data/test/cases/in_clause_test_sqlserver.rb +63 -0
  101. data/test/cases/index_test_sqlserver.rb +15 -15
  102. data/test/cases/json_test_sqlserver.rb +25 -25
  103. data/test/cases/lateral_test_sqlserver.rb +35 -0
  104. data/test/cases/migration_test_sqlserver.rb +74 -27
  105. data/test/cases/optimizer_hints_test_sqlserver.rb +72 -0
  106. data/test/cases/order_test_sqlserver.rb +59 -53
  107. data/test/cases/pessimistic_locking_test_sqlserver.rb +27 -33
  108. data/test/cases/primary_keys_test_sqlserver.rb +103 -0
  109. data/test/cases/rake_test_sqlserver.rb +70 -45
  110. data/test/cases/schema_dumper_test_sqlserver.rb +124 -109
  111. data/test/cases/schema_test_sqlserver.rb +20 -26
  112. data/test/cases/scratchpad_test_sqlserver.rb +4 -4
  113. data/test/cases/showplan_test_sqlserver.rb +28 -35
  114. data/test/cases/specific_schema_test_sqlserver.rb +68 -65
  115. data/test/cases/transaction_test_sqlserver.rb +18 -20
  116. data/test/cases/trigger_test_sqlserver.rb +14 -13
  117. data/test/cases/utils_test_sqlserver.rb +70 -70
  118. data/test/cases/uuid_test_sqlserver.rb +13 -14
  119. data/test/debug.rb +8 -6
  120. data/test/migrations/create_clients_and_change_column_collation.rb +19 -0
  121. data/test/migrations/create_clients_and_change_column_null.rb +3 -1
  122. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +4 -4
  123. data/test/models/sqlserver/booking.rb +3 -1
  124. data/test/models/sqlserver/composite_pk.rb +9 -0
  125. data/test/models/sqlserver/customers_view.rb +3 -1
  126. data/test/models/sqlserver/datatype.rb +2 -0
  127. data/test/models/sqlserver/datatype_migration.rb +2 -0
  128. data/test/models/sqlserver/dollar_table_name.rb +3 -1
  129. data/test/models/sqlserver/edge_schema.rb +3 -3
  130. data/test/models/sqlserver/fk_has_fk.rb +3 -1
  131. data/test/models/sqlserver/fk_has_pk.rb +3 -1
  132. data/test/models/sqlserver/natural_pk_data.rb +4 -2
  133. data/test/models/sqlserver/natural_pk_int_data.rb +3 -1
  134. data/test/models/sqlserver/no_pk_data.rb +3 -1
  135. data/test/models/sqlserver/object_default.rb +3 -1
  136. data/test/models/sqlserver/quoted_table.rb +4 -2
  137. data/test/models/sqlserver/quoted_view_1.rb +3 -1
  138. data/test/models/sqlserver/quoted_view_2.rb +3 -1
  139. data/test/models/sqlserver/sst_memory.rb +3 -1
  140. data/test/models/sqlserver/sst_string_collation.rb +3 -0
  141. data/test/models/sqlserver/string_default.rb +3 -1
  142. data/test/models/sqlserver/string_defaults_big_view.rb +3 -1
  143. data/test/models/sqlserver/string_defaults_view.rb +3 -1
  144. data/test/models/sqlserver/tinyint_pk.rb +3 -1
  145. data/test/models/sqlserver/trigger.rb +4 -2
  146. data/test/models/sqlserver/trigger_history.rb +3 -1
  147. data/test/models/sqlserver/upper.rb +3 -1
  148. data/test/models/sqlserver/uppered.rb +3 -1
  149. data/test/models/sqlserver/uuid.rb +3 -1
  150. data/test/schema/sqlserver_specific_schema.rb +56 -21
  151. data/test/support/coerceable_test_sqlserver.rb +19 -13
  152. data/test/support/connection_reflection.rb +3 -2
  153. data/test/support/core_ext/query_cache.rb +4 -1
  154. data/test/support/load_schema_sqlserver.rb +5 -5
  155. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic.dump +0 -0
  156. data/test/support/marshal_compatibility_fixtures/SQLServer/rails_6_1_topic_associations.dump +0 -0
  157. data/test/support/minitest_sqlserver.rb +3 -1
  158. data/test/support/paths_sqlserver.rb +11 -11
  159. data/test/support/rake_helpers.rb +15 -10
  160. data/test/support/sql_counter_sqlserver.rb +16 -15
  161. data/test/support/test_in_memory_oltp.rb +9 -7
  162. metadata +47 -13
  163. data/.travis.yml +0 -25
  164. data/lib/active_record/connection_adapters/sqlserver/core_ext/query_methods.rb +0 -26
@@ -1,37 +1,38 @@
1
- require 'base64'
2
- require 'active_record'
3
- require 'arel_sqlserver'
4
- require 'active_record/connection_adapters/abstract_adapter'
5
- require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
6
- require 'active_record/connection_adapters/sqlserver/core_ext/calculations'
7
- require 'active_record/connection_adapters/sqlserver/core_ext/explain'
8
- require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber'
9
- require 'active_record/connection_adapters/sqlserver/core_ext/attribute_methods'
10
- require 'active_record/connection_adapters/sqlserver/core_ext/finder_methods'
11
- require 'active_record/connection_adapters/sqlserver/core_ext/query_methods'
12
- require 'active_record/connection_adapters/sqlserver/version'
13
- require 'active_record/connection_adapters/sqlserver/type'
14
- require 'active_record/connection_adapters/sqlserver/database_limits'
15
- require 'active_record/connection_adapters/sqlserver/database_statements'
16
- require 'active_record/connection_adapters/sqlserver/database_tasks'
17
- require 'active_record/connection_adapters/sqlserver/transaction'
18
- require 'active_record/connection_adapters/sqlserver/errors'
19
- require 'active_record/connection_adapters/sqlserver/schema_creation'
20
- require 'active_record/connection_adapters/sqlserver/schema_dumper'
21
- require 'active_record/connection_adapters/sqlserver/schema_statements'
22
- require 'active_record/connection_adapters/sqlserver/sql_type_metadata'
23
- require 'active_record/connection_adapters/sqlserver/showplan'
24
- require 'active_record/connection_adapters/sqlserver/table_definition'
25
- require 'active_record/connection_adapters/sqlserver/quoting'
26
- require 'active_record/connection_adapters/sqlserver/utils'
27
- require 'active_record/sqlserver_base'
28
- require 'active_record/connection_adapters/sqlserver_column'
29
- require 'active_record/tasks/sqlserver_database_tasks'
1
+ # frozen_string_literal: true
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/preloader"
14
+ require "active_record/connection_adapters/sqlserver/version"
15
+ require "active_record/connection_adapters/sqlserver/type"
16
+ require "active_record/connection_adapters/sqlserver/database_limits"
17
+ require "active_record/connection_adapters/sqlserver/database_statements"
18
+ require "active_record/connection_adapters/sqlserver/database_tasks"
19
+ require "active_record/connection_adapters/sqlserver/transaction"
20
+ require "active_record/connection_adapters/sqlserver/errors"
21
+ require "active_record/connection_adapters/sqlserver/schema_creation"
22
+ require "active_record/connection_adapters/sqlserver/schema_dumper"
23
+ require "active_record/connection_adapters/sqlserver/schema_statements"
24
+ require "active_record/connection_adapters/sqlserver/sql_type_metadata"
25
+ require "active_record/connection_adapters/sqlserver/showplan"
26
+ require "active_record/connection_adapters/sqlserver/table_definition"
27
+ require "active_record/connection_adapters/sqlserver/quoting"
28
+ require "active_record/connection_adapters/sqlserver/utils"
29
+ require "active_record/sqlserver_base"
30
+ require "active_record/connection_adapters/sqlserver_column"
31
+ require "active_record/tasks/sqlserver_database_tasks"
30
32
 
31
33
  module ActiveRecord
32
34
  module ConnectionAdapters
33
35
  class SQLServerAdapter < AbstractAdapter
34
-
35
36
  include SQLServer::Version,
36
37
  SQLServer::Quoting,
37
38
  SQLServer::DatabaseStatements,
@@ -40,7 +41,7 @@ module ActiveRecord
40
41
  SQLServer::DatabaseLimits,
41
42
  SQLServer::DatabaseTasks
42
43
 
43
- ADAPTER_NAME = 'SQLServer'.freeze
44
+ ADAPTER_NAME = "SQLServer".freeze
44
45
 
45
46
  # Default precision for 'time' (See https://docs.microsoft.com/en-us/sql/t-sql/data-types/time-transact-sql)
46
47
  DEFAULT_TIME_PRECISION = 7
@@ -53,17 +54,93 @@ module ActiveRecord
53
54
  cattr_accessor :showplan_option, instance_accessor: false
54
55
  cattr_accessor :lowercase_schema_reflection
55
56
 
56
- self.cs_equality_operator = 'COLLATE Latin1_General_CS_AS_WS'
57
+ self.cs_equality_operator = "COLLATE Latin1_General_CS_AS_WS"
57
58
  self.use_output_inserted = true
58
59
  self.exclude_output_inserted_table_names = Concurrent::Map.new { false }
59
60
 
60
- 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
+ if instance_methods.include?(:configure_application_name)
109
+ ActiveSupport::Deprecation.warn <<~MSG.squish
110
+ Configuring the application name used by TinyTDS by overriding the
111
+ `ActiveRecord::ConnectionAdapters::SQLServerAdapter#configure_application_name`
112
+ instance method is no longer supported. The application name should configured
113
+ using the `appname` setting in the `database.yml` file instead. Consult the
114
+ README for further information."
115
+ MSG
116
+ end
117
+
118
+ config[:appname] || rails_application_name
119
+ end
120
+
121
+ def rails_application_name
122
+ Rails.application.class.name.split("::").first
123
+ rescue
124
+ nil # Might not be in a Rails context so we fallback to `nil`.
125
+ end
126
+
127
+ def config_login_timeout(config)
128
+ config[:login_timeout].present? ? config[:login_timeout].to_i : nil
129
+ end
130
+
131
+ def config_timeout(config)
132
+ config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
133
+ end
134
+
135
+ def config_encoding(config)
136
+ config[:encoding].present? ? config[:encoding] : nil
137
+ end
138
+ end
139
+
140
+ def initialize(connection, logger, _connection_options, config)
61
141
  super(connection, logger, config)
62
- # Our Responsibility
63
142
  @connection_options = config
64
- connect
65
- initialize_dateformatter
66
- use_database
143
+ configure_connection
67
144
  end
68
145
 
69
146
  # === Abstract Adapter ========================================== #
@@ -80,6 +157,12 @@ module ActiveRecord
80
157
  SQLServer::SchemaCreation.new self
81
158
  end
82
159
 
160
+ def self.database_exists?(config)
161
+ !!ActiveRecord::Base.sqlserver_connection(config)
162
+ rescue ActiveRecord::NoDatabaseError
163
+ false
164
+ end
165
+
83
166
  def supports_ddl_transactions?
84
167
  true
85
168
  end
@@ -144,10 +227,34 @@ module ActiveRecord
144
227
  true
145
228
  end
146
229
 
230
+ def supports_optimizer_hints?
231
+ true
232
+ end
233
+
234
+ def supports_lazy_transactions?
235
+ true
236
+ end
237
+
147
238
  def supports_in_memory_oltp?
148
239
  @version_year >= 2014
149
240
  end
150
241
 
242
+ def supports_insert_returning?
243
+ true
244
+ end
245
+
246
+ def supports_insert_on_duplicate_skip?
247
+ false
248
+ end
249
+
250
+ def supports_insert_on_duplicate_update?
251
+ false
252
+ end
253
+
254
+ def supports_insert_conflict_target?
255
+ false
256
+ end
257
+
151
258
  def disable_referential_integrity
152
259
  tables = tables_with_referential_integrity
153
260
  tables.each { |t| do_execute "ALTER TABLE #{quote_table_name(t)} NOCHECK CONSTRAINT ALL" }
@@ -160,7 +267,8 @@ module ActiveRecord
160
267
 
161
268
  def active?
162
269
  return false unless @connection
163
- raw_connection_do 'SELECT 1'
270
+
271
+ raw_connection_do "SELECT 1"
164
272
  true
165
273
  rescue *connection_errors
166
274
  false
@@ -190,13 +298,21 @@ module ActiveRecord
190
298
 
191
299
  def reset!
192
300
  reset_transaction
193
- do_execute 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION'
301
+ do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
302
+ end
303
+
304
+ def configure_connection
305
+ @spid = _raw_select("SELECT @@SPID", fetch: :rows).first.first
306
+ @version_year = version_year
307
+
308
+ initialize_dateformatter
309
+ use_database
194
310
  end
195
311
 
196
312
  # === Abstract Adapter (Misc Support) =========================== #
197
313
 
198
314
  def tables_with_referential_integrity
199
- schemas_and_tables = select_rows <<-SQL.strip_heredoc
315
+ schemas_and_tables = select_rows <<~SQL.squish
200
316
  SELECT DISTINCT s.name, o.name
201
317
  FROM sys.foreign_keys i
202
318
  INNER JOIN sys.objects o ON i.parent_object_id = o.OBJECT_ID
@@ -225,6 +341,7 @@ module ActiveRecord
225
341
 
226
342
  def database_prefix_remote_server?
227
343
  return false if database_prefix.blank?
344
+
228
345
  name = SQLServer::Utils.extract_identifiers(database_prefix)
229
346
  name.fully_qualified? && name.object.blank?
230
347
  end
@@ -256,87 +373,115 @@ module ActiveRecord
256
373
  result
257
374
  end
258
375
 
376
+ def get_database_version # :nodoc:
377
+ version_year
378
+ end
379
+
380
+ class << self
381
+ protected
382
+
383
+ def initialize_type_map(m)
384
+ m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
385
+
386
+ # Exact Numerics
387
+ register_class_with_limit m, "bigint(8)", SQLServer::Type::BigInteger
388
+ m.alias_type "bigint", "bigint(8)"
389
+ register_class_with_limit m, "int(4)", SQLServer::Type::Integer
390
+ m.alias_type "integer", "int(4)"
391
+ m.alias_type "int", "int(4)"
392
+ register_class_with_limit m, "smallint(2)", SQLServer::Type::SmallInteger
393
+ m.alias_type "smallint", "smallint(2)"
394
+ register_class_with_limit m, "tinyint(1)", SQLServer::Type::TinyInteger
395
+ m.alias_type "tinyint", "tinyint(1)"
396
+ m.register_type "bit", SQLServer::Type::Boolean.new
397
+ m.register_type %r{\Adecimal}i do |sql_type|
398
+ scale = extract_scale(sql_type)
399
+ precision = extract_precision(sql_type)
400
+ if scale == 0
401
+ SQLServer::Type::DecimalWithoutScale.new(precision: precision)
402
+ else
403
+ SQLServer::Type::Decimal.new(precision: precision, scale: scale)
404
+ end
405
+ end
406
+ m.alias_type %r{\Anumeric}i, "decimal"
407
+ m.register_type "money", SQLServer::Type::Money.new
408
+ m.register_type "smallmoney", SQLServer::Type::SmallMoney.new
409
+
410
+ # Approximate Numerics
411
+ m.register_type "float", SQLServer::Type::Float.new
412
+ m.register_type "real", SQLServer::Type::Real.new
413
+
414
+ # Date and Time
415
+ m.register_type "date", SQLServer::Type::Date.new
416
+ m.register_type %r{\Adatetime} do |sql_type|
417
+ precision = extract_precision(sql_type)
418
+ if precision
419
+ SQLServer::Type::DateTime2.new precision: precision
420
+ else
421
+ SQLServer::Type::DateTime.new
422
+ end
423
+ end
424
+ m.register_type %r{\Adatetimeoffset}i do |sql_type|
425
+ precision = extract_precision(sql_type)
426
+ SQLServer::Type::DateTimeOffset.new precision: precision
427
+ end
428
+ m.register_type "smalldatetime", SQLServer::Type::SmallDateTime.new
429
+ m.register_type %r{\Atime}i do |sql_type|
430
+ precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
431
+ SQLServer::Type::Time.new precision: precision
432
+ end
433
+
434
+ # Character Strings
435
+ register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
436
+ register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
437
+ m.register_type "varchar(max)", SQLServer::Type::VarcharMax.new
438
+ m.register_type "text", SQLServer::Type::Text.new
439
+
440
+ # Unicode Character Strings
441
+ register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
442
+ register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
443
+ m.alias_type "string", "nvarchar(4000)"
444
+ m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
445
+ m.register_type "nvarchar(max)", SQLServer::Type::UnicodeVarcharMax.new
446
+ m.register_type "ntext", SQLServer::Type::UnicodeText.new
447
+
448
+ # Binary Strings
449
+ register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
450
+ register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
451
+ m.register_type "varbinary(max)", SQLServer::Type::VarbinaryMax.new
452
+
453
+ # Other Data Types
454
+ m.register_type "uniqueidentifier", SQLServer::Type::Uuid.new
455
+ m.register_type "timestamp", SQLServer::Type::Timestamp.new
456
+ end
457
+ end
458
+
459
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
259
460
 
260
461
  protected
261
462
 
262
463
  # === Abstract Adapter (Misc Support) =========================== #
263
464
 
264
- def initialize_type_map(m = type_map)
265
- m.register_type %r{.*}, SQLServer::Type::UnicodeString.new
266
- # Exact Numerics
267
- register_class_with_limit m, 'bigint(8)', SQLServer::Type::BigInteger
268
- m.alias_type 'bigint', 'bigint(8)'
269
- register_class_with_limit m, 'int(4)', SQLServer::Type::Integer
270
- m.alias_type 'integer', 'int(4)'
271
- m.alias_type 'int', 'int(4)'
272
- register_class_with_limit m, 'smallint(2)', SQLServer::Type::SmallInteger
273
- m.alias_type 'smallint', 'smallint(2)'
274
- register_class_with_limit m, 'tinyint(1)', SQLServer::Type::TinyInteger
275
- m.alias_type 'tinyint', 'tinyint(1)'
276
- m.register_type 'bit', SQLServer::Type::Boolean.new
277
- m.register_type %r{\Adecimal}i do |sql_type|
278
- scale = extract_scale(sql_type)
279
- precision = extract_precision(sql_type)
280
- SQLServer::Type::Decimal.new precision: precision, scale: scale
281
- end
282
- m.alias_type %r{\Anumeric}i, 'decimal'
283
- m.register_type 'money', SQLServer::Type::Money.new
284
- m.register_type 'smallmoney', SQLServer::Type::SmallMoney.new
285
- # Approximate Numerics
286
- m.register_type 'float', SQLServer::Type::Float.new
287
- m.register_type 'real', SQLServer::Type::Real.new
288
- # Date and Time
289
- m.register_type 'date', SQLServer::Type::Date.new
290
- m.register_type %r{\Adatetime} do |sql_type|
291
- precision = extract_precision(sql_type)
292
- if precision
293
- SQLServer::Type::DateTime2.new precision: precision
294
- else
295
- SQLServer::Type::DateTime.new
296
- end
297
- end
298
- m.register_type %r{\Adatetimeoffset}i do |sql_type|
299
- precision = extract_precision(sql_type)
300
- SQLServer::Type::DateTimeOffset.new precision: precision
301
- end
302
- m.register_type 'smalldatetime', SQLServer::Type::SmallDateTime.new
303
- m.register_type %r{\Atime}i do |sql_type|
304
- precision = extract_precision(sql_type) || DEFAULT_TIME_PRECISION
305
- SQLServer::Type::Time.new precision: precision
306
- end
307
- # Character Strings
308
- register_class_with_limit m, %r{\Achar}i, SQLServer::Type::Char
309
- register_class_with_limit m, %r{\Avarchar}i, SQLServer::Type::Varchar
310
- m.register_type 'varchar(max)', SQLServer::Type::VarcharMax.new
311
- m.register_type 'text', SQLServer::Type::Text.new
312
- # Unicode Character Strings
313
- register_class_with_limit m, %r{\Anchar}i, SQLServer::Type::UnicodeChar
314
- register_class_with_limit m, %r{\Anvarchar}i, SQLServer::Type::UnicodeVarchar
315
- m.alias_type 'string', 'nvarchar(4000)'
316
- m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
317
- m.register_type 'nvarchar(max)', SQLServer::Type::UnicodeVarcharMax.new
318
- m.register_type 'ntext', SQLServer::Type::UnicodeText.new
319
- # Binary Strings
320
- register_class_with_limit m, %r{\Abinary}i, SQLServer::Type::Binary
321
- register_class_with_limit m, %r{\Avarbinary}i, SQLServer::Type::Varbinary
322
- m.register_type 'varbinary(max)', SQLServer::Type::VarbinaryMax.new
323
- # Other Data Types
324
- m.register_type 'uniqueidentifier', SQLServer::Type::Uuid.new
325
- m.register_type 'timestamp', SQLServer::Type::Timestamp.new
326
- end
327
-
328
- def translate_exception(e, message)
465
+ def type_map
466
+ TYPE_MAP
467
+ end
468
+
469
+ def translate_exception(e, message:, sql:, binds:)
329
470
  case message
330
- when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
331
- RecordNotUnique.new(message)
332
- when /conflicted with the foreign key constraint/i
333
- InvalidForeignKey.new(message)
471
+ when /(SQL Server client is not connected)|(failed to execute statement)/i
472
+ ConnectionNotEstablished.new(message)
473
+ when /(cannot insert duplicate key .* with unique index) | (violation of (unique|primary) key constraint)/i
474
+ RecordNotUnique.new(message, sql: sql, binds: binds)
475
+ when /(conflicted with the foreign key constraint) | (The DELETE statement conflicted with the REFERENCE constraint)/i
476
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
334
477
  when /has been chosen as the deadlock victim/i
335
- DeadlockVictim.new(message)
478
+ DeadlockVictim.new(message, sql: sql, binds: binds)
336
479
  when /database .* does not exist/i
337
480
  NoDatabaseError.new(message)
338
481
  when /data would be truncated/
339
- ValueTooLong.new(message)
482
+ ValueTooLong.new(message, sql: sql, binds: binds)
483
+ when /connection timed out/
484
+ StatementTimeout.new(message, sql: sql, binds: binds)
340
485
  when /Column '(.*)' is not the same data type as referencing column '(.*)' in foreign key/
341
486
  pk_id, fk_id = SQLServer::Utils.extract_identifiers($1), SQLServer::Utils.extract_identifiers($2)
342
487
  MismatchedForeignKey.new(
@@ -348,9 +493,9 @@ module ActiveRecord
348
493
  primary_key: pk_id.object
349
494
  )
350
495
  when /Cannot insert the value NULL into column.*does not allow nulls/
351
- NotNullViolation.new(message)
496
+ NotNullViolation.new(message, sql: sql, binds: binds)
352
497
  when /Arithmetic overflow error/
353
- RangeError.new(message)
498
+ RangeError.new(message, sql: sql, binds: binds)
354
499
  else
355
500
  super
356
501
  end
@@ -358,83 +503,21 @@ module ActiveRecord
358
503
 
359
504
  # === SQLServer Specific (Connection Management) ================ #
360
505
 
361
- def connect
362
- config = @connection_options
363
- @connection = case config[:mode]
364
- when :dblib
365
- dblib_connect(config)
366
- end
367
- @spid = _raw_select('SELECT @@SPID', fetch: :rows).first.first
368
- @version_year = version_year
369
- configure_connection
370
- end
371
-
372
506
  def connection_errors
373
507
  @connection_errors ||= [].tap do |errors|
374
508
  errors << TinyTds::Error if defined?(TinyTds::Error)
375
509
  end
376
510
  end
377
511
 
378
- def dblib_connect(config)
379
- TinyTds::Client.new(
380
- dataserver: config[:dataserver],
381
- host: config[:host],
382
- port: config[:port],
383
- username: config[:username],
384
- password: config[:password],
385
- database: config[:database],
386
- tds_version: config[:tds_version] || '7.3',
387
- appname: config_appname(config),
388
- login_timeout: config_login_timeout(config),
389
- timeout: config_timeout(config),
390
- encoding: config_encoding(config),
391
- azure: config[:azure],
392
- contained: config[:contained]
393
- ).tap do |client|
394
- if config[:azure]
395
- client.execute('SET ANSI_NULLS ON').do
396
- client.execute('SET ANSI_NULL_DFLT_ON ON').do
397
- client.execute('SET ANSI_PADDING ON').do
398
- client.execute('SET ANSI_WARNINGS ON').do
399
- else
400
- client.execute('SET ANSI_DEFAULTS ON').do
401
- end
402
- client.execute('SET QUOTED_IDENTIFIER ON').do
403
- client.execute('SET CURSOR_CLOSE_ON_COMMIT OFF').do
404
- client.execute('SET IMPLICIT_TRANSACTIONS OFF').do
405
- client.execute('SET TEXTSIZE 2147483647').do
406
- client.execute('SET CONCAT_NULL_YIELDS_NULL ON').do
407
- end
408
- end
409
-
410
- def config_appname(config)
411
- config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
412
- end
413
-
414
- def config_login_timeout(config)
415
- config[:login_timeout].present? ? config[:login_timeout].to_i : nil
416
- end
417
-
418
- def config_timeout(config)
419
- config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
420
- end
421
-
422
- def config_encoding(config)
423
- config[:encoding].present? ? config[:encoding] : nil
424
- end
425
-
426
- def configure_connection ; end
427
-
428
- def configure_application_name ; end
429
-
430
512
  def initialize_dateformatter
431
513
  @database_dateformat = user_options_dateformat
432
514
  a, b, c = @database_dateformat.each_char.to_a
433
- [a, b, c].each { |f| f.upcase! if f == 'y' }
515
+
516
+ [a, b, c].each { |f| f.upcase! if f == "y" }
434
517
  dateformat = "%#{a}-%#{b}-%#{c}"
435
518
  ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
436
519
  ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
437
- ::Time::DATE_FORMATS[:_sqlserver_time] = '%H:%M:%S'
520
+ ::Time::DATE_FORMATS[:_sqlserver_time] = "%H:%M:%S"
438
521
  ::Time::DATE_FORMATS[:_sqlserver_datetime] = "#{dateformat} %H:%M:%S"
439
522
  ::Time::DATE_FORMATS[:_sqlserver_datetimeoffset] = lambda { |time|
440
523
  time.strftime "#{dateformat} %H:%M:%S.%9N #{time.formatted_offset}"
@@ -443,13 +526,21 @@ module ActiveRecord
443
526
 
444
527
  def version_year
445
528
  return 2016 if sqlserver_version =~ /vNext/
529
+
446
530
  /SQL Server (\d+)/.match(sqlserver_version).to_a.last.to_s.to_i
447
- rescue StandardError => e
531
+ rescue StandardError
448
532
  2016
449
533
  end
450
534
 
451
535
  def sqlserver_version
452
- @sqlserver_version ||= _raw_select('SELECT @@version', fetch: :rows).first.first.to_s
536
+ @sqlserver_version ||= _raw_select("SELECT @@version", fetch: :rows).first.first.to_s
537
+ end
538
+
539
+ private
540
+
541
+ def connect
542
+ @connection = self.class.new_client(@connection_options)
543
+ configure_connection
453
544
  end
454
545
  end
455
546
  end
@@ -1,28 +1,88 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
- class SQLServerColumn < Column
5
+ module SQLServer
6
+ class Column < ConnectionAdapters::Column
7
+ delegate :is_identity, :is_primary, :table_name, :ordinal_position, to: :sql_type_metadata
4
8
 
5
- def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment = nil, sqlserver_options = {})
6
- @sqlserver_options = sqlserver_options || {}
7
- super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
8
- end
9
+ def initialize(*, is_identity: nil, is_primary: nil, table_name: nil, ordinal_position: nil, **)
10
+ super
11
+ @is_identity = is_identity
12
+ @is_primary = is_primary
13
+ @table_name = table_name
14
+ @ordinal_position = ordinal_position
15
+ end
9
16
 
10
- def is_identity?
11
- @sqlserver_options[:is_identity]
12
- end
17
+ def is_identity?
18
+ is_identity
19
+ end
13
20
 
14
- def is_primary?
15
- @sqlserver_options[:is_primary]
16
- end
21
+ def is_primary?
22
+ is_primary
23
+ end
17
24
 
18
- def is_utf8?
19
- sql_type =~ /nvarchar|ntext|nchar/i
20
- end
25
+ def is_utf8?
26
+ sql_type =~ /nvarchar|ntext|nchar/i
27
+ end
28
+
29
+ def case_sensitive?
30
+ collation && collation.match(/_CS/)
31
+ end
32
+
33
+ def init_with(coder)
34
+ @is_identity = coder["is_identity"]
35
+ @is_primary = coder["is_primary"]
36
+ @table_name = coder["table_name"]
37
+ @ordinal_position = coder["ordinal_position"]
38
+ super
39
+ end
40
+
41
+ def encode_with(coder)
42
+ coder["is_identity"] = @is_identity
43
+ coder["is_primary"] = @is_primary
44
+ coder["table_name"] = @table_name
45
+ coder["ordinal_position"] = @ordinal_position
46
+ super
47
+ end
48
+
49
+ def ==(other)
50
+ other.is_a?(Column) &&
51
+ super &&
52
+ is_identity? == other.is_identity? &&
53
+ is_primary? == other.is_primary? &&
54
+ table_name == other.table_name &&
55
+ ordinal_position == other.ordinal_position
56
+ end
57
+ alias :eql? :==
58
+
59
+ def hash
60
+ Column.hash ^
61
+ super.hash ^
62
+ is_identity?.hash ^
63
+ is_primary?.hash ^
64
+ table_name.hash ^
65
+ ordinal_position.hash
66
+ end
67
+
68
+ private
21
69
 
22
- def case_sensitive?
23
- collation && collation.match(/_CS/)
70
+ # In the Rails version of this method there is an assumption that the `default` value will always be a
71
+ # `String` class, which must be true for the MySQL/PostgreSQL/SQLite adapters. However, in the SQL Server
72
+ # adapter the `default` value can also be Boolean/Date/Time/etc. Changed the implementation of this method
73
+ # to handle non-String `default` objects.
74
+ def deduplicated
75
+ @name = -name
76
+ @sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
77
+ @default = (default.is_a?(String) ? -default : default.dup.freeze) if default
78
+ @default_function = -default_function if default_function
79
+ @collation = -collation if collation
80
+ @comment = -comment if comment
81
+ freeze
82
+ end
24
83
  end
25
84
 
85
+ SQLServerColumn = SQLServer::Column
26
86
  end
27
87
  end
28
88
  end