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
@@ -0,0 +1,458 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module DatabaseStatements
|
5
|
+
|
6
|
+
include CoreExt::DatabaseStatements
|
7
|
+
|
8
|
+
def select_rows(sql, name = nil)
|
9
|
+
raw_select sql, name, [], :fetch => :rows
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(sql, name = nil)
|
13
|
+
if id_insert_table_name = query_requires_identity_insert?(sql)
|
14
|
+
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
|
15
|
+
else
|
16
|
+
do_execute(sql,name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def exec_query(sql, name = 'SQL', binds = [], sqlserver_options = {})
|
21
|
+
if id_insert_table_name = sqlserver_options[:insert] ? query_requires_identity_insert?(sql) : nil
|
22
|
+
with_identity_insert_enabled(id_insert_table_name) { do_exec_query(sql, name, binds) }
|
23
|
+
else
|
24
|
+
do_exec_query(sql, name, binds)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def exec_insert(sql, name, binds)
|
29
|
+
exec_query sql, name, binds, :insert => true
|
30
|
+
end
|
31
|
+
|
32
|
+
def exec_delete(sql, name, binds)
|
33
|
+
sql << "; SELECT @@ROWCOUNT AS AffectedRows"
|
34
|
+
super.rows.first.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def exec_update(sql, name, binds)
|
38
|
+
sql << "; SELECT @@ROWCOUNT AS AffectedRows"
|
39
|
+
super.rows.first.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def outside_transaction?
|
43
|
+
uncached { select_value('SELECT @@TRANCOUNT', 'SCHEMA') == 0 }
|
44
|
+
end
|
45
|
+
|
46
|
+
def supports_statement_cache?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def transaction(options = {})
|
51
|
+
if retry_deadlock_victim?
|
52
|
+
block_given? ? transaction_with_retry_deadlock_victim(options) { yield } : transaction_with_retry_deadlock_victim(options)
|
53
|
+
else
|
54
|
+
block_given? ? super(options) { yield } : super(options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def begin_db_transaction
|
59
|
+
do_execute "BEGIN TRANSACTION"
|
60
|
+
end
|
61
|
+
|
62
|
+
def commit_db_transaction
|
63
|
+
disable_auto_reconnect { do_execute "COMMIT TRANSACTION" }
|
64
|
+
end
|
65
|
+
|
66
|
+
def rollback_db_transaction
|
67
|
+
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_savepoint
|
71
|
+
disable_auto_reconnect { do_execute "SAVE TRANSACTION #{current_savepoint_name}" }
|
72
|
+
end
|
73
|
+
|
74
|
+
def release_savepoint
|
75
|
+
end
|
76
|
+
|
77
|
+
def rollback_to_savepoint
|
78
|
+
disable_auto_reconnect { do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}" }
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_limit_offset!(sql, options)
|
82
|
+
raise NotImplementedError, 'This has been moved to the SQLServerCompiler in Arel.'
|
83
|
+
end
|
84
|
+
|
85
|
+
def empty_insert_statement_value
|
86
|
+
"DEFAULT VALUES"
|
87
|
+
end
|
88
|
+
|
89
|
+
def case_sensitive_modifier(node)
|
90
|
+
node.acts_like?(:string) ? Arel::Nodes::Bin.new(node) : node
|
91
|
+
end
|
92
|
+
|
93
|
+
# === SQLServer Specific ======================================== #
|
94
|
+
|
95
|
+
def execute_procedure(proc_name, *variables)
|
96
|
+
vars = if variables.any? && variables.first.is_a?(Hash)
|
97
|
+
variables.first.map { |k,v| "@#{k} = #{quote(v)}" }
|
98
|
+
else
|
99
|
+
variables.map { |v| quote(v) }
|
100
|
+
end.join(', ')
|
101
|
+
sql = "EXEC #{proc_name} #{vars}".strip
|
102
|
+
name = 'Execute Procedure'
|
103
|
+
log(sql, name) do
|
104
|
+
case @connection_options[:mode]
|
105
|
+
when :dblib
|
106
|
+
result = @connection.execute(sql)
|
107
|
+
result.each(:as => :hash, :cache_rows => true) do |row|
|
108
|
+
r = row.with_indifferent_access
|
109
|
+
yield(r) if block_given?
|
110
|
+
end
|
111
|
+
result.each.map{ |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
|
112
|
+
when :odbc
|
113
|
+
results = []
|
114
|
+
raw_connection_run(sql) do |handle|
|
115
|
+
get_rows = lambda {
|
116
|
+
rows = handle_to_names_and_values handle, :fetch => :all
|
117
|
+
rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
|
118
|
+
results << rows
|
119
|
+
}
|
120
|
+
get_rows.call
|
121
|
+
while handle_more_results?(handle)
|
122
|
+
get_rows.call
|
123
|
+
end
|
124
|
+
end
|
125
|
+
results.many? ? results : results.first
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def use_database(database=nil)
|
131
|
+
return if sqlserver_azure?
|
132
|
+
database ||= @connection_options[:database]
|
133
|
+
do_execute "USE #{quote_table_name(database)}" unless database.blank?
|
134
|
+
end
|
135
|
+
|
136
|
+
def user_options
|
137
|
+
return {} if sqlserver_azure?
|
138
|
+
# fixes #535
|
139
|
+
rows = select_rows('DBCC USEROPTIONS WITH NO_INFOMSGS', 'SCHEMA')
|
140
|
+
rows = rows.first if rows.size == 2 && rows.last.empty?
|
141
|
+
rows.reduce(HashWithIndifferentAccess.new) do |values, row|
|
142
|
+
if row.instance_of? Hash
|
143
|
+
set_option = row.values[0].gsub(/\s+/, '_')
|
144
|
+
user_value = row.values[1]
|
145
|
+
elsif row.instance_of? Array
|
146
|
+
set_option = row[0].gsub(/\s+/, '_')
|
147
|
+
user_value = row[1]
|
148
|
+
end
|
149
|
+
values[set_option] = user_value
|
150
|
+
values
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def user_options_dateformat
|
155
|
+
if sqlserver_azure?
|
156
|
+
select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
|
157
|
+
else
|
158
|
+
user_options['dateformat']
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def user_options_isolation_level
|
163
|
+
if sqlserver_azure?
|
164
|
+
sql = %|SELECT CASE [transaction_isolation_level]
|
165
|
+
WHEN 0 THEN NULL
|
166
|
+
WHEN 1 THEN 'READ UNCOMITTED'
|
167
|
+
WHEN 2 THEN 'READ COMITTED'
|
168
|
+
WHEN 3 THEN 'REPEATABLE READ'
|
169
|
+
WHEN 4 THEN 'SERIALIZABLE'
|
170
|
+
WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
|
171
|
+
FROM [sys].[dm_exec_sessions]
|
172
|
+
WHERE [session_id] = @@SPID|.squish
|
173
|
+
select_value sql, 'SCHEMA'
|
174
|
+
else
|
175
|
+
user_options['isolation_level']
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def user_options_language
|
180
|
+
if sqlserver_azure?
|
181
|
+
select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
|
182
|
+
else
|
183
|
+
user_options['language']
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def run_with_isolation_level(isolation_level)
|
188
|
+
raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{valid_isolation_levels.to_sentence}." if !valid_isolation_levels.include?(isolation_level.upcase)
|
189
|
+
initial_isolation_level = user_options_isolation_level || "READ COMMITTED"
|
190
|
+
do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
|
191
|
+
begin
|
192
|
+
yield
|
193
|
+
ensure
|
194
|
+
do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
|
195
|
+
end if block_given?
|
196
|
+
end
|
197
|
+
|
198
|
+
def newid_function
|
199
|
+
select_value "SELECT NEWID()"
|
200
|
+
end
|
201
|
+
|
202
|
+
def newsequentialid_function
|
203
|
+
select_value "SELECT NEWSEQUENTIALID()"
|
204
|
+
end
|
205
|
+
|
206
|
+
def activity_stats
|
207
|
+
select_all %|
|
208
|
+
SELECT
|
209
|
+
[session_id] = s.session_id,
|
210
|
+
[user_process] = CONVERT(CHAR(1), s.is_user_process),
|
211
|
+
[login] = s.login_name,
|
212
|
+
[database] = ISNULL(db_name(r.database_id), N''),
|
213
|
+
[task_state] = ISNULL(t.task_state, N''),
|
214
|
+
[command] = ISNULL(r.command, N''),
|
215
|
+
[application] = ISNULL(s.program_name, N''),
|
216
|
+
[wait_time_ms] = ISNULL(w.wait_duration_ms, 0),
|
217
|
+
[wait_type] = ISNULL(w.wait_type, N''),
|
218
|
+
[wait_resource] = ISNULL(w.resource_description, N''),
|
219
|
+
[blocked_by] = ISNULL(CONVERT (varchar, w.blocking_session_id), ''),
|
220
|
+
[head_blocker] =
|
221
|
+
CASE
|
222
|
+
-- session has an active request, is blocked, but is blocking others
|
223
|
+
WHEN r2.session_id IS NOT NULL AND r.blocking_session_id = 0 THEN '1'
|
224
|
+
-- session is idle but has an open tran and is blocking others
|
225
|
+
WHEN r.session_id IS NULL THEN '1'
|
226
|
+
ELSE ''
|
227
|
+
END,
|
228
|
+
[total_cpu_ms] = s.cpu_time,
|
229
|
+
[total_physical_io_mb] = (s.reads + s.writes) * 8 / 1024,
|
230
|
+
[memory_use_kb] = s.memory_usage * 8192 / 1024,
|
231
|
+
[open_transactions] = ISNULL(r.open_transaction_count,0),
|
232
|
+
[login_time] = s.login_time,
|
233
|
+
[last_request_start_time] = s.last_request_start_time,
|
234
|
+
[host_name] = ISNULL(s.host_name, N''),
|
235
|
+
[net_address] = ISNULL(c.client_net_address, N''),
|
236
|
+
[execution_context_id] = ISNULL(t.exec_context_id, 0),
|
237
|
+
[request_id] = ISNULL(r.request_id, 0),
|
238
|
+
[workload_group] = N''
|
239
|
+
FROM sys.dm_exec_sessions s LEFT OUTER JOIN sys.dm_exec_connections c ON (s.session_id = c.session_id)
|
240
|
+
LEFT OUTER JOIN sys.dm_exec_requests r ON (s.session_id = r.session_id)
|
241
|
+
LEFT OUTER JOIN sys.dm_os_tasks t ON (r.session_id = t.session_id AND r.request_id = t.request_id)
|
242
|
+
LEFT OUTER JOIN
|
243
|
+
(SELECT *, ROW_NUMBER() OVER (PARTITION BY waiting_task_address ORDER BY wait_duration_ms DESC) AS row_num
|
244
|
+
FROM sys.dm_os_waiting_tasks
|
245
|
+
) w ON (t.task_address = w.waiting_task_address) AND w.row_num = 1
|
246
|
+
LEFT OUTER JOIN sys.dm_exec_requests r2 ON (r.session_id = r2.blocking_session_id)
|
247
|
+
WHERE db_name(r.database_id) = '#{current_database}'
|
248
|
+
ORDER BY s.session_id|
|
249
|
+
end
|
250
|
+
|
251
|
+
# === SQLServer Specific (Rake/Test Helpers) ==================== #
|
252
|
+
|
253
|
+
def recreate_database
|
254
|
+
remove_database_connections_and_rollback do
|
255
|
+
do_execute "EXEC sp_MSforeachtable 'DROP TABLE ?'"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def recreate_database!(database=nil)
|
260
|
+
current_db = current_database
|
261
|
+
database ||= current_db
|
262
|
+
this_db = database.to_s == current_db
|
263
|
+
do_execute 'USE master' if this_db
|
264
|
+
drop_database(database)
|
265
|
+
create_database(database)
|
266
|
+
ensure
|
267
|
+
use_database(current_db) if this_db
|
268
|
+
end
|
269
|
+
|
270
|
+
def drop_database(database)
|
271
|
+
retry_count = 0
|
272
|
+
max_retries = 1
|
273
|
+
begin
|
274
|
+
do_execute "DROP DATABASE #{quote_table_name(database)}"
|
275
|
+
rescue ActiveRecord::StatementInvalid => err
|
276
|
+
if err.message =~ /because it is currently in use/i
|
277
|
+
raise if retry_count >= max_retries
|
278
|
+
retry_count += 1
|
279
|
+
remove_database_connections_and_rollback(database)
|
280
|
+
retry
|
281
|
+
elsif err.message =~ /does not exist/i
|
282
|
+
nil
|
283
|
+
else
|
284
|
+
raise
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def create_database(database)
|
290
|
+
do_execute "CREATE DATABASE #{quote_table_name(database)}"
|
291
|
+
end
|
292
|
+
|
293
|
+
def current_database
|
294
|
+
select_value 'SELECT DB_NAME()'
|
295
|
+
end
|
296
|
+
|
297
|
+
def charset
|
298
|
+
select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
protected
|
303
|
+
|
304
|
+
def select(sql, name = nil, binds = [])
|
305
|
+
exec_query(sql, name, binds).to_a
|
306
|
+
end
|
307
|
+
|
308
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
309
|
+
sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"# unless binds.empty?
|
310
|
+
super
|
311
|
+
end
|
312
|
+
|
313
|
+
def last_inserted_id(result)
|
314
|
+
super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
315
|
+
end
|
316
|
+
|
317
|
+
# === SQLServer Specific ======================================== #
|
318
|
+
|
319
|
+
def valid_isolation_levels
|
320
|
+
["READ COMMITTED", "READ UNCOMMITTED", "REPEATABLE READ", "SERIALIZABLE", "SNAPSHOT"]
|
321
|
+
end
|
322
|
+
|
323
|
+
# === SQLServer Specific (Executing) ============================ #
|
324
|
+
|
325
|
+
def do_execute(sql, name = 'SQL')
|
326
|
+
log(sql, name) do
|
327
|
+
with_sqlserver_error_handling { raw_connection_do(sql) }
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def do_exec_query(sql, name, binds)
|
332
|
+
explaining = name == 'EXPLAIN'
|
333
|
+
names_and_types = []
|
334
|
+
params = []
|
335
|
+
binds.each_with_index do |(column,value),index|
|
336
|
+
ar_column = column.is_a?(ActiveRecord::ConnectionAdapters::Column)
|
337
|
+
next if ar_column && column.sql_type == 'timestamp'
|
338
|
+
v = value
|
339
|
+
names_and_types << if ar_column
|
340
|
+
v = value.to_i if column.is_integer? && value.present?
|
341
|
+
"@#{index} #{column.sql_type_for_statement}"
|
342
|
+
elsif column.acts_like?(:string)
|
343
|
+
"@#{index} nvarchar(max)"
|
344
|
+
elsif column.is_a?(Fixnum)
|
345
|
+
v = value.to_i
|
346
|
+
"@#{index} int"
|
347
|
+
else
|
348
|
+
raise "Unknown bind columns. We can account for this."
|
349
|
+
end
|
350
|
+
quoted_value = ar_column ? quote(v,column) : quote(v,nil)
|
351
|
+
params << (explaining ? quoted_value : "@#{index} = #{quoted_value}")
|
352
|
+
end
|
353
|
+
if explaining
|
354
|
+
params.each_with_index do |param, index|
|
355
|
+
substitute_at_finder = /(@#{index})(?=(?:[^']|'[^']*')*$)/ # Finds unquoted @n values.
|
356
|
+
sql.sub! substitute_at_finder, param
|
357
|
+
end
|
358
|
+
else
|
359
|
+
sql = "EXEC sp_executesql #{quote(sql)}"
|
360
|
+
sql << ", #{quote(names_and_types.join(', '))}, #{params.join(', ')}" unless binds.empty?
|
361
|
+
end
|
362
|
+
raw_select sql, name, binds, :ar_result => true
|
363
|
+
end
|
364
|
+
|
365
|
+
def raw_connection_do(sql)
|
366
|
+
case @connection_options[:mode]
|
367
|
+
when :dblib
|
368
|
+
@connection.execute(sql).do
|
369
|
+
when :odbc
|
370
|
+
@connection.do(sql)
|
371
|
+
end
|
372
|
+
ensure
|
373
|
+
@update_sql = false
|
374
|
+
end
|
375
|
+
|
376
|
+
# === SQLServer Specific (Selecting) ============================ #
|
377
|
+
|
378
|
+
def raw_select(sql, name='SQL', binds=[], options={})
|
379
|
+
log(sql,name,binds) { _raw_select(sql, options) }
|
380
|
+
end
|
381
|
+
|
382
|
+
def _raw_select(sql, options={})
|
383
|
+
begin
|
384
|
+
handle = raw_connection_run(sql)
|
385
|
+
handle_to_names_and_values(handle, options)
|
386
|
+
ensure
|
387
|
+
finish_statement_handle(handle)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def raw_connection_run(sql)
|
392
|
+
with_sqlserver_error_handling do
|
393
|
+
case @connection_options[:mode]
|
394
|
+
when :dblib
|
395
|
+
@connection.execute(sql)
|
396
|
+
when :odbc
|
397
|
+
block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def handle_more_results?(handle)
|
403
|
+
case @connection_options[:mode]
|
404
|
+
when :dblib
|
405
|
+
when :odbc
|
406
|
+
handle.more_results
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def handle_to_names_and_values(handle, options={})
|
411
|
+
case @connection_options[:mode]
|
412
|
+
when :dblib
|
413
|
+
handle_to_names_and_values_dblib(handle, options)
|
414
|
+
when :odbc
|
415
|
+
handle_to_names_and_values_odbc(handle, options)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def handle_to_names_and_values_dblib(handle, options={})
|
420
|
+
query_options = {}.tap do |qo|
|
421
|
+
qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
|
422
|
+
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
|
423
|
+
end
|
424
|
+
results = handle.each(query_options)
|
425
|
+
columns = lowercase_schema_reflection ? handle.fields.map { |c| c.downcase } : handle.fields
|
426
|
+
options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
|
427
|
+
end
|
428
|
+
|
429
|
+
def handle_to_names_and_values_odbc(handle, options={})
|
430
|
+
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc
|
431
|
+
if options[:ar_result]
|
432
|
+
columns = lowercase_schema_reflection ? handle.columns(true).map { |c| c.name.downcase } : handle.columns(true).map { |c| c.name }
|
433
|
+
rows = handle.fetch_all || []
|
434
|
+
ActiveRecord::Result.new(columns, rows)
|
435
|
+
else
|
436
|
+
case options[:fetch]
|
437
|
+
when :all
|
438
|
+
handle.each_hash || []
|
439
|
+
when :rows
|
440
|
+
handle.fetch_all || []
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def finish_statement_handle(handle)
|
446
|
+
case @connection_options[:mode]
|
447
|
+
when :dblib
|
448
|
+
handle.cancel if handle
|
449
|
+
when :odbc
|
450
|
+
handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
|
451
|
+
end
|
452
|
+
handle
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
class LostConnection < WrappedDatabaseException
|
4
|
+
end
|
5
|
+
|
6
|
+
class DeadlockVictim < WrappedDatabaseException
|
7
|
+
end
|
8
|
+
|
9
|
+
module ConnectionAdapters
|
10
|
+
module Sqlserver
|
11
|
+
module Errors
|
12
|
+
|
13
|
+
LOST_CONNECTION_EXCEPTIONS = {
|
14
|
+
:dblib => ['TinyTds::Error'],
|
15
|
+
:odbc => ['ODBC::Error']
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
LOST_CONNECTION_MESSAGES = {
|
19
|
+
:dblib => [/closed connection/, /dead or not enabled/, /server failed/i],
|
20
|
+
:odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i]
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
|
24
|
+
def lost_connection_exceptions
|
25
|
+
exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
|
26
|
+
@lost_connection_exceptions ||= exceptions ? exceptions.map{ |e| e.constantize rescue nil }.compact : []
|
27
|
+
end
|
28
|
+
|
29
|
+
def lost_connection_messages
|
30
|
+
LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module Quoting
|
5
|
+
|
6
|
+
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
7
|
+
QUOTED_STRING_PREFIX = 'N'
|
8
|
+
|
9
|
+
def quote(value, column = nil)
|
10
|
+
case value
|
11
|
+
when String, ActiveSupport::Multibyte::Chars
|
12
|
+
if column && column.type == :integer && value.blank?
|
13
|
+
value.to_i.to_s
|
14
|
+
elsif column && column.type == :binary
|
15
|
+
column.class.string_to_binary(value)
|
16
|
+
elsif value.is_utf8? || (column && column.type == :string)
|
17
|
+
"#{quoted_string_prefix}'#{quote_string(value)}'"
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
when Date, Time
|
22
|
+
if column && column.sql_type == 'datetime'
|
23
|
+
"'#{quoted_datetime(value)}'"
|
24
|
+
elsif column && (column.sql_type == 'datetimeoffset' || column.sql_type == 'time')
|
25
|
+
"'#{quoted_full_iso8601(value)}'"
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
when nil
|
30
|
+
column.respond_to?(:sql_type) && column.sql_type == 'timestamp' ? 'DEFAULT' : super
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def quoted_string_prefix
|
37
|
+
QUOTED_STRING_PREFIX
|
38
|
+
end
|
39
|
+
|
40
|
+
def quote_string(string)
|
41
|
+
string.to_s.gsub(/\'/, "''")
|
42
|
+
end
|
43
|
+
|
44
|
+
def quote_column_name(name)
|
45
|
+
schema_cache.quote_name(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def quote_table_name(name)
|
49
|
+
quote_column_name(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
def substitute_at(column, index)
|
53
|
+
if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
|
54
|
+
nil
|
55
|
+
else
|
56
|
+
Arel.sql "@#{index}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def quoted_true
|
61
|
+
QUOTED_TRUE
|
62
|
+
end
|
63
|
+
|
64
|
+
def quoted_false
|
65
|
+
QUOTED_FALSE
|
66
|
+
end
|
67
|
+
|
68
|
+
def quoted_datetime(value)
|
69
|
+
if value.acts_like?(:time)
|
70
|
+
time_zone_qualified_value = quoted_value_acts_like_time_filter(value)
|
71
|
+
if value.is_a?(Date)
|
72
|
+
time_zone_qualified_value.to_time.xmlschema.to(18)
|
73
|
+
else
|
74
|
+
# CHANGED [Ruby 1.8] Not needed when 1.8 is dropped.
|
75
|
+
if value.is_a?(ActiveSupport::TimeWithZone) && RUBY_VERSION < '1.9'
|
76
|
+
time_zone_qualified_value = time_zone_qualified_value.to_time
|
77
|
+
end
|
78
|
+
time_zone_qualified_value.iso8601(3).to(22)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
quoted_date(value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def quoted_full_iso8601(value)
|
86
|
+
if value.acts_like?(:time)
|
87
|
+
value.is_a?(Date) ? quoted_value_acts_like_time_filter(value).to_time.xmlschema.to(18) : quoted_value_acts_like_time_filter(value).iso8601(7).to(22)
|
88
|
+
else
|
89
|
+
quoted_date(value)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def quoted_date(value)
|
94
|
+
if value.acts_like?(:time) && value.respond_to?(:usec)
|
95
|
+
"#{super}.#{sprintf("%03d",value.usec/1000)}"
|
96
|
+
elsif value.acts_like?(:date)
|
97
|
+
value.to_s(:_sqlserver_dateformat)
|
98
|
+
else
|
99
|
+
super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def quoted_value_acts_like_time_filter(value)
|
106
|
+
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
107
|
+
value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
|
5
|
+
|
6
|
+
attr_reader :view_information
|
7
|
+
|
8
|
+
def initialize(conn)
|
9
|
+
super
|
10
|
+
@table_names = nil
|
11
|
+
@view_names = nil
|
12
|
+
@view_information = {}
|
13
|
+
@quoted_names = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Superclass Overrides
|
17
|
+
|
18
|
+
def table_exists?(table_name)
|
19
|
+
return false if table_name.blank?
|
20
|
+
key = table_name_key(table_name)
|
21
|
+
return @tables[key] if @tables.key? key
|
22
|
+
@tables[key] = connection.table_exists?(table_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear!
|
26
|
+
super
|
27
|
+
@table_names = nil
|
28
|
+
@view_names = nil
|
29
|
+
@view_information.clear
|
30
|
+
@quoted_names.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_table_cache!(table_name)
|
34
|
+
key = table_name_key(table_name)
|
35
|
+
super(key)
|
36
|
+
super(table_name)
|
37
|
+
# SQL Server Specific
|
38
|
+
if @table_names
|
39
|
+
@table_names.delete key
|
40
|
+
@table_names.delete table_name
|
41
|
+
end
|
42
|
+
if @view_names
|
43
|
+
@view_names.delete key
|
44
|
+
@view_names.delete table_name
|
45
|
+
end
|
46
|
+
@view_information.delete key
|
47
|
+
end
|
48
|
+
|
49
|
+
# SQL Server Specific
|
50
|
+
|
51
|
+
def table_names
|
52
|
+
@table_names ||= connection.tables
|
53
|
+
end
|
54
|
+
|
55
|
+
def view_names
|
56
|
+
@view_names ||= connection.views
|
57
|
+
end
|
58
|
+
|
59
|
+
def view_exists?(table_name)
|
60
|
+
table_exists?(table_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def view_information(table_name)
|
64
|
+
key = table_name_key(table_name)
|
65
|
+
return @view_information[key] if @view_information.key? key
|
66
|
+
@view_information[key] = connection.send(:view_information, table_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def quote_name(name)
|
70
|
+
return @quoted_names[name] if @quoted_names.key? name
|
71
|
+
@quoted_names[name] = name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def table_name_key(table_name)
|
78
|
+
Utils.unqualify_table_name(table_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|