activerecord-sqlserver-adapter-2000 3.0.15

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.
@@ -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
+