activerecord-sqlserver-adapter 2.3.7 → 3.2.18
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|