activerecord-sqlserver-adapter 2.3.7 → 3.2.18

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