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.
- data/CHANGELOG +400 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +176 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +58 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +414 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +35 -0
- data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +55 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +403 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +470 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/visitors/sqlserver.rb +330 -0
- metadata +103 -0
@@ -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
|
+
|