activerecord-sqlserver-adapter 3.1.1 → 3.2.18

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.
@@ -1,29 +1,35 @@
1
+ require 'base64'
1
2
  require 'arel/visitors/sqlserver'
2
3
  require 'active_record'
4
+ require 'active_record/base'
5
+ require 'active_support/concern'
6
+ require 'active_support/core_ext/string'
3
7
  require 'active_record/connection_adapters/abstract_adapter'
4
8
  require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
9
+ require 'active_record/connection_adapters/sqlserver/core_ext/database_statements'
10
+ require 'active_record/connection_adapters/sqlserver/core_ext/explain'
11
+ require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber'
12
+ require 'active_record/connection_adapters/sqlserver/core_ext/relation'
5
13
  require 'active_record/connection_adapters/sqlserver/database_limits'
6
14
  require 'active_record/connection_adapters/sqlserver/database_statements'
7
15
  require 'active_record/connection_adapters/sqlserver/errors'
16
+ require 'active_record/connection_adapters/sqlserver/schema_cache'
8
17
  require 'active_record/connection_adapters/sqlserver/schema_statements'
18
+ require 'active_record/connection_adapters/sqlserver/showplan'
9
19
  require 'active_record/connection_adapters/sqlserver/quoting'
10
- require 'active_record/connection_adapters/sqlserver/version'
11
- require 'active_support/core_ext/kernel/requires'
12
- require 'active_support/core_ext/string'
13
- require 'base64'
20
+ require 'active_record/connection_adapters/sqlserver/utils'
14
21
 
15
22
  module ActiveRecord
16
-
23
+
17
24
  class Base
18
-
25
+
19
26
  def self.sqlserver_connection(config) #:nodoc:
20
27
  config = config.symbolize_keys
21
- config.reverse_merge! :mode => :dblib, :host => 'localhost', :username => 'sa', :password => ''
28
+ config.reverse_merge! :mode => :dblib
22
29
  mode = config[:mode].to_s.downcase.underscore.to_sym
23
30
  case mode
24
31
  when :dblib
25
32
  require 'tiny_tds'
26
- warn("TinyTds v0.4.3 or higher required. Using #{TinyTds::VERSION}") unless TinyTds::Client.instance_methods.map(&:to_s).include?("active?")
27
33
  when :odbc
28
34
  raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
29
35
  require 'odbc'
@@ -31,75 +37,79 @@ module ActiveRecord
31
37
  else
32
38
  raise ArgumentError, "Unknown connection mode in #{config.inspect}."
33
39
  end
34
- ConnectionAdapters::SQLServerAdapter.new(logger,config.merge(:mode=>mode))
40
+ ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(:mode=>mode))
35
41
  end
36
-
42
+
37
43
  protected
38
-
44
+
39
45
  def self.did_retry_sqlserver_connection(connection,count)
40
46
  logger.info "CONNECTION RETRY: #{connection.class.name} retry ##{count}."
41
47
  end
42
-
48
+
43
49
  def self.did_lose_sqlserver_connection(connection)
44
50
  logger.info "CONNECTION LOST: #{connection.class.name}"
45
51
  end
46
-
52
+
47
53
  end
48
-
54
+
49
55
  module ConnectionAdapters
50
-
56
+
51
57
  class SQLServerColumn < Column
52
58
 
53
59
  def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
54
60
  @sqlserver_options = sqlserver_options.symbolize_keys
55
61
  super(name, default, sql_type, null)
56
- @primary = @sqlserver_options[:is_identity]
62
+ @primary = @sqlserver_options[:is_identity] || @sqlserver_options[:is_primary]
57
63
  end
58
-
64
+
59
65
  class << self
60
-
66
+
61
67
  def string_to_binary(value)
62
68
  "0x#{value.unpack("H*")[0]}"
63
69
  end
64
-
70
+
65
71
  def binary_to_string(value)
66
72
  value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
67
73
  end
68
-
74
+
69
75
  end
70
-
76
+
71
77
  def is_identity?
72
78
  @sqlserver_options[:is_identity]
73
79
  end
74
-
80
+
81
+ def is_primary?
82
+ @sqlserver_options[:is_primary]
83
+ end
84
+
75
85
  def is_utf8?
76
86
  !!(@sql_type =~ /nvarchar|ntext|nchar/i)
77
87
  end
78
-
88
+
79
89
  def is_integer?
80
90
  !!(@sql_type =~ /int/i)
81
91
  end
82
-
92
+
83
93
  def is_real?
84
94
  !!(@sql_type =~ /real/i)
85
95
  end
86
-
96
+
87
97
  def sql_type_for_statement
88
98
  if is_integer? || is_real?
89
- sql_type.sub(/\(\d+\)/,'')
99
+ sql_type.sub(/\((\d+)?\)/,'')
90
100
  else
91
101
  sql_type
92
102
  end
93
103
  end
94
-
104
+
95
105
  def default_function
96
106
  @sqlserver_options[:default_function]
97
107
  end
98
-
108
+
99
109
  def table_name
100
110
  @sqlserver_options[:table_name]
101
111
  end
102
-
112
+
103
113
  def table_klass
104
114
  @table_klass ||= begin
105
115
  table_name.classify.constantize
@@ -108,14 +118,14 @@ module ActiveRecord
108
118
  end
109
119
  (@table_klass && @table_klass < ActiveRecord::Base) ? @table_klass : nil
110
120
  end
111
-
121
+
112
122
  def database_year
113
123
  @sqlserver_options[:database_year]
114
124
  end
115
-
116
-
125
+
126
+
117
127
  private
118
-
128
+
119
129
  def extract_limit(sql_type)
120
130
  case sql_type
121
131
  when /^smallint/i
@@ -130,7 +140,7 @@ module ActiveRecord
130
140
  super
131
141
  end
132
142
  end
133
-
143
+
134
144
  def simplified_type(field_type)
135
145
  case field_type
136
146
  when /real/i then :float
@@ -144,7 +154,7 @@ module ActiveRecord
144
154
  else super
145
155
  end
146
156
  end
147
-
157
+
148
158
  def simplified_datetime
149
159
  if database_year >= 2008
150
160
  :datetime
@@ -156,47 +166,50 @@ module ActiveRecord
156
166
  :datetime
157
167
  end
158
168
  end
159
-
169
+
160
170
  end #class SQLServerColumn
161
-
171
+
162
172
  class SQLServerAdapter < AbstractAdapter
163
-
173
+
164
174
  include Sqlserver::Quoting
165
175
  include Sqlserver::DatabaseStatements
176
+ include Sqlserver::Showplan
166
177
  include Sqlserver::SchemaStatements
167
178
  include Sqlserver::DatabaseLimits
168
179
  include Sqlserver::Errors
169
- include Sqlserver::Version
170
-
180
+
181
+ VERSION = File.read(File.expand_path("../../../../VERSION",__FILE__)).strip
171
182
  ADAPTER_NAME = 'SQLServer'.freeze
172
183
  DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
173
- SUPPORTED_VERSIONS = [2005,2008,2010,2011].freeze
174
-
175
- attr_reader :database_version, :database_year
176
-
184
+ SUPPORTED_VERSIONS = [2005,2008,2010,2011,2012,2014,2016,2017]
185
+
186
+ attr_reader :database_version, :database_year, :spid, :product_level, :product_version, :edition
187
+
177
188
  cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
178
- :log_info_schema_queries, :enable_default_unicode_types, :auto_connect,
179
- :cs_equality_operator, :lowercase_schema_reflection, :auto_connect_duration
180
-
189
+ :enable_default_unicode_types, :auto_connect, :retry_deadlock_victim,
190
+ :cs_equality_operator, :lowercase_schema_reflection, :auto_connect_duration,
191
+ :showplan_option
192
+
181
193
  self.enable_default_unicode_types = true
182
-
183
- class << self
184
-
185
- def visitor_for(pool)
186
- Arel::Visitors::SQLServer.new(pool)
187
- end
188
-
189
- end
190
-
191
- def initialize(logger,config)
194
+
195
+
196
+ def initialize(connection, logger, pool, config)
197
+ super(connection, logger, pool)
198
+ # AbstractAdapter Responsibility
199
+ @schema_cache = Sqlserver::SchemaCache.new self
200
+ @visitor = Arel::Visitors::SQLServer.new self
201
+ # Our Responsibility
202
+ @config = config
192
203
  @connection_options = config
193
204
  connect
194
- super(@connection, logger)
195
- @database_version = info_schema_query { select_value('SELECT @@version') }
205
+ @database_version = select_value 'SELECT @@version', 'SCHEMA'
196
206
  @database_year = begin
197
- if @database_version =~ /Microsoft SQL Azure/i
207
+ if @database_version =~ /Azure/i
198
208
  @sqlserver_azure = true
199
- @database_version.match(/\s(\d{4})\s/)[1].to_i
209
+ @database_version.match(/\s-\s([0-9.]+)/)[1]
210
+ year = 2016
211
+ elsif @database_version =~ /vNext/i
212
+ year = 2016
200
213
  else
201
214
  year = DATABASE_VERSION_REGEXP.match(@database_version)[1]
202
215
  year == "Denali" ? 2011 : year.to_i
@@ -204,49 +217,63 @@ module ActiveRecord
204
217
  rescue
205
218
  0
206
219
  end
220
+ @product_level = select_value "SELECT CAST(SERVERPROPERTY('productlevel') AS VARCHAR(128))", 'SCHEMA'
221
+ @product_version = select_value "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(128))", 'SCHEMA'
222
+ @edition = select_value "SELECT CAST(SERVERPROPERTY('edition') AS VARCHAR(128))", 'SCHEMA'
207
223
  initialize_dateformatter
208
- initialize_sqlserver_caches
209
224
  use_database
210
- unless SUPPORTED_VERSIONS.include?(@database_year)
225
+ unless (@sqlserver_azure == true || SUPPORTED_VERSIONS.include?(@database_year))
211
226
  raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported. We got back #{@database_version}."
212
227
  end
213
228
  end
214
-
229
+
215
230
  # === Abstract Adapter ========================================== #
216
-
231
+
217
232
  def adapter_name
218
233
  ADAPTER_NAME
219
234
  end
220
-
235
+
221
236
  def supports_migrations?
222
237
  true
223
238
  end
224
-
239
+
225
240
  def supports_primary_key?
226
241
  true
227
242
  end
228
-
243
+
229
244
  def supports_count_distinct?
230
245
  true
231
246
  end
232
-
247
+
233
248
  def supports_ddl_transactions?
234
249
  true
235
250
  end
236
-
251
+
252
+ def supports_bulk_alter?
253
+ false
254
+ end
255
+
237
256
  def supports_savepoints?
238
257
  true
239
258
  end
240
-
259
+
260
+ def supports_index_sort_order?
261
+ true
262
+ end
263
+
264
+ def supports_explain?
265
+ true
266
+ end
267
+
241
268
  def disable_referential_integrity
242
269
  do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
243
270
  yield
244
271
  ensure
245
272
  do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
246
273
  end
247
-
274
+
248
275
  # === Abstract Adapter (Connection Management) ================== #
249
-
276
+
250
277
  def active?
251
278
  case @connection_options[:mode]
252
279
  when :dblib
@@ -265,6 +292,7 @@ module ActiveRecord
265
292
  end
266
293
 
267
294
  def disconnect!
295
+ @spid = nil
268
296
  case @connection_options[:mode]
269
297
  when :dblib
270
298
  @connection.close rescue nil
@@ -272,123 +300,130 @@ module ActiveRecord
272
300
  @connection.disconnect rescue nil
273
301
  end
274
302
  end
275
-
303
+
276
304
  def reset!
277
305
  remove_database_connections_and_rollback { }
278
306
  end
279
-
280
- def clear_cache!
281
- initialize_sqlserver_caches
282
- end
283
-
307
+
284
308
  # === Abstract Adapter (Misc Support) =========================== #
285
-
309
+
286
310
  def pk_and_sequence_for(table_name)
287
311
  idcol = identity_column(table_name)
288
312
  idcol ? [idcol.name,nil] : nil
289
313
  end
290
314
 
291
315
  def primary_key(table_name)
292
- identity_column(table_name).try(:name)
316
+ identity_column(table_name).try(:name) || schema_cache.columns[table_name].detect(&:is_primary?).try(:name)
293
317
  end
294
-
318
+
295
319
  # === SQLServer Specific (DB Reflection) ======================== #
296
-
320
+
297
321
  def sqlserver?
298
322
  true
299
323
  end
300
-
324
+
301
325
  def sqlserver_2005?
302
326
  @database_year == 2005
303
327
  end
304
-
328
+
305
329
  def sqlserver_2008?
306
330
  @database_year == 2008
307
331
  end
308
-
332
+
309
333
  def sqlserver_2011?
310
334
  @database_year == 2011
311
335
  end
312
-
336
+
337
+ def sqlserver_2012?
338
+ @database_year == 2012
339
+ end
340
+
313
341
  def sqlserver_azure?
314
342
  @sqlserver_azure
315
343
  end
316
-
344
+
317
345
  def version
318
346
  self.class::VERSION
319
347
  end
320
-
348
+
321
349
  def inspect
322
- "#<#{self.class} version: #{version}, year: #{@database_year}, connection_options: #{@connection_options.inspect}>"
350
+ "#<#{self.class} version: #{version}, year: #{@database_year}, product_level: #{@product_level.inspect}, product_version: #{@product_version.inspect}, edition: #{@edition.inspect}, connection_options: #{@connection_options.inspect}>"
323
351
  end
324
-
352
+
325
353
  def auto_connect
326
354
  @@auto_connect.is_a?(FalseClass) ? false : true
327
355
  end
328
-
356
+
329
357
  def auto_connect_duration
330
358
  @@auto_connect_duration ||= 10
331
359
  end
332
-
360
+
361
+ def retry_deadlock_victim
362
+ @@retry_deadlock_victim.is_a?(FalseClass) ? false : true
363
+ end
364
+ alias :retry_deadlock_victim? :retry_deadlock_victim
365
+
333
366
  def native_string_database_type
334
- @@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
367
+ @@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
335
368
  end
336
-
369
+
337
370
  def native_text_database_type
338
371
  @@native_text_database_type || enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
339
372
  end
340
-
373
+
341
374
  def native_time_database_type
342
375
  sqlserver_2005? ? 'datetime' : 'time'
343
376
  end
344
-
377
+
345
378
  def native_date_database_type
346
379
  sqlserver_2005? ? 'datetime' : 'date'
347
380
  end
348
-
381
+
349
382
  def native_binary_database_type
350
383
  @@native_binary_database_type || 'varbinary(max)'
351
384
  end
352
-
385
+
353
386
  def cs_equality_operator
354
387
  @@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
355
388
  end
356
-
357
-
389
+
358
390
  protected
359
-
391
+
360
392
  # === Abstract Adapter (Misc Support) =========================== #
361
-
393
+
362
394
  def translate_exception(e, message)
363
395
  case message
364
- when /cannot insert duplicate key .* with unique index/i
396
+ when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
365
397
  RecordNotUnique.new(message,e)
366
398
  when /conflicted with the foreign key constraint/i
367
399
  InvalidForeignKey.new(message,e)
400
+ when /has been chosen as the deadlock victim/i
401
+ DeadlockVictim.new(message,e)
368
402
  when *lost_connection_messages
369
403
  LostConnection.new(message,e)
370
404
  else
371
405
  super
372
406
  end
373
407
  end
374
-
408
+
375
409
  # === SQLServer Specific (Connection Management) ================ #
376
-
410
+
377
411
  def connect
378
412
  config = @connection_options
379
- @connection = case @connection_options[:mode]
413
+ @connection = case config[:mode]
380
414
  when :dblib
381
- appname = config[:appname] || Rails.application.class.name.split('::').first rescue nil
415
+ appname = config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
382
416
  login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
383
417
  timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
384
418
  encoding = config[:encoding].present? ? config[:encoding] : nil
385
- TinyTds::Client.new({
419
+ TinyTds::Client.new({
386
420
  :dataserver => config[:dataserver],
387
421
  :host => config[:host],
388
422
  :port => config[:port],
389
423
  :username => config[:username],
390
424
  :password => config[:password],
391
425
  :database => config[:database],
426
+ :tds_version => config[:tds_version],
392
427
  :appname => appname,
393
428
  :login_timeout => login_timeout,
394
429
  :timeout => timeout,
@@ -408,6 +443,8 @@ module ActiveRecord
408
443
  client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
409
444
  client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
410
445
  end
446
+ client.execute("SET TEXTSIZE 2147483647").do
447
+ client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
411
448
  end
412
449
  when :odbc
413
450
  if config[:dsn].include?(';')
@@ -418,7 +455,7 @@ module ActiveRecord
418
455
  ODBC::Database.new.drvconnect(driver)
419
456
  else
420
457
  ODBC.connect config[:dsn], config[:username], config[:password]
421
- end.tap do |c|
458
+ end.tap do |c|
422
459
  begin
423
460
  c.use_time = true
424
461
  c.use_utc = ActiveRecord::Base.default_timezone == :utc
@@ -427,19 +464,34 @@ module ActiveRecord
427
464
  end
428
465
  end
429
466
  end
467
+ @spid = _raw_select("SELECT @@SPID", :fetch => :rows).first.first
468
+ configure_connection
430
469
  rescue
431
470
  raise unless @auto_connecting
432
471
  end
433
-
472
+
473
+ # Override this method so every connection can be configured to your needs.
474
+ # For example:
475
+ # raw_connection_do "SET TEXTSIZE #{64.megabytes}"
476
+ # raw_connection_do "SET CONCAT_NULL_YIELDS_NULL ON"
477
+ def configure_connection
478
+ end
479
+
480
+ # Override this method so every connection can have a unique name. Max 30 characters. Used by TinyTDS only.
481
+ # For example:
482
+ # "myapp_#{$$}_#{Thread.current.object_id}".to(29)
483
+ def configure_application_name
484
+ end
485
+
434
486
  def initialize_dateformatter
435
- @database_dateformat = user_options['dateformat']
487
+ @database_dateformat = user_options_dateformat
436
488
  a, b, c = @database_dateformat.each_char.to_a
437
489
  [a,b,c].each { |f| f.upcase! if f == 'y' }
438
490
  dateformat = "%#{a}-%#{b}-%#{c}"
439
491
  ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
440
492
  ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
441
493
  end
442
-
494
+
443
495
  def remove_database_connections_and_rollback(database=nil)
444
496
  database ||= current_database
445
497
  do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
@@ -449,24 +501,35 @@ module ActiveRecord
449
501
  do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
450
502
  end if block_given?
451
503
  end
452
-
453
- def with_auto_reconnect
504
+
505
+ def with_sqlserver_error_handling
454
506
  begin
455
507
  yield
456
508
  rescue Exception => e
457
- retry if translate_exception(e,e.message).is_a?(LostConnection) && auto_reconnected?
509
+ case translate_exception(e,e.message)
510
+ when LostConnection; retry if auto_reconnected?
511
+ when DeadlockVictim; retry if retry_deadlock_victim? && open_transactions == 0
512
+ end
458
513
  raise
459
514
  end
460
515
  end
461
-
516
+
517
+ def disable_auto_reconnect
518
+ old_auto_connect, self.class.auto_connect = self.class.auto_connect, false
519
+ yield
520
+ ensure
521
+ self.class.auto_connect = old_auto_connect
522
+ end
523
+
462
524
  def auto_reconnected?
463
525
  return false unless auto_connect
464
526
  @auto_connecting = true
465
527
  count = 0
466
528
  while count <= (auto_connect_duration / 2)
467
- sleep 2** count
529
+ result = reconnect!
468
530
  ActiveRecord::Base.did_retry_sqlserver_connection(self,count)
469
- return true if reconnect!
531
+ return true if result
532
+ sleep 2** count
470
533
  count += 1
471
534
  end
472
535
  ActiveRecord::Base.did_lose_sqlserver_connection(self)
@@ -474,10 +537,10 @@ module ActiveRecord
474
537
  ensure
475
538
  @auto_connecting = false
476
539
  end
477
-
540
+
478
541
  end #class SQLServerAdapter < AbstractAdapter
479
-
542
+
480
543
  end #module ConnectionAdapters
481
-
544
+
482
545
  end #module ActiveRecord
483
546