activerecord-sqlserver-adapter 3.1.1 → 3.2.18

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