activerecord-sqlserver-adapter 3.1.1 → 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 +253 -63
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -7
- 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/relation.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +169 -61
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +3 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +12 -4
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +102 -123
- 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 +185 -122
- data/lib/arel/visitors/sqlserver.rb +32 -9
- metadata +46 -63
- data/README.rdoc +0 -176
- data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -11
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module CoreExt
|
5
|
+
class ExplainSubscriber
|
6
|
+
def call(*args)
|
7
|
+
if queries = Thread.current[:available_queries_for_explain]
|
8
|
+
payload = args.last
|
9
|
+
queries << payload.values_at(:sql, :binds) unless ignore_sqlserver_payload?(payload)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
|
14
|
+
SQLSERVER_EXPLAINED_SQLS = /(select|update|delete|insert)/i
|
15
|
+
|
16
|
+
# Need to modify the regex for the TSQL generated by this adapter so we can explain the proper sql statements
|
17
|
+
def ignore_sqlserver_payload?(payload)
|
18
|
+
payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ SQLSERVER_EXPLAINED_SQLS
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveSupport::Notifications.subscribe("sql.active_record", new)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module CoreExt
|
5
|
+
module Relation
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def tables_in_string(string)
|
10
|
+
super - ['__rnt']
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Relation.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::Relation
|
@@ -2,7 +2,9 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Sqlserver
|
4
4
|
module DatabaseStatements
|
5
|
-
|
5
|
+
|
6
|
+
include CoreExt::DatabaseStatements
|
7
|
+
|
6
8
|
def select_rows(sql, name = nil)
|
7
9
|
raw_select sql, name, [], :fetch => :rows
|
8
10
|
end
|
@@ -14,7 +16,7 @@ module ActiveRecord
|
|
14
16
|
do_execute(sql,name)
|
15
17
|
end
|
16
18
|
end
|
17
|
-
|
19
|
+
|
18
20
|
def exec_query(sql, name = 'SQL', binds = [], sqlserver_options = {})
|
19
21
|
if id_insert_table_name = sqlserver_options[:insert] ? query_requires_identity_insert?(sql) : nil
|
20
22
|
with_identity_insert_enabled(id_insert_table_name) { do_exec_query(sql, name, binds) }
|
@@ -22,11 +24,11 @@ module ActiveRecord
|
|
22
24
|
do_exec_query(sql, name, binds)
|
23
25
|
end
|
24
26
|
end
|
25
|
-
|
27
|
+
|
26
28
|
def exec_insert(sql, name, binds)
|
27
29
|
exec_query sql, name, binds, :insert => true
|
28
30
|
end
|
29
|
-
|
31
|
+
|
30
32
|
def exec_delete(sql, name, binds)
|
31
33
|
sql << "; SELECT @@ROWCOUNT AS AffectedRows"
|
32
34
|
super.rows.first.first
|
@@ -38,34 +40,42 @@ module ActiveRecord
|
|
38
40
|
end
|
39
41
|
|
40
42
|
def outside_transaction?
|
41
|
-
|
43
|
+
uncached { select_value('SELECT @@TRANCOUNT', 'SCHEMA') == 0 }
|
42
44
|
end
|
43
|
-
|
45
|
+
|
44
46
|
def supports_statement_cache?
|
45
47
|
true
|
46
48
|
end
|
47
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
|
+
|
48
58
|
def begin_db_transaction
|
49
59
|
do_execute "BEGIN TRANSACTION"
|
50
60
|
end
|
51
61
|
|
52
62
|
def commit_db_transaction
|
53
|
-
do_execute "COMMIT TRANSACTION"
|
63
|
+
disable_auto_reconnect { do_execute "COMMIT TRANSACTION" }
|
54
64
|
end
|
55
65
|
|
56
66
|
def rollback_db_transaction
|
57
|
-
do_execute "ROLLBACK TRANSACTION"
|
67
|
+
do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
|
58
68
|
end
|
59
69
|
|
60
70
|
def create_savepoint
|
61
|
-
do_execute "SAVE TRANSACTION #{current_savepoint_name}"
|
71
|
+
disable_auto_reconnect { do_execute "SAVE TRANSACTION #{current_savepoint_name}" }
|
62
72
|
end
|
63
73
|
|
64
74
|
def release_savepoint
|
65
75
|
end
|
66
76
|
|
67
77
|
def rollback_to_savepoint
|
68
|
-
do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}"
|
78
|
+
disable_auto_reconnect { do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}" }
|
69
79
|
end
|
70
80
|
|
71
81
|
def add_limit_offset!(sql, options)
|
@@ -79,11 +89,15 @@ module ActiveRecord
|
|
79
89
|
def case_sensitive_modifier(node)
|
80
90
|
node.acts_like?(:string) ? Arel::Nodes::Bin.new(node) : node
|
81
91
|
end
|
82
|
-
|
92
|
+
|
83
93
|
# === SQLServer Specific ======================================== #
|
84
|
-
|
94
|
+
|
85
95
|
def execute_procedure(proc_name, *variables)
|
86
|
-
vars = 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(', ')
|
87
101
|
sql = "EXEC #{proc_name} #{vars}".strip
|
88
102
|
name = 'Execute Procedure'
|
89
103
|
log(sql, name) do
|
@@ -112,45 +126,130 @@ module ActiveRecord
|
|
112
126
|
end
|
113
127
|
end
|
114
128
|
end
|
115
|
-
|
129
|
+
|
116
130
|
def use_database(database=nil)
|
117
131
|
return if sqlserver_azure?
|
118
132
|
database ||= @connection_options[:database]
|
119
133
|
do_execute "USE #{quote_table_name(database)}" unless database.blank?
|
120
134
|
end
|
121
|
-
|
135
|
+
|
122
136
|
def user_options
|
123
|
-
|
124
|
-
|
125
|
-
|
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+/, '_')
|
126
147
|
user_value = row[1]
|
127
|
-
values[set_option] = user_value
|
128
|
-
values
|
129
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']
|
130
184
|
end
|
131
185
|
end
|
132
186
|
|
133
187
|
def run_with_isolation_level(isolation_level)
|
134
188
|
raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{valid_isolation_levels.to_sentence}." if !valid_isolation_levels.include?(isolation_level.upcase)
|
135
|
-
initial_isolation_level =
|
189
|
+
initial_isolation_level = user_options_isolation_level || "READ COMMITTED"
|
136
190
|
do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
|
137
191
|
begin
|
138
|
-
yield
|
192
|
+
yield
|
139
193
|
ensure
|
140
194
|
do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
|
141
195
|
end if block_given?
|
142
196
|
end
|
143
|
-
|
197
|
+
|
144
198
|
def newid_function
|
145
199
|
select_value "SELECT NEWID()"
|
146
200
|
end
|
147
|
-
|
201
|
+
|
148
202
|
def newsequentialid_function
|
149
203
|
select_value "SELECT NEWSEQUENTIALID()"
|
150
204
|
end
|
151
|
-
|
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
|
+
|
152
251
|
# === SQLServer Specific (Rake/Test Helpers) ==================== #
|
153
|
-
|
252
|
+
|
154
253
|
def recreate_database
|
155
254
|
remove_database_connections_and_rollback do
|
156
255
|
do_execute "EXEC sp_MSforeachtable 'DROP TABLE ?'"
|
@@ -194,18 +293,18 @@ module ActiveRecord
|
|
194
293
|
def current_database
|
195
294
|
select_value 'SELECT DB_NAME()'
|
196
295
|
end
|
197
|
-
|
296
|
+
|
198
297
|
def charset
|
199
298
|
select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
|
200
299
|
end
|
201
|
-
|
202
|
-
|
300
|
+
|
301
|
+
|
203
302
|
protected
|
204
|
-
|
303
|
+
|
205
304
|
def select(sql, name = nil, binds = [])
|
206
305
|
exec_query(sql, name, binds).to_a
|
207
306
|
end
|
208
|
-
|
307
|
+
|
209
308
|
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
210
309
|
sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"# unless binds.empty?
|
211
310
|
super
|
@@ -214,24 +313,23 @@ module ActiveRecord
|
|
214
313
|
def last_inserted_id(result)
|
215
314
|
super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
|
216
315
|
end
|
217
|
-
|
316
|
+
|
218
317
|
# === SQLServer Specific ======================================== #
|
219
|
-
|
318
|
+
|
220
319
|
def valid_isolation_levels
|
221
320
|
["READ COMMITTED", "READ UNCOMMITTED", "REPEATABLE READ", "SERIALIZABLE", "SNAPSHOT"]
|
222
321
|
end
|
223
|
-
|
322
|
+
|
224
323
|
# === SQLServer Specific (Executing) ============================ #
|
225
324
|
|
226
|
-
def do_execute(sql, name =
|
227
|
-
name ||= 'EXECUTE'
|
325
|
+
def do_execute(sql, name = 'SQL')
|
228
326
|
log(sql, name) do
|
229
|
-
|
327
|
+
with_sqlserver_error_handling { raw_connection_do(sql) }
|
230
328
|
end
|
231
329
|
end
|
232
|
-
|
330
|
+
|
233
331
|
def do_exec_query(sql, name, binds)
|
234
|
-
|
332
|
+
explaining = name == 'EXPLAIN'
|
235
333
|
names_and_types = []
|
236
334
|
params = []
|
237
335
|
binds.each_with_index do |(column,value),index|
|
@@ -250,13 +348,20 @@ module ActiveRecord
|
|
250
348
|
raise "Unknown bind columns. We can account for this."
|
251
349
|
end
|
252
350
|
quoted_value = ar_column ? quote(v,column) : quote(v,nil)
|
253
|
-
params << "@#{index} = #{quoted_value}"
|
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?
|
254
361
|
end
|
255
|
-
sql = "EXEC sp_executesql #{statement}"
|
256
|
-
sql << ", #{quote(names_and_types.join(', '))}, #{params.join(', ')}" unless binds.empty?
|
257
362
|
raw_select sql, name, binds, :ar_result => true
|
258
363
|
end
|
259
|
-
|
364
|
+
|
260
365
|
def raw_connection_do(sql)
|
261
366
|
case @connection_options[:mode]
|
262
367
|
when :dblib
|
@@ -267,22 +372,24 @@ module ActiveRecord
|
|
267
372
|
ensure
|
268
373
|
@update_sql = false
|
269
374
|
end
|
270
|
-
|
375
|
+
|
271
376
|
# === SQLServer Specific (Selecting) ============================ #
|
272
377
|
|
273
|
-
def raw_select(sql, name=
|
274
|
-
log(sql,name,binds)
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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)
|
281
388
|
end
|
282
389
|
end
|
283
|
-
|
390
|
+
|
284
391
|
def raw_connection_run(sql)
|
285
|
-
|
392
|
+
with_sqlserver_error_handling do
|
286
393
|
case @connection_options[:mode]
|
287
394
|
when :dblib
|
288
395
|
@connection.execute(sql)
|
@@ -291,7 +398,7 @@ module ActiveRecord
|
|
291
398
|
end
|
292
399
|
end
|
293
400
|
end
|
294
|
-
|
401
|
+
|
295
402
|
def handle_more_results?(handle)
|
296
403
|
case @connection_options[:mode]
|
297
404
|
when :dblib
|
@@ -299,7 +406,7 @@ module ActiveRecord
|
|
299
406
|
handle.more_results
|
300
407
|
end
|
301
408
|
end
|
302
|
-
|
409
|
+
|
303
410
|
def handle_to_names_and_values(handle, options={})
|
304
411
|
case @connection_options[:mode]
|
305
412
|
when :dblib
|
@@ -308,7 +415,7 @@ module ActiveRecord
|
|
308
415
|
handle_to_names_and_values_odbc(handle, options)
|
309
416
|
end
|
310
417
|
end
|
311
|
-
|
418
|
+
|
312
419
|
def handle_to_names_and_values_dblib(handle, options={})
|
313
420
|
query_options = {}.tap do |qo|
|
314
421
|
qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
|
@@ -318,7 +425,7 @@ module ActiveRecord
|
|
318
425
|
columns = lowercase_schema_reflection ? handle.fields.map { |c| c.downcase } : handle.fields
|
319
426
|
options[:ar_result] ? ActiveRecord::Result.new(columns, results) : results
|
320
427
|
end
|
321
|
-
|
428
|
+
|
322
429
|
def handle_to_names_and_values_odbc(handle, options={})
|
323
430
|
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc
|
324
431
|
if options[:ar_result]
|
@@ -334,16 +441,17 @@ module ActiveRecord
|
|
334
441
|
end
|
335
442
|
end
|
336
443
|
end
|
337
|
-
|
444
|
+
|
338
445
|
def finish_statement_handle(handle)
|
339
446
|
case @connection_options[:mode]
|
340
|
-
when :dblib
|
447
|
+
when :dblib
|
448
|
+
handle.cancel if handle
|
341
449
|
when :odbc
|
342
450
|
handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
|
343
451
|
end
|
344
452
|
handle
|
345
453
|
end
|
346
|
-
|
454
|
+
|
347
455
|
end
|
348
456
|
end
|
349
457
|
end
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
case value
|
11
11
|
when String, ActiveSupport::Multibyte::Chars
|
12
12
|
if column && column.type == :integer && value.blank?
|
13
|
-
|
13
|
+
value.to_i.to_s
|
14
14
|
elsif column && column.type == :binary
|
15
15
|
column.class.string_to_binary(value)
|
16
16
|
elsif value.is_utf8? || (column && column.type == :string)
|
@@ -42,8 +42,7 @@ module ActiveRecord
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def quote_column_name(name)
|
45
|
-
|
46
|
-
name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
|
45
|
+
schema_cache.quote_name(name)
|
47
46
|
end
|
48
47
|
|
49
48
|
def quote_table_name(name)
|
@@ -68,7 +67,16 @@ module ActiveRecord
|
|
68
67
|
|
69
68
|
def quoted_datetime(value)
|
70
69
|
if value.acts_like?(:time)
|
71
|
-
|
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
|
72
80
|
else
|
73
81
|
quoted_date(value)
|
74
82
|
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
|
+
|