activerecord-sqlserver-adapter 2.3.7 → 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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +385 -61
  3. data/MIT-LICENSE +1 -1
  4. data/VERSION +1 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +42 -0
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +97 -0
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +26 -0
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
  11. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  12. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
  13. data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
  14. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +113 -0
  15. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
  16. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +376 -0
  17. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
  18. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
  19. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
  20. data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
  21. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +344 -1055
  22. data/lib/arel/visitors/sqlserver.rb +389 -0
  23. metadata +60 -83
  24. data/README.rdoc +0 -190
  25. data/RUNNING_UNIT_TESTS +0 -65
  26. data/Rakefile +0 -41
  27. data/autotest/discover.rb +0 -4
  28. data/autotest/railssqlserver.rb +0 -16
  29. data/autotest/sqlserver.rb +0 -54
  30. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
  31. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
  32. data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
  33. data/test/cases/adapter_test_sqlserver.rb +0 -756
  34. data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
  35. data/test/cases/basics_test_sqlserver.rb +0 -21
  36. data/test/cases/calculations_test_sqlserver.rb +0 -20
  37. data/test/cases/column_test_sqlserver.rb +0 -285
  38. data/test/cases/connection_test_sqlserver.rb +0 -146
  39. data/test/cases/eager_association_test_sqlserver.rb +0 -42
  40. data/test/cases/execute_procedure_test_sqlserver.rb +0 -44
  41. data/test/cases/inheritance_test_sqlserver.rb +0 -28
  42. data/test/cases/method_scoping_test_sqlserver.rb +0 -28
  43. data/test/cases/migration_test_sqlserver.rb +0 -123
  44. data/test/cases/named_scope_test_sqlserver.rb +0 -21
  45. data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
  46. data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
  47. data/test/cases/query_cache_test_sqlserver.rb +0 -24
  48. data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
  49. data/test/cases/specific_schema_test_sqlserver.rb +0 -97
  50. data/test/cases/sqlserver_helper.rb +0 -127
  51. data/test/cases/table_name_test_sqlserver.rb +0 -38
  52. data/test/cases/transaction_test_sqlserver.rb +0 -93
  53. data/test/cases/unicode_test_sqlserver.rb +0 -50
  54. data/test/cases/validations_test_sqlserver.rb +0 -35
  55. data/test/connections/native_sqlserver/connection.rb +0 -25
  56. data/test/connections/native_sqlserver_odbc/connection.rb +0 -27
  57. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
  58. data/test/schema/sqlserver_specific_schema.rb +0 -94
@@ -1,105 +1,115 @@
1
+ require 'base64'
2
+ require 'arel/visitors/sqlserver'
1
3
  require 'active_record'
4
+ require 'active_record/base'
5
+ require 'active_support/concern'
6
+ require 'active_support/core_ext/string'
2
7
  require 'active_record/connection_adapters/abstract_adapter'
3
- require 'active_record/connection_adapters/sqlserver_adapter/core_ext/active_record'
4
- require 'base64'
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'
13
+ require 'active_record/connection_adapters/sqlserver/database_limits'
14
+ require 'active_record/connection_adapters/sqlserver/database_statements'
15
+ require 'active_record/connection_adapters/sqlserver/errors'
16
+ require 'active_record/connection_adapters/sqlserver/schema_cache'
17
+ require 'active_record/connection_adapters/sqlserver/schema_statements'
18
+ require 'active_record/connection_adapters/sqlserver/showplan'
19
+ require 'active_record/connection_adapters/sqlserver/quoting'
20
+ require 'active_record/connection_adapters/sqlserver/utils'
5
21
 
6
22
  module ActiveRecord
7
-
23
+
8
24
  class Base
9
-
25
+
10
26
  def self.sqlserver_connection(config) #:nodoc:
11
- config = config.dup.symbolize_keys!
12
- config.reverse_merge! :mode => :odbc, :host => 'localhost', :username => 'sa', :password => ''
27
+ config = config.symbolize_keys
28
+ config.reverse_merge! :mode => :dblib
13
29
  mode = config[:mode].to_s.downcase.underscore.to_sym
14
30
  case mode
31
+ when :dblib
32
+ require 'tiny_tds'
15
33
  when :odbc
16
- require_library_or_gem 'odbc' unless defined?(ODBC)
17
- require 'active_record/connection_adapters/sqlserver_adapter/core_ext/odbc'
18
34
  raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
19
- when :adonet
20
- require 'System.Data'
21
- raise ArgumentError, 'Missing :database configuration.' unless config.has_key?(:database)
22
- when :ado
23
- raise NotImplementedError, 'Please use version 2.3.1 of the adapter for ADO connections. Future versions may support ADO.NET.'
24
- raise ArgumentError, 'Missing :database configuration.' unless config.has_key?(:database)
35
+ require 'odbc'
36
+ require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
25
37
  else
26
38
  raise ArgumentError, "Unknown connection mode in #{config.inspect}."
27
39
  end
28
- ConnectionAdapters::SQLServerAdapter.new(logger,config.merge(:mode=>mode))
40
+ ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(:mode=>mode))
29
41
  end
30
-
42
+
31
43
  protected
32
-
44
+
33
45
  def self.did_retry_sqlserver_connection(connection,count)
34
46
  logger.info "CONNECTION RETRY: #{connection.class.name} retry ##{count}."
35
47
  end
36
-
48
+
37
49
  def self.did_lose_sqlserver_connection(connection)
38
50
  logger.info "CONNECTION LOST: #{connection.class.name}"
39
51
  end
40
-
52
+
41
53
  end
42
-
54
+
43
55
  module ConnectionAdapters
44
-
56
+
45
57
  class SQLServerColumn < Column
46
-
58
+
47
59
  def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
48
- @sqlserver_options = sqlserver_options
60
+ @sqlserver_options = sqlserver_options.symbolize_keys
49
61
  super(name, default, sql_type, null)
62
+ @primary = @sqlserver_options[:is_identity] || @sqlserver_options[:is_primary]
50
63
  end
51
-
64
+
52
65
  class << self
53
-
54
- def string_to_utf8_encoding(value)
55
- value.force_encoding('UTF-8') rescue value
56
- end
57
-
66
+
58
67
  def string_to_binary(value)
59
- value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
60
68
  "0x#{value.unpack("H*")[0]}"
61
69
  end
62
-
70
+
63
71
  def binary_to_string(value)
64
- value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
65
72
  value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
66
73
  end
67
-
68
- end
69
-
70
- def type_cast(value)
71
- if value && type == :string && is_utf8?
72
- self.class.string_to_utf8_encoding(value)
73
- else
74
- super
75
- end
76
- end
77
-
78
- def type_cast_code(var_name)
79
- if type == :string && is_utf8?
80
- "#{self.class.name}.string_to_utf8_encoding(#{var_name})"
81
- else
82
- super
83
- end
74
+
84
75
  end
85
-
76
+
86
77
  def is_identity?
87
78
  @sqlserver_options[:is_identity]
88
79
  end
89
-
90
- def is_special?
91
- # TODO: Not sure if these should be added: varbinary(max), nchar, nvarchar(max)
92
- sql_type =~ /^text|ntext|image$/
80
+
81
+ def is_primary?
82
+ @sqlserver_options[:is_primary]
93
83
  end
94
-
84
+
95
85
  def is_utf8?
96
- sql_type =~ /nvarchar|ntext|nchar/i
86
+ !!(@sql_type =~ /nvarchar|ntext|nchar/i)
97
87
  end
98
-
88
+
89
+ def is_integer?
90
+ !!(@sql_type =~ /int/i)
91
+ end
92
+
93
+ def is_real?
94
+ !!(@sql_type =~ /real/i)
95
+ end
96
+
97
+ def sql_type_for_statement
98
+ if is_integer? || is_real?
99
+ sql_type.sub(/\((\d+)?\)/,'')
100
+ else
101
+ sql_type
102
+ end
103
+ end
104
+
105
+ def default_function
106
+ @sqlserver_options[:default_function]
107
+ end
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
@@ -140,10 +150,11 @@ module ActiveRecord
140
150
  when /uniqueidentifier/i then :string
141
151
  when /datetime/i then simplified_datetime
142
152
  when /varchar\(max\)/ then :text
153
+ when /timestamp/ then :binary
143
154
  else super
144
155
  end
145
156
  end
146
-
157
+
147
158
  def simplified_datetime
148
159
  if database_year >= 2008
149
160
  :datetime
@@ -155,198 +166,119 @@ module ActiveRecord
155
166
  :datetime
156
167
  end
157
168
  end
158
-
159
- end #SQLServerColumn
160
-
161
- # In ODBC mode, the adapter requires Ruby ODBC and requires that you specify
162
- # a :dsn option. Ruby ODBC is available at http://www.ch-werner.de/rubyodbc/
163
- #
164
- # Options:
165
- #
166
- # * <tt>:username</tt> -- Defaults to sa.
167
- # * <tt>:password</tt> -- Defaults to blank string.
168
- # * <tt>:dsn</tt> -- An ODBC DSN. (required)
169
- #
169
+
170
+ end #class SQLServerColumn
171
+
170
172
  class SQLServerAdapter < AbstractAdapter
171
-
173
+
174
+ include Sqlserver::Quoting
175
+ include Sqlserver::DatabaseStatements
176
+ include Sqlserver::Showplan
177
+ include Sqlserver::SchemaStatements
178
+ include Sqlserver::DatabaseLimits
179
+ include Sqlserver::Errors
180
+
181
+ VERSION = File.read(File.expand_path("../../../../VERSION",__FILE__)).strip
172
182
  ADAPTER_NAME = 'SQLServer'.freeze
173
- VERSION = '2.3.7'.freeze
174
- DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
175
- SUPPORTED_VERSIONS = [2000,2005,2008].freeze
176
- LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].freeze
177
- LOST_CONNECTION_EXCEPTIONS = {
178
- :odbc => ['ODBC::Error'],
179
- :adonet => ['TypeError','System::Data::SqlClient::SqlException']
180
- }
181
- LOST_CONNECTION_MESSAGES = {
182
- :odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
183
- :adonet => [/current state is closed/, /network-related/]
184
- }
185
-
183
+ DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
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
+
186
188
  cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
187
- :log_info_schema_queries, :enable_default_unicode_types, :auto_connect
188
-
189
- class << self
190
-
191
- def type_limitable?(type)
192
- LIMITABLE_TYPES.include?(type.to_s)
193
- end
194
-
195
- end
196
-
197
- def initialize(logger,config)
189
+ :enable_default_unicode_types, :auto_connect, :retry_deadlock_victim,
190
+ :cs_equality_operator, :lowercase_schema_reflection, :auto_connect_duration,
191
+ :showplan_option
192
+
193
+ self.enable_default_unicode_types = true
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
198
203
  @connection_options = config
199
204
  connect
200
- super(raw_connection, logger)
201
- initialize_sqlserver_caches
205
+ @database_version = select_value 'SELECT @@version', 'SCHEMA'
206
+ @database_year = begin
207
+ if @database_version =~ /Azure/i
208
+ @sqlserver_azure = true
209
+ @database_version.match(/\s-\s([0-9.]+)/)[1]
210
+ year = 2016
211
+ elsif @database_version =~ /vNext/i
212
+ year = 2016
213
+ else
214
+ year = DATABASE_VERSION_REGEXP.match(@database_version)[1]
215
+ year == "Denali" ? 2011 : year.to_i
216
+ end
217
+ rescue
218
+ 0
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'
223
+ initialize_dateformatter
202
224
  use_database
203
- unless SUPPORTED_VERSIONS.include?(database_year)
204
- raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported."
225
+ unless (@sqlserver_azure == true || SUPPORTED_VERSIONS.include?(@database_year))
226
+ raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported. We got back #{@database_version}."
205
227
  end
206
228
  end
207
-
208
- # ABSTRACT ADAPTER =========================================#
209
-
229
+
230
+ # === Abstract Adapter ========================================== #
231
+
210
232
  def adapter_name
211
233
  ADAPTER_NAME
212
234
  end
213
-
235
+
214
236
  def supports_migrations?
215
237
  true
216
238
  end
217
-
218
- def supports_ddl_transactions?
239
+
240
+ def supports_primary_key?
219
241
  true
220
242
  end
221
-
222
- def supports_savepoints?
243
+
244
+ def supports_count_distinct?
223
245
  true
224
246
  end
225
-
226
- def database_version
227
- @database_version ||= info_schema_query { select_value('SELECT @@version') }
228
- end
229
-
230
- def database_year
231
- DATABASE_VERSION_REGEXP.match(database_version)[1].to_i
232
- end
233
-
234
- def sqlserver?
247
+
248
+ def supports_ddl_transactions?
235
249
  true
236
250
  end
237
-
238
- def sqlserver_2000?
239
- database_year == 2000
240
- end
241
-
242
- def sqlserver_2005?
243
- database_year == 2005
244
- end
245
-
246
- def sqlserver_2008?
247
- database_year == 2008
248
- end
249
-
250
- def version
251
- self.class::VERSION
252
- end
253
-
254
- def inspect
255
- "#<#{self.class} version: #{version}, year: #{database_year}, connection_options: #{@connection_options.inspect}>"
256
- end
257
-
258
- def auto_connect
259
- @@auto_connect.is_a?(FalseClass) ? false : true
260
- end
261
-
262
- def native_string_database_type
263
- @@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
264
- end
265
-
266
- def native_text_database_type
267
- @@native_text_database_type ||
268
- if sqlserver_2005? || sqlserver_2008?
269
- enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
270
- else
271
- enable_default_unicode_types ? 'ntext' : 'text'
272
- end
273
- end
274
-
275
- def native_time_database_type
276
- sqlserver_2008? ? 'time' : 'datetime'
277
- end
278
-
279
- def native_date_database_type
280
- sqlserver_2008? ? 'date' : 'datetime'
281
- end
282
-
283
- def native_binary_database_type
284
- @@native_binary_database_type || ((sqlserver_2005? || sqlserver_2008?) ? 'varbinary(max)' : 'image')
285
- end
286
-
287
-
288
- # QUOTING ==================================================#
289
-
290
- def quote(value, column = nil)
291
- case value
292
- when String, ActiveSupport::Multibyte::Chars
293
- if column && column.type == :binary
294
- column.class.string_to_binary(value)
295
- elsif column && column.respond_to?(:is_utf8?) && column.is_utf8?
296
- quoted_utf8_value(value)
297
- else
298
- super
299
- end
300
- else
301
- super
302
- end
303
- end
304
-
305
- def quote_string(string)
306
- string.to_s.gsub(/\'/, "''")
307
- end
308
-
309
- def quote_column_name(column_name)
310
- column_name.to_s.split('.').map{ |name| name =~ /^\[.*\]$/ ? name : "[#{name}]" }.join('.')
311
- end
312
-
313
- def quote_table_name(table_name)
314
- return table_name if table_name =~ /^\[.*\]$/
315
- quote_column_name(table_name)
316
- end
317
-
318
- def quoted_true
319
- '1'
251
+
252
+ def supports_bulk_alter?
253
+ false
320
254
  end
321
255
 
322
- def quoted_false
323
- '0'
256
+ def supports_savepoints?
257
+ true
324
258
  end
325
-
326
- def quoted_date(value)
327
- if value.acts_like?(:time) && value.respond_to?(:usec)
328
- "#{super}.#{sprintf("%03d",value.usec/1000)}"
329
- else
330
- super
331
- end
259
+
260
+ def supports_index_sort_order?
261
+ true
332
262
  end
333
-
334
- def quoted_utf8_value(value)
335
- "N'#{quote_string(value)}'"
263
+
264
+ def supports_explain?
265
+ true
336
266
  end
337
-
338
- # REFERENTIAL INTEGRITY ====================================#
339
-
267
+
340
268
  def disable_referential_integrity
341
269
  do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
342
270
  yield
343
271
  ensure
344
272
  do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
345
273
  end
346
-
347
- # CONNECTION MANAGEMENT ====================================#
348
-
274
+
275
+ # === Abstract Adapter (Connection Management) ================== #
276
+
349
277
  def active?
278
+ case @connection_options[:mode]
279
+ when :dblib
280
+ return @connection.active?
281
+ end
350
282
  raw_connection_do("SELECT 1")
351
283
  true
352
284
  rescue *lost_connection_exceptions
@@ -360,500 +292,244 @@ module ActiveRecord
360
292
  end
361
293
 
362
294
  def disconnect!
363
- case connection_mode
295
+ @spid = nil
296
+ case @connection_options[:mode]
297
+ when :dblib
298
+ @connection.close rescue nil
364
299
  when :odbc
365
- raw_connection.disconnect rescue nil
366
- else :adonet
367
- raw_connection.close rescue nil
368
- end
369
- end
370
-
371
- # DATABASE STATEMENTS ======================================#
372
-
373
- def user_options
374
- info_schema_query do
375
- select_rows("dbcc useroptions").inject(HashWithIndifferentAccess.new) do |values,row|
376
- set_option = row[0].gsub(/\s+/,'_')
377
- user_value = row[1]
378
- values[set_option] = user_value
379
- values
380
- end
381
- end
382
- end
383
-
384
- VALID_ISOLATION_LEVELS = ["READ COMMITTED", "READ UNCOMMITTED", "REPEATABLE READ", "SERIALIZABLE", "SNAPSHOT"]
385
-
386
- def run_with_isolation_level(isolation_level)
387
- raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{VALID_ISOLATION_LEVELS.to_sentence}." if !VALID_ISOLATION_LEVELS.include?(isolation_level.upcase)
388
- initial_isolation_level = user_options[:isolation_level] || "READ COMMITTED"
389
- do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
390
- begin
391
- yield
392
- ensure
393
- do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
394
- end if block_given?
395
- end
396
-
397
- def select_rows(sql, name = nil)
398
- raw_select(sql,name).first.last
399
- end
400
-
401
- def execute(sql, name = nil, skip_logging = false)
402
- if table_name = query_requires_identity_insert?(sql)
403
- with_identity_insert_enabled(table_name) { do_execute(sql,name) }
404
- else
405
- do_execute(sql,name)
300
+ @connection.disconnect rescue nil
406
301
  end
407
302
  end
408
-
409
- def execute_procedure(proc_name, *variables)
410
- vars = variables.map{ |v| quote(v) }.join(', ')
411
- sql = "EXEC #{proc_name} #{vars}".strip
412
- select(sql,'Execute Procedure',true).inject([]) do |results,row|
413
- if row.kind_of?(Array)
414
- results << row.inject([]) { |rs,r| rs << r.with_indifferent_access }
415
- else
416
- results << row.with_indifferent_access
417
- end
418
- end
419
- end
420
-
421
- def use_database(database=nil)
422
- database ||= @connection_options[:database]
423
- do_execute "USE #{database}" unless database.blank?
424
- end
425
-
426
- def outside_transaction?
427
- info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
428
- end
429
-
430
- def begin_db_transaction
431
- do_execute "BEGIN TRANSACTION"
432
- end
433
303
 
434
- def commit_db_transaction
435
- do_execute "COMMIT TRANSACTION"
304
+ def reset!
305
+ remove_database_connections_and_rollback { }
436
306
  end
437
307
 
438
- def rollback_db_transaction
439
- do_execute "ROLLBACK TRANSACTION" rescue nil
440
- end
441
-
442
- def create_savepoint
443
- do_execute "SAVE TRANSACTION #{current_savepoint_name}"
444
- end
308
+ # === Abstract Adapter (Misc Support) =========================== #
445
309
 
446
- def release_savepoint
447
- end
448
-
449
- def rollback_to_savepoint
450
- do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}"
451
- end
452
-
453
- def add_limit_offset!(sql, options)
454
- # Validate and/or convert integers for :limit and :offets options.
455
- if options[:offset]
456
- raise ArgumentError, "offset should have a limit" unless options[:limit]
457
- unless options[:offset].kind_of?(Integer)
458
- if options[:offset] =~ /^\d+$/
459
- options[:offset] = options[:offset].to_i
460
- else
461
- raise ArgumentError, "offset should be an integer"
462
- end
463
- end
464
- end
465
- if options[:limit] && !(options[:limit].kind_of?(Integer))
466
- if options[:limit] =~ /^\d+$/
467
- options[:limit] = options[:limit].to_i
468
- else
469
- raise ArgumentError, "limit should be an integer"
470
- end
471
- end
472
- # The business of adding limit/offset
473
- if options[:limit] and options[:offset]
474
- tally_sql = "SELECT count(*) as TotalRows from (#{sql.sub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally"
475
- add_lock! tally_sql, options
476
- total_rows = select_value(tally_sql).to_i
477
- if (options[:limit] + options[:offset]) >= total_rows
478
- options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
479
- end
480
- # Make sure we do not need a special limit/offset for association limiting. http://gist.github.com/25118
481
- add_limit_offset_for_association_limiting!(sql,options) and return if sql_for_association_limiting?(sql)
482
- # Wrap the SQL query in a bunch of outer SQL queries that emulate proper LIMIT,OFFSET support.
483
- sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]}")
484
- sql << ") AS tmp1"
485
- if options[:order]
486
- order = options[:order].split(',').map do |field|
487
- order_by_column, order_direction = field.split(" ")
488
- order_by_column = quote_column_name(order_by_column)
489
- # Investigate the SQL query to figure out if the order_by_column has been renamed.
490
- if sql =~ /#{Regexp.escape(order_by_column)} AS (t\d+_r\d+)/
491
- # Fx "[foo].[bar] AS t4_r2" was found in the SQL. Use the column alias (ie 't4_r2') for the subsequent orderings
492
- order_by_column = $1
493
- elsif order_by_column =~ /\w+\.\[?(\w+)\]?/
494
- order_by_column = $1
495
- else
496
- # It doesn't appear that the column name has been renamed as part of the query. Use just the column
497
- # name rather than the full identifier for the outer queries.
498
- order_by_column = order_by_column.split('.').last
499
- end
500
- # Put the column name and eventual direction back together
501
- [order_by_column, order_direction].join(' ').strip
502
- end.join(', ')
503
- sql << " ORDER BY #{change_order_direction(order)}) AS tmp2 ORDER BY #{order}"
504
- else
505
- sql << ") AS tmp2"
506
- end
507
- elsif options[:limit] && sql !~ /^\s*SELECT (@@|COUNT\()/i
508
- if md = sql.match(/^(\s*SELECT)(\s+DISTINCT)?(.*)/im)
509
- sql.replace "#{md[1]}#{md[2]} TOP #{options[:limit]}#{md[3]}"
510
- else
511
- # Account for building SQL fragments without SELECT yet. See #update_all and #limited_update_conditions.
512
- sql.replace "TOP #{options[:limit]} #{sql}"
513
- end
514
- end
515
- end
516
-
517
- def add_lock!(sql, options)
518
- # http://blog.sqlauthority.com/2007/04/27/sql-server-2005-locking-hints-and-examples/
519
- return unless options[:lock]
520
- lock_type = options[:lock] == true ? 'WITH(HOLDLOCK, ROWLOCK)' : options[:lock]
521
- sql.gsub! %r|LEFT OUTER JOIN\s+(.*?)\s+ON|im, "LEFT OUTER JOIN \\1 #{lock_type} ON"
522
- sql.gsub! %r{FROM\s([\w\[\]\.]+)}im, "FROM \\1 #{lock_type}"
523
- end
524
-
525
- def empty_insert_statement(table_name)
526
- "INSERT INTO #{quote_table_name(table_name)} DEFAULT VALUES"
527
- end
528
-
529
- def case_sensitive_equality_operator
530
- "COLLATE Latin1_General_CS_AS ="
531
- end
532
-
533
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
534
- match_data = where_sql.match(/^(.*?[\]\) ])WHERE[\[\( ]/)
535
- limit = match_data[1]
536
- where_sql.sub!(limit,'')
537
- "WHERE #{quoted_primary_key} IN (SELECT #{limit} #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
310
+ def pk_and_sequence_for(table_name)
311
+ idcol = identity_column(table_name)
312
+ idcol ? [idcol.name,nil] : nil
538
313
  end
539
-
540
- # SCHEMA STATEMENTS ========================================#
541
-
542
- def native_database_types
543
- {
544
- :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
545
- :string => { :name => native_string_database_type, :limit => 255 },
546
- :text => { :name => native_text_database_type },
547
- :integer => { :name => "int", :limit => 4 },
548
- :float => { :name => "float", :limit => 8 },
549
- :decimal => { :name => "decimal" },
550
- :datetime => { :name => "datetime" },
551
- :timestamp => { :name => "datetime" },
552
- :time => { :name => native_time_database_type },
553
- :date => { :name => native_date_database_type },
554
- :binary => { :name => native_binary_database_type },
555
- :boolean => { :name => "bit"},
556
- # These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
557
- :char => { :name => 'char' },
558
- :varchar_max => { :name => 'varchar(max)' },
559
- :nchar => { :name => "nchar" },
560
- :nvarchar => { :name => "nvarchar", :limit => 255 },
561
- :nvarchar_max => { :name => "nvarchar(max)" },
562
- :ntext => { :name => "ntext" }
563
- }
314
+
315
+ def primary_key(table_name)
316
+ identity_column(table_name).try(:name) || schema_cache.columns[table_name].detect(&:is_primary?).try(:name)
564
317
  end
565
-
566
- def table_alias_length
567
- 128
318
+
319
+ # === SQLServer Specific (DB Reflection) ======================== #
320
+
321
+ def sqlserver?
322
+ true
568
323
  end
569
-
570
- def tables(name = nil)
571
- info_schema_query do
572
- select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
573
- end
324
+
325
+ def sqlserver_2005?
326
+ @database_year == 2005
574
327
  end
575
-
576
- def views(name = nil)
577
- @sqlserver_views_cache ||=
578
- info_schema_query { select_values("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')") }
328
+
329
+ def sqlserver_2008?
330
+ @database_year == 2008
579
331
  end
580
-
581
- def view_information(table_name)
582
- table_name = unqualify_table_name(table_name)
583
- @sqlserver_view_information_cache[table_name] ||= begin
584
- view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
585
- if view_info
586
- if view_info['VIEW_DEFINITION'].blank? || view_info['VIEW_DEFINITION'].length == 4000
587
- view_info['VIEW_DEFINITION'] = info_schema_query { select_values("EXEC sp_helptext #{table_name}").join }
588
- end
589
- end
590
- view_info
591
- end
332
+
333
+ def sqlserver_2011?
334
+ @database_year == 2011
592
335
  end
593
-
594
- def view_table_name(table_name)
595
- view_info = view_information(table_name)
596
- view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
336
+
337
+ def sqlserver_2012?
338
+ @database_year == 2012
597
339
  end
598
-
599
- def table_exists?(table_name)
600
- super || tables.include?(unqualify_table_name(table_name)) || views.include?(table_name.to_s)
340
+
341
+ def sqlserver_azure?
342
+ @sqlserver_azure
601
343
  end
602
-
603
- def indexes(table_name, name = nil)
604
- unquoted_table_name = unqualify_table_name(table_name)
605
- select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name).inject([]) do |indexes,index|
606
- if index['index_description'] =~ /primary key/
607
- indexes
608
- else
609
- name = index['index_name']
610
- unique = index['index_description'] =~ /unique/
611
- columns = index['index_keys'].split(',').map do |column|
612
- column.strip!
613
- column.gsub! '(-)', '' if column.ends_with?('(-)')
614
- column
615
- end
616
- indexes << IndexDefinition.new(table_name, name, unique, columns)
617
- end
618
- end
344
+
345
+ def version
346
+ self.class::VERSION
619
347
  end
620
-
621
- def columns(table_name, name = nil)
622
- return [] if table_name.blank?
623
- cache_key = unqualify_table_name(table_name)
624
- @sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
625
- sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
626
- SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
627
- end
348
+
349
+ def 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}>"
628
351
  end
629
-
630
- def create_table(table_name, options = {})
631
- super
632
- remove_sqlserver_columns_cache_for(table_name)
352
+
353
+ def auto_connect
354
+ @@auto_connect.is_a?(FalseClass) ? false : true
633
355
  end
634
-
635
- def rename_table(table_name, new_name)
636
- do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
356
+
357
+ def auto_connect_duration
358
+ @@auto_connect_duration ||= 10
637
359
  end
638
-
639
- def drop_table(table_name, options = {})
640
- super
641
- remove_sqlserver_columns_cache_for(table_name)
360
+
361
+ def retry_deadlock_victim
362
+ @@retry_deadlock_victim.is_a?(FalseClass) ? false : true
642
363
  end
643
-
644
- def add_column(table_name, column_name, type, options = {})
645
- super
646
- remove_sqlserver_columns_cache_for(table_name)
364
+ alias :retry_deadlock_victim? :retry_deadlock_victim
365
+
366
+ def native_string_database_type
367
+ @@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
647
368
  end
648
-
649
- def remove_column(table_name, *column_names)
650
- column_names.flatten.each do |column_name|
651
- remove_check_constraints(table_name, column_name)
652
- remove_default_constraint(table_name, column_name)
653
- remove_indexes(table_name, column_name)
654
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
655
- end
656
- remove_sqlserver_columns_cache_for(table_name)
369
+
370
+ def native_text_database_type
371
+ @@native_text_database_type || enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
657
372
  end
658
-
659
- def change_column(table_name, column_name, type, options = {})
660
- sql_commands = []
661
- column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
662
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
663
- change_column_sql << " NOT NULL" if options[:null] == false
664
- sql_commands << change_column_sql
665
- if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
666
- remove_default_constraint(table_name,column_name)
667
- end
668
- if options_include_default?(options)
669
- remove_sqlserver_columns_cache_for(table_name)
670
- sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
671
- end
672
- sql_commands.each { |c| do_execute(c) }
673
- remove_sqlserver_columns_cache_for(table_name)
373
+
374
+ def native_time_database_type
375
+ sqlserver_2005? ? 'datetime' : 'time'
674
376
  end
675
-
676
- def change_column_default(table_name, column_name, default)
677
- remove_default_constraint(table_name, column_name)
678
- do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
679
- remove_sqlserver_columns_cache_for(table_name)
377
+
378
+ def native_date_database_type
379
+ sqlserver_2005? ? 'datetime' : 'date'
680
380
  end
681
-
682
- def rename_column(table_name, column_name, new_column_name)
683
- column_for(table_name,column_name)
684
- do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
685
- remove_sqlserver_columns_cache_for(table_name)
381
+
382
+ def native_binary_database_type
383
+ @@native_binary_database_type || 'varbinary(max)'
686
384
  end
687
-
688
- def remove_index(table_name, options = {})
689
- do_execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}" rescue nil
385
+
386
+ def cs_equality_operator
387
+ @@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
690
388
  end
691
-
692
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
693
- limit = nil unless self.class.type_limitable?(type)
694
- case type.to_s
695
- when 'integer'
696
- case limit
697
- when 1..2 then 'smallint'
698
- when 3..4, nil then 'integer'
699
- when 5..8 then 'bigint'
700
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
701
- end
389
+
390
+ protected
391
+
392
+ # === Abstract Adapter (Misc Support) =========================== #
393
+
394
+ def translate_exception(e, message)
395
+ case message
396
+ when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
397
+ RecordNotUnique.new(message,e)
398
+ when /conflicted with the foreign key constraint/i
399
+ InvalidForeignKey.new(message,e)
400
+ when /has been chosen as the deadlock victim/i
401
+ DeadlockVictim.new(message,e)
402
+ when *lost_connection_messages
403
+ LostConnection.new(message,e)
702
404
  else
703
405
  super
704
406
  end
705
407
  end
706
-
707
- def add_order_by_for_association_limiting!(sql, options)
708
- # Disertation http://gist.github.com/24073
709
- # Information http://weblogs.sqlteam.com/jeffs/archive/2007/12/13/select-distinct-order-by-error.aspx
710
- return sql if options[:order].blank?
711
- columns = sql.match(/SELECT\s+DISTINCT(.*?)FROM/)[1].strip
712
- sql.sub!(/SELECT\s+DISTINCT/,'SELECT')
713
- sql << "GROUP BY #{columns} ORDER BY #{order_to_min_set(options[:order])}"
714
- end
715
-
716
- def change_column_null(table_name, column_name, null, default = nil)
717
- column = column_for(table_name,column_name)
718
- unless null || default.nil?
719
- do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
720
- end
721
- sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
722
- sql << " NOT NULL" unless null
723
- do_execute sql
724
- end
725
-
726
- def pk_and_sequence_for(table_name)
727
- idcol = identity_column(table_name)
728
- idcol ? [idcol.name,nil] : nil
729
- end
730
-
731
- # RAKE UTILITY METHODS =====================================#
732
-
733
- def recreate_database
734
- remove_database_connections_and_rollback do
735
- do_execute "EXEC sp_MSforeachtable 'DROP TABLE ?'"
736
- end
737
- end
738
-
739
- def recreate_database!(database=nil)
740
- current_db = current_database
741
- database ||= current_db
742
- this_db = database.to_s == current_db
743
- do_execute 'USE master' if this_db
744
- drop_database(database)
745
- create_database(database)
746
- ensure
747
- use_database(current_db) if this_db
748
- end
749
-
750
- # Remove existing connections and rollback any transactions if we received the message
751
- # 'Cannot drop the database 'test' because it is currently in use'
752
- def drop_database(database)
753
- retry_count = 0
754
- max_retries = 1
755
- begin
756
- do_execute "DROP DATABASE #{database}"
757
- rescue ActiveRecord::StatementInvalid => err
758
- if err.message =~ /because it is currently in use/i
759
- raise if retry_count >= max_retries
760
- retry_count += 1
761
- remove_database_connections_and_rollback(database)
762
- retry
763
- else
764
- raise
765
- end
766
- end
767
- end
768
408
 
769
- def create_database(database)
770
- do_execute "CREATE DATABASE #{database}"
771
- end
772
-
773
- def current_database
774
- select_value 'SELECT DB_NAME()'
775
- end
776
-
777
- def charset
778
- select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
779
- end
780
-
781
- # This should disconnect all other users and rollback any transactions for SQL 2000 and 2005
782
- # http://sqlserver2000.databases.aspfaq.com/how-do-i-drop-a-sql-server-database.html
783
- def remove_database_connections_and_rollback(database=nil)
784
- database ||= current_database
785
- do_execute "ALTER DATABASE #{database} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
786
- begin
787
- yield
788
- ensure
789
- do_execute "ALTER DATABASE #{database} SET MULTI_USER"
790
- end if block_given?
791
- end
792
-
793
-
794
-
795
- protected
796
-
797
- # CONNECTION MANAGEMENT ====================================#
798
-
409
+ # === SQLServer Specific (Connection Management) ================ #
410
+
799
411
  def connect
800
412
  config = @connection_options
801
- @connection = case connection_mode
413
+ @connection = case config[:mode]
414
+ when :dblib
415
+ appname = config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
416
+ login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
417
+ timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
418
+ encoding = config[:encoding].present? ? config[:encoding] : nil
419
+ TinyTds::Client.new({
420
+ :dataserver => config[:dataserver],
421
+ :host => config[:host],
422
+ :port => config[:port],
423
+ :username => config[:username],
424
+ :password => config[:password],
425
+ :database => config[:database],
426
+ :tds_version => config[:tds_version],
427
+ :appname => appname,
428
+ :login_timeout => login_timeout,
429
+ :timeout => timeout,
430
+ :encoding => encoding,
431
+ :azure => config[:azure]
432
+ }).tap do |client|
433
+ if config[:azure]
434
+ client.execute("SET ANSI_NULLS ON").do
435
+ client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
436
+ client.execute("SET ANSI_NULL_DFLT_ON ON").do
437
+ client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
438
+ client.execute("SET ANSI_PADDING ON").do
439
+ client.execute("SET QUOTED_IDENTIFIER ON")
440
+ client.execute("SET ANSI_WARNINGS ON").do
441
+ else
442
+ client.execute("SET ANSI_DEFAULTS ON").do
443
+ client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
444
+ client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
445
+ end
446
+ client.execute("SET TEXTSIZE 2147483647").do
447
+ client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
448
+ end
802
449
  when :odbc
803
- ODBC.connect config[:dsn], config[:username], config[:password]
804
- when :adonet
805
- System::Data::SqlClient::SqlConnection.new.tap do |connection|
806
- connection.connection_string = System::Data::SqlClient::SqlConnectionStringBuilder.new.tap do |cs|
807
- if config[:integrated_security]
808
- cs.integrated_security = true
809
- else
810
- cs.user_i_d = config[:username]
811
- cs.password = config[:password]
812
- end
813
- cs.add 'Server', config[:host].to_clr_string
814
- cs.initial_catalog = config[:database]
815
- cs.multiple_active_result_sets = false
816
- cs.pooling = false
817
- end.to_s
818
- connection.open
450
+ if config[:dsn].include?(';')
451
+ driver = ODBC::Driver.new.tap do |d|
452
+ d.name = config[:dsn_name] || 'Driver1'
453
+ d.attrs = config[:dsn].split(';').map{ |atr| atr.split('=') }.reject{ |kv| kv.size != 2 }.inject({}){ |h,kv| k,v = kv ; h[k] = v ; h }
454
+ end
455
+ ODBC::Database.new.drvconnect(driver)
456
+ else
457
+ ODBC.connect config[:dsn], config[:username], config[:password]
458
+ end.tap do |c|
459
+ begin
460
+ c.use_time = true
461
+ c.use_utc = ActiveRecord::Base.default_timezone == :utc
462
+ rescue Exception => e
463
+ warn "Ruby ODBC v0.99992 or higher is required."
464
+ end
819
465
  end
820
466
  end
467
+ @spid = _raw_select("SELECT @@SPID", :fetch => :rows).first.first
468
+ configure_connection
821
469
  rescue
822
470
  raise unless @auto_connecting
823
471
  end
824
-
825
- def connection_mode
826
- @connection_options[:mode]
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
827
484
  end
828
-
829
- def lost_connection_exceptions
830
- exceptions = LOST_CONNECTION_EXCEPTIONS[connection_mode]
831
- @lost_connection_exceptions ||= exceptions ? exceptions.map(&:constantize) : []
485
+
486
+ def initialize_dateformatter
487
+ @database_dateformat = user_options_dateformat
488
+ a, b, c = @database_dateformat.each_char.to_a
489
+ [a,b,c].each { |f| f.upcase! if f == 'y' }
490
+ dateformat = "%#{a}-%#{b}-%#{c}"
491
+ ::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
492
+ ::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
832
493
  end
833
-
834
- def lost_connection_messages
835
- LOST_CONNECTION_MESSAGES[connection_mode]
494
+
495
+ def remove_database_connections_and_rollback(database=nil)
496
+ database ||= current_database
497
+ do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
498
+ begin
499
+ yield
500
+ ensure
501
+ do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
502
+ end if block_given?
836
503
  end
837
-
838
- def with_auto_reconnect
504
+
505
+ def with_sqlserver_error_handling
839
506
  begin
840
507
  yield
841
- rescue *lost_connection_exceptions => e
842
- if lost_connection_messages.any? { |lcm| e.message =~ lcm }
843
- retry if auto_reconnected?
508
+ rescue Exception => e
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
844
512
  end
845
513
  raise
846
514
  end
847
515
  end
848
-
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
+
849
524
  def auto_reconnected?
850
525
  return false unless auto_connect
851
526
  @auto_connecting = true
852
527
  count = 0
853
- while count <= 5
854
- sleep 2** count
528
+ while count <= (auto_connect_duration / 2)
529
+ result = reconnect!
855
530
  ActiveRecord::Base.did_retry_sqlserver_connection(self,count)
856
- return true if reconnect!
531
+ return true if result
532
+ sleep 2** count
857
533
  count += 1
858
534
  end
859
535
  ActiveRecord::Base.did_lose_sqlserver_connection(self)
@@ -861,397 +537,10 @@ module ActiveRecord
861
537
  ensure
862
538
  @auto_connecting = false
863
539
  end
864
-
865
- def raw_connection_run(sql)
866
- with_auto_reconnect do
867
- case connection_mode
868
- when :odbc
869
- block_given? ? raw_connection.run_block(sql) { |handle| yield(handle) } : raw_connection.run(sql)
870
- else :adonet
871
- raw_connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_reader
872
- end
873
- end
874
- end
875
-
876
- def raw_connection_do(sql)
877
- case connection_mode
878
- when :odbc
879
- raw_connection.do(sql)
880
- else :adonet
881
- raw_connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
882
- end
883
- end
884
-
885
- def finish_statement_handle(handle)
886
- case connection_mode
887
- when :odbc
888
- handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
889
- when :adonet
890
- handle.close if handle && handle.respond_to?(:close) && !handle.is_closed
891
- handle.dispose if handle && handle.respond_to?(:dispose)
892
- end
893
- handle
894
- end
895
-
896
- # DATABASE STATEMENTS ======================================
897
-
898
- def select(sql, name = nil, ignore_special_columns = false)
899
- repair_special_columns(sql) unless ignore_special_columns
900
- fields_and_row_sets = raw_select(sql,name)
901
- final_result_set = fields_and_row_sets.inject([]) do |rs,fields_and_rows|
902
- fields, rows = fields_and_rows
903
- rs << zip_fields_and_rows(fields,rows)
904
- end
905
- final_result_set.many? ? final_result_set : final_result_set.first
906
- end
907
-
908
- def zip_fields_and_rows(fields, rows)
909
- rows.inject([]) do |results,row|
910
- row_hash = {}
911
- fields.each_with_index do |f, i|
912
- row_hash[f] = row[i]
913
- end
914
- results << row_hash
915
- end
916
- end
917
-
918
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
919
- super || select_value("SELECT SCOPE_IDENTITY() AS Ident")
920
- end
921
-
922
- def update_sql(sql, name = nil)
923
- execute(sql, name)
924
- select_value('SELECT @@ROWCOUNT AS AffectedRows')
925
- end
926
-
927
- def info_schema_query
928
- log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
929
- end
930
-
931
- def do_execute(sql,name=nil)
932
- log(sql, name || 'EXECUTE') do
933
- with_auto_reconnect { raw_connection_do(sql) }
934
- end
935
- end
936
-
937
- def raw_select(sql, name = nil)
938
- fields_and_row_sets = []
939
- log(sql,name) do
940
- begin
941
- handle = raw_connection_run(sql)
942
- loop do
943
- fields_and_rows = case connection_mode
944
- when :odbc
945
- handle_to_fields_and_rows_odbc(handle)
946
- when :adonet
947
- handle_to_fields_and_rows_adonet(handle)
948
- end
949
- fields_and_row_sets << fields_and_rows
950
- break unless handle_more_results?(handle)
951
- end
952
- ensure
953
- finish_statement_handle(handle)
954
- end
955
- end
956
- fields_and_row_sets
957
- end
958
-
959
- def handle_more_results?(handle)
960
- case connection_mode
961
- when :odbc
962
- handle.more_results
963
- when :adonet
964
- handle.next_result
965
- end
966
- end
967
-
968
- def handle_to_fields_and_rows_odbc(handle)
969
- fields = handle.columns(true).map { |c| c.name }
970
- results = handle.inject([]) do |rows,row|
971
- rows << row.inject([]) { |values,value| values << value }
972
- end
973
- rows = results.inject([]) do |rows,row|
974
- row.each_with_index do |value, i|
975
- if value.is_a? ODBC::TimeStamp
976
- row[i] = value.to_sqlserver_string
977
- end
978
- end
979
- rows << row
980
- end
981
- [fields,rows]
982
- end
983
-
984
- def handle_to_fields_and_rows_adonet(handle)
985
- if handle.has_rows
986
- fields = []
987
- rows = []
988
- fields_named = false
989
- while handle.read
990
- row = []
991
- handle.visible_field_count.times do |row_index|
992
- value = handle.get_value(row_index)
993
- value = if value.is_a? System::String
994
- value.to_s
995
- elsif value.is_a? System::DBNull
996
- nil
997
- elsif value.is_a? System::DateTime
998
- value.to_string("yyyy-MM-dd HH:MM:ss.fff").to_s
999
- else
1000
- value
1001
- end
1002
- row << value
1003
- fields << handle.get_name(row_index).to_s unless fields_named
1004
- end
1005
- rows << row
1006
- fields_named = true
1007
- end
1008
- else
1009
- fields, rows = [], []
1010
- end
1011
- [fields,rows]
1012
- end
1013
-
1014
- def add_limit_offset_for_association_limiting!(sql, options)
1015
- sql.replace %|
1016
- SET NOCOUNT ON
1017
- DECLARE @row_number TABLE (row int identity(1,1), id int)
1018
- INSERT INTO @row_number (id)
1019
- #{sql}
1020
- SET NOCOUNT OFF
1021
- SELECT id FROM (
1022
- SELECT TOP #{options[:limit]} * FROM (
1023
- SELECT TOP #{options[:limit] + options[:offset]} * FROM @row_number ORDER BY row
1024
- ) AS tmp1 ORDER BY row DESC
1025
- ) AS tmp2 ORDER BY row
1026
- |.gsub(/[ \t\r\n]+/,' ')
1027
- end
1028
-
1029
- # SCHEMA STATEMENTS ========================================#
1030
-
1031
- def remove_check_constraints(table_name, column_name)
1032
- constraints = info_schema_query { select_values("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'") }
1033
- constraints.each do |constraint|
1034
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
1035
- end
1036
- end
1037
-
1038
- def remove_default_constraint(table_name, column_name)
1039
- select_all("EXEC sp_helpconstraint '#{quote_string(table_name)}','nomsg'").select do |row|
1040
- row['constraint_type'] == "DEFAULT on column #{column_name}"
1041
- end.each do |row|
1042
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
1043
- end
1044
- end
1045
-
1046
- def remove_indexes(table_name, column_name)
1047
- indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
1048
- remove_index(table_name, {:name => index.name})
1049
- end
1050
- end
1051
-
1052
- def default_name(table_name, column_name)
1053
- "DF_#{table_name}_#{column_name}"
1054
- end
1055
-
1056
- # IDENTITY INSERTS =========================================#
1057
-
1058
- def with_identity_insert_enabled(table_name)
1059
- table_name = quote_table_name(table_name_or_views_table_name(table_name))
1060
- set_identity_insert(table_name, true)
1061
- yield
1062
- ensure
1063
- set_identity_insert(table_name, false)
1064
- end
1065
-
1066
- def set_identity_insert(table_name, enable = true)
1067
- sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
1068
- do_execute(sql,'IDENTITY_INSERT')
1069
- rescue Exception => e
1070
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
1071
- end
1072
-
1073
- def query_requires_identity_insert?(sql)
1074
- if insert_sql?(sql)
1075
- table_name = get_table_name(sql)
1076
- id_column = identity_column(table_name)
1077
- id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
1078
- else
1079
- false
1080
- end
1081
- end
1082
-
1083
- def identity_column(table_name)
1084
- columns(table_name).detect(&:is_identity?)
1085
- end
1086
-
1087
- def table_name_or_views_table_name(table_name)
1088
- unquoted_table_name = unqualify_table_name(table_name)
1089
- views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
1090
- end
1091
-
1092
- # HELPER METHODS ===========================================#
1093
-
1094
- def insert_sql?(sql)
1095
- !(sql =~ /^\s*INSERT/i).nil?
1096
- end
1097
-
1098
- def unqualify_table_name(table_name)
1099
- table_name.to_s.split('.').last.gsub(/[\[\]]/,'')
1100
- end
1101
-
1102
- def unqualify_db_name(table_name)
1103
- table_names = table_name.to_s.split('.')
1104
- table_names.length == 3 ? table_names.first.tr('[]','') : nil
1105
- end
1106
-
1107
- def get_table_name(sql)
1108
- if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
1109
- $1 || $2
1110
- elsif sql =~ /from\s+([^\(\s]+)\s*/i
1111
- $1
1112
- else
1113
- nil
1114
- end
1115
- end
1116
-
1117
- def orders_and_dirs_set(order)
1118
- orders = order.sub('ORDER BY','').split(',').map(&:strip).reject(&:blank?)
1119
- orders_dirs = orders.map do |ord|
1120
- dir = nil
1121
- ord.sub!(/\b(asc|desc)$/i) do |match|
1122
- if match
1123
- dir = match.upcase.strip
1124
- ''
1125
- end
1126
- end
1127
- [ord.strip, dir]
1128
- end
1129
- end
1130
-
1131
- def views_real_column_name(table_name,column_name)
1132
- view_definition = view_information(table_name)['VIEW_DEFINITION']
1133
- match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
1134
- match_data ? match_data[1] : column_name
1135
- end
1136
-
1137
- def order_to_min_set(order)
1138
- orders_dirs = orders_and_dirs_set(order)
1139
- orders_dirs.map do |o,d|
1140
- "MIN(#{o}) #{d}".strip
1141
- end.join(', ')
1142
- end
1143
-
1144
- def sql_for_association_limiting?(sql)
1145
- if md = sql.match(/^\s*SELECT(.*)FROM.*GROUP BY.*ORDER BY.*/im)
1146
- select_froms = md[1].split(',')
1147
- select_froms.size == 1 && !select_froms.first.include?('*')
1148
- end
1149
- end
1150
-
1151
- def remove_sqlserver_columns_cache_for(table_name)
1152
- cache_key = unqualify_table_name(table_name)
1153
- @sqlserver_columns_cache[cache_key] = nil
1154
- initialize_sqlserver_caches(false)
1155
- end
1156
-
1157
- def initialize_sqlserver_caches(reset_columns=true)
1158
- @sqlserver_columns_cache = {} if reset_columns
1159
- @sqlserver_views_cache = nil
1160
- @sqlserver_view_information_cache = {}
1161
- end
1162
-
1163
- def column_definitions(table_name)
1164
- db_name = unqualify_db_name(table_name)
1165
- db_name_with_period = "#{db_name}." if db_name
1166
- table_name = unqualify_table_name(table_name)
1167
- sql = %{
1168
- SELECT
1169
- columns.TABLE_NAME as table_name,
1170
- columns.COLUMN_NAME as name,
1171
- columns.DATA_TYPE as type,
1172
- columns.COLUMN_DEFAULT as default_value,
1173
- columns.NUMERIC_SCALE as numeric_scale,
1174
- columns.NUMERIC_PRECISION as numeric_precision,
1175
- CASE
1176
- WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
1177
- ELSE COL_LENGTH(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
1178
- END as length,
1179
- CASE
1180
- WHEN columns.IS_NULLABLE = 'YES' THEN 1
1181
- ELSE NULL
1182
- end as is_nullable,
1183
- CASE
1184
- WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
1185
- ELSE 1
1186
- END as is_identity
1187
- FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
1188
- WHERE columns.TABLE_NAME = '#{table_name}'
1189
- ORDER BY columns.ordinal_position
1190
- }.gsub(/[ \t\r\n]+/,' ')
1191
- results = info_schema_query { select(sql,nil,true) }
1192
- results.collect do |ci|
1193
- ci.symbolize_keys!
1194
- ci[:type] = case ci[:type]
1195
- when /^bit|image|text|ntext|datetime$/
1196
- ci[:type]
1197
- when /^numeric|decimal$/i
1198
- "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
1199
- when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
1200
- ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
1201
- else
1202
- ci[:type]
1203
- end
1204
- if ci[:default_value].nil? && views.include?(table_name)
1205
- real_table_name = table_name_or_views_table_name(table_name)
1206
- real_column_name = views_real_column_name(table_name,ci[:name])
1207
- col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
1208
- ci[:default_value] = info_schema_query { select_value(col_default_sql) }
1209
- end
1210
- ci[:default_value] = case ci[:default_value]
1211
- when nil, '(null)', '(NULL)'
1212
- nil
1213
- else
1214
- match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
1215
- match_data ? match_data[1] : nil
1216
- end
1217
- ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
1218
- ci
1219
- end
1220
- end
1221
-
1222
- def column_for(table_name, column_name)
1223
- unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
1224
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
1225
- end
1226
- column
1227
- end
1228
-
1229
- def change_order_direction(order)
1230
- order.split(",").collect {|fragment|
1231
- case fragment
1232
- when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
1233
- when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
1234
- else String.new(fragment).split(',').join(' DESC,') + ' DESC'
1235
- end
1236
- }.join(",")
1237
- end
1238
-
1239
- def special_columns(table_name)
1240
- columns(table_name).select(&:is_special?).map(&:name)
1241
- end
1242
-
1243
- def repair_special_columns(sql)
1244
- special_cols = special_columns(get_table_name(sql))
1245
- for col in special_cols.to_a
1246
- sql.gsub!(/((\.|\s|\()\[?#{col.to_s}\]?)\s?=\s?/, '\1 LIKE ')
1247
- sql.gsub!(/ORDER BY #{col.to_s}/i, '')
1248
- end
1249
- sql
1250
- end
1251
-
540
+
1252
541
  end #class SQLServerAdapter < AbstractAdapter
1253
-
542
+
1254
543
  end #module ConnectionAdapters
1255
-
544
+
1256
545
  end #module ActiveRecord
1257
546