activerecord-sqlserver-adapter 2.3.7 → 3.2.18
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.
- checksums.yaml +7 -0
- data/CHANGELOG +385 -61
- data/MIT-LICENSE +1 -1
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +42 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +97 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +113 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +376 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +344 -1055
- data/lib/arel/visitors/sqlserver.rb +389 -0
- metadata +60 -83
- data/README.rdoc +0 -190
- data/RUNNING_UNIT_TESTS +0 -65
- data/Rakefile +0 -41
- data/autotest/discover.rb +0 -4
- data/autotest/railssqlserver.rb +0 -16
- data/autotest/sqlserver.rb +0 -54
- data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
- data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
- data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
- data/test/cases/adapter_test_sqlserver.rb +0 -756
- data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
- data/test/cases/basics_test_sqlserver.rb +0 -21
- data/test/cases/calculations_test_sqlserver.rb +0 -20
- data/test/cases/column_test_sqlserver.rb +0 -285
- data/test/cases/connection_test_sqlserver.rb +0 -146
- data/test/cases/eager_association_test_sqlserver.rb +0 -42
- data/test/cases/execute_procedure_test_sqlserver.rb +0 -44
- data/test/cases/inheritance_test_sqlserver.rb +0 -28
- data/test/cases/method_scoping_test_sqlserver.rb +0 -28
- data/test/cases/migration_test_sqlserver.rb +0 -123
- data/test/cases/named_scope_test_sqlserver.rb +0 -21
- data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
- data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
- data/test/cases/query_cache_test_sqlserver.rb +0 -24
- data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
- data/test/cases/specific_schema_test_sqlserver.rb +0 -97
- data/test/cases/sqlserver_helper.rb +0 -127
- data/test/cases/table_name_test_sqlserver.rb +0 -38
- data/test/cases/transaction_test_sqlserver.rb +0 -93
- data/test/cases/unicode_test_sqlserver.rb +0 -50
- data/test/cases/validations_test_sqlserver.rb +0 -35
- data/test/connections/native_sqlserver/connection.rb +0 -25
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -27
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
- 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/
|
4
|
-
require '
|
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.
|
12
|
-
config.reverse_merge! :mode => :
|
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
|
-
|
20
|
-
require '
|
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
|
91
|
-
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
:
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
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
|
-
|
201
|
-
|
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
|
-
#
|
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
|
239
|
+
|
240
|
+
def supports_primary_key?
|
219
241
|
true
|
220
242
|
end
|
221
|
-
|
222
|
-
def
|
243
|
+
|
244
|
+
def supports_count_distinct?
|
223
245
|
true
|
224
246
|
end
|
225
|
-
|
226
|
-
def
|
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
|
239
|
-
|
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
|
323
|
-
|
256
|
+
def supports_savepoints?
|
257
|
+
true
|
324
258
|
end
|
325
|
-
|
326
|
-
def
|
327
|
-
|
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
|
335
|
-
|
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
|
-
#
|
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
|
-
|
295
|
+
@spid = nil
|
296
|
+
case @connection_options[:mode]
|
297
|
+
when :dblib
|
298
|
+
@connection.close rescue nil
|
364
299
|
when :odbc
|
365
|
-
|
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
|
435
|
-
|
304
|
+
def reset!
|
305
|
+
remove_database_connections_and_rollback { }
|
436
306
|
end
|
437
307
|
|
438
|
-
|
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
|
447
|
-
|
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
|
-
|
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
|
-
|
567
|
-
|
318
|
+
|
319
|
+
# === SQLServer Specific (DB Reflection) ======================== #
|
320
|
+
|
321
|
+
def sqlserver?
|
322
|
+
true
|
568
323
|
end
|
569
|
-
|
570
|
-
def
|
571
|
-
|
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
|
577
|
-
@
|
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
|
582
|
-
|
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
|
595
|
-
|
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
|
600
|
-
|
340
|
+
|
341
|
+
def sqlserver_azure?
|
342
|
+
@sqlserver_azure
|
601
343
|
end
|
602
|
-
|
603
|
-
def
|
604
|
-
|
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
|
622
|
-
|
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
|
631
|
-
|
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
|
636
|
-
|
356
|
+
|
357
|
+
def auto_connect_duration
|
358
|
+
@@auto_connect_duration ||= 10
|
637
359
|
end
|
638
|
-
|
639
|
-
def
|
640
|
-
|
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
|
-
|
645
|
-
|
646
|
-
|
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
|
650
|
-
|
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
|
660
|
-
|
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
|
677
|
-
|
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
|
683
|
-
|
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
|
689
|
-
|
385
|
+
|
386
|
+
def cs_equality_operator
|
387
|
+
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
|
690
388
|
end
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
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
|
-
|
770
|
-
|
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
|
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
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
end
|
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
|
-
|
826
|
-
|
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
|
830
|
-
|
831
|
-
|
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
|
835
|
-
|
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
|
504
|
+
|
505
|
+
def with_sqlserver_error_handling
|
839
506
|
begin
|
840
507
|
yield
|
841
|
-
rescue
|
842
|
-
|
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 <=
|
854
|
-
|
528
|
+
while count <= (auto_connect_duration / 2)
|
529
|
+
result = reconnect!
|
855
530
|
ActiveRecord::Base.did_retry_sqlserver_connection(self,count)
|
856
|
-
return true if
|
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
|
|