activerecord-sqlserver-adapter-2000 3.0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,470 @@
1
+ require 'arel/visitors/sqlserver'
2
+ require 'active_record'
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+ require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
5
+ require 'active_record/connection_adapters/sqlserver/database_limits'
6
+ require 'active_record/connection_adapters/sqlserver/database_statements'
7
+ require 'active_record/connection_adapters/sqlserver/errors'
8
+ require 'active_record/connection_adapters/sqlserver/query_cache'
9
+ require 'active_record/connection_adapters/sqlserver/schema_statements'
10
+ require 'active_record/connection_adapters/sqlserver/quoting'
11
+ require 'active_support/core_ext/kernel/requires'
12
+ require 'active_support/core_ext/string'
13
+ require 'base64'
14
+
15
+ module ActiveRecord
16
+
17
+ class Base
18
+
19
+ def self.sqlserver_connection(config) #:nodoc:
20
+ config = config.symbolize_keys
21
+ config.reverse_merge! :mode => :dblib, :host => 'localhost', :username => 'sa', :password => ''
22
+ mode = config[:mode].to_s.downcase.underscore.to_sym
23
+ case mode
24
+ when :dblib
25
+ require_library_or_gem 'tiny_tds'
26
+ warn("TinyTds v0.4.3 or higher required. Using #{TinyTds::VERSION}") unless TinyTds::Client.instance_methods.map(&:to_s).include?("active?")
27
+ when :odbc
28
+ raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
29
+ if RUBY_VERSION < '1.9'
30
+ require_library_or_gem 'odbc'
31
+ else
32
+ begin
33
+ # TODO: [ODBC] Change this to 'odbc_utf8'
34
+ require_library_or_gem 'odbc'
35
+ rescue LoadError
36
+ require_library_or_gem 'odbc'
37
+ end
38
+ end unless ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].any? { |odbc_ns| odbc_ns.constantize rescue nil }
39
+ require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
40
+ when :adonet
41
+ require 'System.Data'
42
+ raise ArgumentError, 'Missing :database configuration.' unless config.has_key?(:database)
43
+ else
44
+ raise ArgumentError, "Unknown connection mode in #{config.inspect}."
45
+ end
46
+ ConnectionAdapters::SQLServerAdapter.new(logger,config.merge(:mode=>mode))
47
+ end
48
+
49
+ protected
50
+
51
+ def self.did_retry_sqlserver_connection(connection,count)
52
+ logger.info "CONNECTION RETRY: #{connection.class.name} retry ##{count}."
53
+ end
54
+
55
+ def self.did_lose_sqlserver_connection(connection)
56
+ logger.info "CONNECTION LOST: #{connection.class.name}"
57
+ end
58
+
59
+ end
60
+
61
+ module ConnectionAdapters
62
+
63
+ class SQLServerColumn < Column
64
+
65
+ def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
66
+ @sqlserver_options = sqlserver_options.symbolize_keys
67
+ super(name, default, sql_type, null)
68
+ end
69
+
70
+ class << self
71
+
72
+ def string_to_binary(value)
73
+ "0x#{value.unpack("H*")[0]}"
74
+ end
75
+
76
+ def binary_to_string(value)
77
+ value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
78
+ end
79
+
80
+ end
81
+
82
+ def is_identity?
83
+ @sqlserver_options[:is_identity]
84
+ end
85
+
86
+ def is_utf8?
87
+ @sql_type =~ /nvarchar|ntext|nchar/i
88
+ end
89
+
90
+ def default_function
91
+ @sqlserver_options[:default_function]
92
+ end
93
+
94
+ def table_name
95
+ @sqlserver_options[:table_name]
96
+ end
97
+
98
+ def table_klass
99
+ @table_klass ||= begin
100
+ table_name.classify.constantize
101
+ rescue StandardError, NameError, LoadError
102
+ nil
103
+ end
104
+ (@table_klass && @table_klass < ActiveRecord::Base) ? @table_klass : nil
105
+ end
106
+
107
+ def database_year
108
+ @sqlserver_options[:database_year]
109
+ end
110
+
111
+
112
+ private
113
+
114
+ def extract_limit(sql_type)
115
+ case sql_type
116
+ when /^smallint/i
117
+ 2
118
+ when /^int/i
119
+ 4
120
+ when /^bigint/i
121
+ 8
122
+ when /\(max\)/, /decimal/, /numeric/
123
+ nil
124
+ else
125
+ super
126
+ end
127
+ end
128
+
129
+ def simplified_type(field_type)
130
+ case field_type
131
+ when /real/i then :float
132
+ when /money/i then :decimal
133
+ when /image/i then :binary
134
+ when /bit/i then :boolean
135
+ when /uniqueidentifier/i then :string
136
+ when /datetime/i then simplified_datetime
137
+ when /varchar\(max\)/ then :text
138
+ when /timestamp/ then :binary
139
+ else super
140
+ end
141
+ end
142
+
143
+ def simplified_datetime
144
+ if database_year >= 2008
145
+ :datetime
146
+ elsif table_klass && table_klass.coerced_sqlserver_date_columns.include?(name)
147
+ :date
148
+ elsif table_klass && table_klass.coerced_sqlserver_time_columns.include?(name)
149
+ :time
150
+ else
151
+ :datetime
152
+ end
153
+ end
154
+
155
+ end #class SQLServerColumn
156
+
157
+ class SQLServerAdapter < AbstractAdapter
158
+
159
+ include Sqlserver::Quoting
160
+ include Sqlserver::DatabaseStatements
161
+ include Sqlserver::SchemaStatements
162
+ include Sqlserver::DatabaseLimits
163
+ include Sqlserver::QueryCache
164
+ include Sqlserver::Errors
165
+
166
+ ADAPTER_NAME = 'SQLServer'.freeze
167
+ VERSION = '3.0.15'.freeze
168
+ DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
169
+ SUPPORTED_VERSIONS = [2000,2005,2008,2010,2011].freeze
170
+
171
+ attr_reader :database_version, :database_year,
172
+ :connection_supports_native_types
173
+
174
+ cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
175
+ :log_info_schema_queries, :enable_default_unicode_types, :auto_connect,
176
+ :cs_equality_operator
177
+
178
+ def initialize(logger,config)
179
+ @connection_options = config
180
+ connect
181
+ super(@connection, logger)
182
+ @database_version = info_schema_query { select_value('SELECT @@version') }
183
+ @database_year = begin
184
+ if @database_version =~ /Microsoft SQL Azure/i
185
+ @sqlserver_azure = true
186
+ @database_version.match(/\s(\d{4})\s/)[1].to_i
187
+ else
188
+ year = DATABASE_VERSION_REGEXP.match(@database_version)[1]
189
+ year == "Denali" ? 2011 : year.to_i
190
+ end
191
+ rescue
192
+ 0
193
+ end
194
+ initialize_sqlserver_caches
195
+ use_database
196
+ unless SUPPORTED_VERSIONS.include?(@database_year)
197
+ raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported. We got back #{@database_version}."
198
+ end
199
+ end
200
+
201
+ # === Abstract Adapter ========================================== #
202
+
203
+ def adapter_name
204
+ ADAPTER_NAME
205
+ end
206
+
207
+ def supports_migrations?
208
+ true
209
+ end
210
+
211
+ def supports_primary_key?
212
+ true
213
+ end
214
+
215
+ def supports_count_distinct?
216
+ true
217
+ end
218
+
219
+ def supports_ddl_transactions?
220
+ true
221
+ end
222
+
223
+ def supports_savepoints?
224
+ true
225
+ end
226
+
227
+ def disable_referential_integrity
228
+ do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
229
+ yield
230
+ ensure
231
+ do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
232
+ end
233
+
234
+ # === Abstract Adapter (Connection Management) ================== #
235
+
236
+ def active?
237
+ case @connection_options[:mode]
238
+ when :dblib
239
+ return @connection.active?
240
+ end
241
+ raw_connection_do("SELECT 1")
242
+ true
243
+ rescue *lost_connection_exceptions
244
+ false
245
+ end
246
+
247
+ def reconnect!
248
+ disconnect!
249
+ connect
250
+ active?
251
+ end
252
+
253
+ def disconnect!
254
+ case @connection_options[:mode]
255
+ when :dblib
256
+ @connection.close rescue nil
257
+ when :odbc
258
+ @connection.disconnect rescue nil
259
+ else :adonet
260
+ @connection.close rescue nil
261
+ end
262
+ end
263
+
264
+ def reset!
265
+ remove_database_connections_and_rollback { }
266
+ end
267
+
268
+ # === Abstract Adapter (Misc Support) =========================== #
269
+
270
+ def pk_and_sequence_for(table_name)
271
+ idcol = identity_column(table_name)
272
+ idcol ? [idcol.name,nil] : nil
273
+ end
274
+
275
+ def primary_key(table_name)
276
+ identity_column(table_name).try(:name)
277
+ end
278
+
279
+ # === SQLServer Specific (DB Reflection) ======================== #
280
+
281
+ def sqlserver?
282
+ true
283
+ end
284
+
285
+ def sqlserver_2005?
286
+ @database_year == 2005
287
+ end
288
+
289
+ def sqlserver_2008?
290
+ @database_year == 2008
291
+ end
292
+
293
+ def sqlserver_2011?
294
+ @database_year == 2011
295
+ end
296
+
297
+ def sqlserver_azure?
298
+ @sqlserver_azure
299
+ end
300
+
301
+ def version
302
+ self.class::VERSION
303
+ end
304
+
305
+ def inspect
306
+ "#<#{self.class} version: #{version}, year: #{@database_year}, connection_options: #{@connection_options.inspect}>"
307
+ end
308
+
309
+ def auto_connect
310
+ @@auto_connect.is_a?(FalseClass) ? false : true
311
+ end
312
+
313
+ def native_string_database_type
314
+ @@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
315
+ end
316
+
317
+ def native_text_database_type
318
+ @@native_text_database_type || enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
319
+ end
320
+
321
+ def native_time_database_type
322
+ sqlserver_2005? ? 'datetime' : 'time'
323
+ end
324
+
325
+ def native_date_database_type
326
+ sqlserver_2005? ? 'datetime' : 'date'
327
+ end
328
+
329
+ def native_binary_database_type
330
+ @@native_binary_database_type || 'varbinary(max)'
331
+ end
332
+
333
+ def cs_equality_operator
334
+ @@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS ='
335
+ end
336
+
337
+
338
+ protected
339
+
340
+ # === Abstract Adapter (Misc Support) =========================== #
341
+
342
+ def translate_exception(e, message)
343
+ case message
344
+ when /cannot insert duplicate key .* with unique index/i
345
+ RecordNotUnique.new(message,e)
346
+ when /conflicted with the foreign key constraint/i
347
+ InvalidForeignKey.new(message,e)
348
+ when *lost_connection_messages
349
+ LostConnection.new(message,e)
350
+ else
351
+ super
352
+ end
353
+ end
354
+
355
+ # === SQLServer Specific (Connection Management) ================ #
356
+
357
+ def connect
358
+ config = @connection_options
359
+ @connection = case @connection_options[:mode]
360
+ when :dblib
361
+ appname = config[:appname] || Rails.application.class.name.split('::').first rescue nil
362
+ login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
363
+ timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
364
+ encoding = config[:encoding].present? ? config[:encoding] : nil
365
+ TinyTds::Client.new({
366
+ :dataserver => config[:dataserver],
367
+ :host => config[:host],
368
+ :port => config[:port],
369
+ :username => config[:username],
370
+ :password => config[:password],
371
+ :database => config[:database],
372
+ :appname => appname,
373
+ :login_timeout => login_timeout,
374
+ :timeout => timeout,
375
+ :encoding => encoding,
376
+ :azure => config[:azure]
377
+ }).tap do |client|
378
+ if config[:azure]
379
+ client.execute("SET ANSI_NULLS ON").do
380
+ client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
381
+ client.execute("SET ANSI_NULL_DFLT_ON ON").do
382
+ client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
383
+ client.execute("SET ANSI_PADDING ON").do
384
+ client.execute("SET QUOTED_IDENTIFIER ON")
385
+ client.execute("SET ANSI_WARNINGS ON").do
386
+ else
387
+ client.execute("SET ANSI_DEFAULTS ON").do
388
+ client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
389
+ client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
390
+ end
391
+ end
392
+ when :odbc
393
+ odbc = ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].detect{ |odbc_ns| odbc_ns.constantize rescue nil }.constantize
394
+ if config[:dsn].include?(';')
395
+ driver = odbc::Driver.new.tap do |d|
396
+ d.name = config[:dsn_name] || 'Driver1'
397
+ d.attrs = config[:dsn].split(';').map{ |atr| atr.split('=') }.reject{ |kv| kv.size != 2 }.inject({}){ |h,kv| k,v = kv ; h[k] = v ; h }
398
+ end
399
+ odbc::Database.new.drvconnect(driver)
400
+ else
401
+ odbc.connect config[:dsn], config[:username], config[:password]
402
+ end.tap do |c|
403
+ if c.respond_to?(:use_time)
404
+ c.use_time = true
405
+ c.use_utc = ActiveRecord::Base.default_timezone == :utc
406
+ @connection_supports_native_types = true
407
+ end
408
+ end
409
+ when :adonet
410
+ System::Data::SqlClient::SqlConnection.new.tap do |connection|
411
+ connection.connection_string = System::Data::SqlClient::SqlConnectionStringBuilder.new.tap do |cs|
412
+ if config[:integrated_security]
413
+ cs.integrated_security = true
414
+ else
415
+ cs.user_i_d = config[:username]
416
+ cs.password = config[:password]
417
+ end
418
+ cs.add 'Server', config[:host].to_clr_string
419
+ cs.initial_catalog = config[:database]
420
+ cs.multiple_active_result_sets = false
421
+ cs.pooling = false
422
+ end.to_s
423
+ connection.open
424
+ end
425
+ end
426
+ rescue
427
+ raise unless @auto_connecting
428
+ end
429
+
430
+ def remove_database_connections_and_rollback(database=nil)
431
+ database ||= current_database
432
+ do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
433
+ begin
434
+ yield
435
+ ensure
436
+ do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
437
+ end if block_given?
438
+ end
439
+
440
+ def with_auto_reconnect
441
+ begin
442
+ yield
443
+ rescue Exception => e
444
+ retry if translate_exception(e,e.message).is_a?(LostConnection) && auto_reconnected?
445
+ raise
446
+ end
447
+ end
448
+
449
+ def auto_reconnected?
450
+ return false unless auto_connect
451
+ @auto_connecting = true
452
+ count = 0
453
+ while count <= 5
454
+ sleep 2** count
455
+ ActiveRecord::Base.did_retry_sqlserver_connection(self,count)
456
+ return true if reconnect!
457
+ count += 1
458
+ end
459
+ ActiveRecord::Base.did_lose_sqlserver_connection(self)
460
+ false
461
+ ensure
462
+ @auto_connecting = false
463
+ end
464
+
465
+ end #class SQLServerAdapter < AbstractAdapter
466
+
467
+ end #module ConnectionAdapters
468
+
469
+ end #module ActiveRecord
470
+