Skip to content

Commit 297e4cf

Browse files
author
Anna Carey
committed
moved SQLServerAdapter back and split up the connect method
1 parent 64efc89 commit 297e4cf

File tree

2 files changed

+396
-372
lines changed

2 files changed

+396
-372
lines changed

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,398 @@
2323

2424
require 'active_record/sqlserver_base'
2525
require 'active_record/connection_adapters/sqlserver_column'
26+
27+
module ActiveRecord
28+
module ConnectionAdapters
29+
class SQLServerAdapter < AbstractAdapter
30+
include Sqlserver::Quoting
31+
include Sqlserver::DatabaseStatements
32+
include Sqlserver::Showplan
33+
include Sqlserver::SchemaStatements
34+
include Sqlserver::DatabaseLimits
35+
include Sqlserver::Errors
36+
37+
VERSION = File.read(File.expand_path('../../../../VERSION', __FILE__)).strip
38+
ADAPTER_NAME = 'SQLServer'.freeze
39+
DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
40+
SUPPORTED_VERSIONS = [2005, 2008, 2010, 2011, 2012]
41+
42+
attr_reader :database_version, :database_year, :spid, :product_level, :product_version, :edition
43+
44+
cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
45+
:enable_default_unicode_types, :auto_connect, :cs_equality_operator,
46+
:lowercase_schema_reflection, :auto_connect_duration, :showplan_option
47+
48+
self.enable_default_unicode_types = true
49+
50+
class BindSubstitution < Arel::Visitors::SQLServer # :nodoc:
51+
include Arel::Visitors::BindVisitor
52+
end
53+
54+
def initialize(connection, logger, pool, config)
55+
super(connection, logger, pool)
56+
# AbstractAdapter Responsibility
57+
@schema_cache = Sqlserver::SchemaCache.new self
58+
@visitor = Arel::Visitors::SQLServer.new self
59+
# Our Responsibility
60+
@config = config
61+
@connection_options = config
62+
connect
63+
@database_version = select_value 'SELECT @@version', 'SCHEMA'
64+
@database_year = begin
65+
if @database_version =~ /Azure/i
66+
@sqlserver_azure = true
67+
@database_version.match(/\s-\s([0-9.]+)/)[1]
68+
year = 2012
69+
else
70+
year = DATABASE_VERSION_REGEXP.match(@database_version)[1]
71+
year == 'Denali' ? 2011 : year.to_i
72+
end
73+
rescue
74+
0
75+
end
76+
@product_level = select_value "SELECT CAST(SERVERPROPERTY('productlevel') AS VARCHAR(128))", 'SCHEMA'
77+
@product_version = select_value "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(128))", 'SCHEMA'
78+
@edition = select_value "SELECT CAST(SERVERPROPERTY('edition') AS VARCHAR(128))", 'SCHEMA'
79+
initialize_dateformatter
80+
use_database
81+
unless @sqlserver_azure == true || SUPPORTED_VERSIONS.include?(@database_year)
82+
raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported. We got back #{@database_version}."
83+
end
84+
end
85+
86+
# === Abstract Adapter ========================================== #
87+
88+
def adapter_name
89+
ADAPTER_NAME
90+
end
91+
92+
def supports_migrations?
93+
true
94+
end
95+
96+
def supports_primary_key?
97+
true
98+
end
99+
100+
def supports_count_distinct?
101+
true
102+
end
103+
104+
def supports_ddl_transactions?
105+
true
106+
end
107+
108+
def supports_bulk_alter?
109+
false
110+
end
111+
112+
def supports_savepoints?
113+
true
114+
end
115+
116+
def supports_index_sort_order?
117+
true
118+
end
119+
120+
def supports_partial_index?
121+
@database_year >= 2008
122+
end
123+
124+
def supports_explain?
125+
true
126+
end
127+
128+
def disable_referential_integrity
129+
do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
130+
yield
131+
ensure
132+
do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
133+
end
134+
135+
# === Abstract Adapter (Connection Management) ================== #
136+
137+
def active?
138+
case @connection_options[:mode]
139+
when :dblib
140+
return @connection.active?
141+
end
142+
raw_connection_do('SELECT 1')
143+
true
144+
rescue *lost_connection_exceptions
145+
false
146+
end
147+
148+
def reconnect!
149+
reset_transaction
150+
disconnect!
151+
connect
152+
active?
153+
end
154+
155+
def disconnect!
156+
reset_transaction
157+
@spid = nil
158+
case @connection_options[:mode]
159+
when :dblib
160+
@connection.close rescue nil
161+
when :odbc
162+
@connection.disconnect rescue nil
163+
end
164+
end
165+
166+
def reset!
167+
remove_database_connections_and_rollback {}
168+
end
169+
170+
# === Abstract Adapter (Misc Support) =========================== #
171+
172+
def pk_and_sequence_for(table_name)
173+
pk = primary_key(table_name)
174+
pk ? [pk, nil] : nil
175+
end
176+
177+
def primary_key(table_name)
178+
identity_column(table_name).try(:name) || schema_cache.columns(table_name).find(&:is_primary?).try(:name)
179+
end
180+
181+
def schema_creation
182+
Sqlserver::SchemaCreation.new self
183+
end
184+
185+
# === SQLServer Specific (DB Reflection) ======================== #
186+
187+
def sqlserver?
188+
true
189+
end
190+
191+
def sqlserver_2005?
192+
@database_year == 2005
193+
end
194+
195+
def sqlserver_2008?
196+
@database_year == 2008
197+
end
198+
199+
def sqlserver_2011?
200+
@database_year == 2011
201+
end
202+
203+
def sqlserver_2012?
204+
@database_year == 2012
205+
end
206+
207+
def sqlserver_azure?
208+
@sqlserver_azure
209+
end
210+
211+
def version
212+
self.class::VERSION
213+
end
214+
215+
def inspect
216+
"#<#{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}>"
217+
end
218+
219+
def auto_connect
220+
@@auto_connect.is_a?(FalseClass) ? false : true
221+
end
222+
223+
def auto_connect_duration
224+
@@auto_connect_duration ||= 10
225+
end
226+
227+
def native_string_database_type
228+
@@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
229+
end
230+
231+
def native_text_database_type
232+
@@native_text_database_type || enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
233+
end
234+
235+
def native_time_database_type
236+
sqlserver_2005? ? 'datetime' : 'time'
237+
end
238+
239+
def native_date_database_type
240+
sqlserver_2005? ? 'datetime' : 'date'
241+
end
242+
243+
def native_binary_database_type
244+
@@native_binary_database_type || 'varbinary(max)'
245+
end
246+
247+
def cs_equality_operator
248+
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
249+
end
250+
251+
protected
252+
253+
# === Abstract Adapter (Misc Support) =========================== #
254+
255+
def translate_exception(e, message)
256+
case message
257+
when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
258+
RecordNotUnique.new(message, e)
259+
when /conflicted with the foreign key constraint/i
260+
InvalidForeignKey.new(message, e)
261+
when /has been chosen as the deadlock victim/i
262+
DeadlockVictim.new(message, e)
263+
when *lost_connection_messages
264+
LostConnection.new(message, e)
265+
else
266+
super
267+
end
268+
end
269+
270+
# === SQLServer Specific (Connection Management) ================ #
271+
272+
def connect
273+
config = @connection_options
274+
@connection = case config[:mode]
275+
when :dblib
276+
dblib_connect(config)
277+
when :odbc
278+
odbc_connect(config)
279+
end
280+
@spid = _raw_select('SELECT @@SPID', fetch: :rows).first.first
281+
configure_connection
282+
rescue
283+
raise unless @auto_connecting
284+
end
285+
286+
def dblib_connect(config)
287+
TinyTds::Client.new(
288+
dataserver: config[:dataserver],
289+
host: config[:host],
290+
port: config[:port],
291+
username: config[:username],
292+
password: config[:password],
293+
database: config[:database],
294+
tds_version: config[:tds_version],
295+
appname: appname(config),
296+
login_timeout: login_timeout(config),
297+
timeout: timeout(config),
298+
encoding: encoding(config),
299+
azure: config[:azure]
300+
).tap do |client|
301+
if config[:azure]
302+
client.execute('SET ANSI_NULLS ON').do
303+
client.execute('SET CURSOR_CLOSE_ON_COMMIT OFF').do
304+
client.execute('SET ANSI_NULL_DFLT_ON ON').do
305+
client.execute('SET IMPLICIT_TRANSACTIONS OFF').do
306+
client.execute('SET ANSI_PADDING ON').do
307+
client.execute('SET QUOTED_IDENTIFIER ON')
308+
client.execute('SET ANSI_WARNINGS ON').do
309+
else
310+
client.execute('SET ANSI_DEFAULTS ON').do
311+
client.execute('SET CURSOR_CLOSE_ON_COMMIT OFF').do
312+
client.execute('SET IMPLICIT_TRANSACTIONS OFF').do
313+
end
314+
client.execute('SET TEXTSIZE 2147483647').do
315+
client.execute('SET CONCAT_NULL_YIELDS_NULL ON').do
316+
end
317+
end
318+
319+
def appname(config)
320+
config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
321+
end
322+
323+
def login_timeout(config)
324+
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
325+
end
326+
327+
def timeout(config)
328+
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
329+
end
330+
331+
def encoding(config)
332+
config[:encoding].present? ? config[:encoding] : nil
333+
end
334+
335+
def odbc_connect(config)
336+
if config[:dsn].include?(';')
337+
driver = ODBC::Driver.new.tap do |d|
338+
d.name = config[:dsn_name] || 'Driver1'
339+
d.attrs = config[:dsn].split(';').map { |atr| atr.split('=') }.reject { |kv| kv.size != 2 }.reduce({}) { |a, e| k, v = e ; a[k] = v ; a }
340+
end
341+
ODBC::Database.new.drvconnect(driver)
342+
else
343+
ODBC.connect config[:dsn], config[:username], config[:password]
344+
end.tap do |c|
345+
begin
346+
c.use_time = true
347+
c.use_utc = ActiveRecord::Base.default_timezone == :utc
348+
rescue Exception
349+
warn 'Ruby ODBC v0.99992 or higher is required.'
350+
end
351+
end
352+
end
353+
354+
# Override this method so every connection can be configured to your needs.
355+
# For example:
356+
# raw_connection_do "SET TEXTSIZE #{64.megabytes}"
357+
# raw_connection_do "SET CONCAT_NULL_YIELDS_NULL ON"
358+
def configure_connection
359+
end
360+
361+
# Override this method so every connection can have a unique name. Max 30 characters. Used by TinyTDS only.
362+
# For example:
363+
# "myapp_#{$$}_#{Thread.current.object_id}".to(29)
364+
def configure_application_name
365+
end
366+
367+
def initialize_dateformatter
368+
@database_dateformat = user_options_dateformat
369+
a, b, c = @database_dateformat.each_char.to_a
370+
[a, b, c].each { |f| f.upcase! if f == 'y' }
371+
dateformat = "%#{a}-%#{b}-%#{c}"
372+
::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
373+
::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
374+
end
375+
376+
def remove_database_connections_and_rollback(database = nil)
377+
database ||= current_database
378+
do_execute "ALTER DATABASE #{quote_database_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
379+
begin
380+
yield
381+
ensure
382+
do_execute "ALTER DATABASE #{quote_database_name(database)} SET MULTI_USER"
383+
end if block_given?
384+
end
385+
386+
def with_sqlserver_error_handling
387+
yield
388+
rescue Exception => e
389+
case translate_exception(e, e.message)
390+
when LostConnection then retry if auto_reconnected?
391+
end
392+
raise
393+
end
394+
395+
def disable_auto_reconnect
396+
old_auto_connect, self.class.auto_connect = self.class.auto_connect, false
397+
yield
398+
ensure
399+
self.class.auto_connect = old_auto_connect
400+
end
401+
402+
def auto_reconnected?
403+
return false unless auto_connect
404+
@auto_connecting = true
405+
count = 0
406+
while count <= (auto_connect_duration / 2)
407+
result = reconnect!
408+
ActiveRecord::Base.did_retry_sqlserver_connection(self, count)
409+
return true if result
410+
sleep 2**count
411+
count += 1
412+
end
413+
ActiveRecord::Base.did_lose_sqlserver_connection(self)
414+
false
415+
ensure
416+
@auto_connecting = false
417+
end
418+
end # class SQLServerAdapter < AbstractAdapter
419+
end # module ConnectionAdapters
420+
end # module ActiveRecord

0 commit comments

Comments
 (0)