activerecord-sqlserver-adapter 5.2.1 → 7.0.0.0

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