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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +385 -61
  3. data/MIT-LICENSE +1 -1
  4. data/VERSION +1 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +42 -0
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +97 -0
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +26 -0
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
  10. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
  11. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  12. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
  13. data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
  14. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +113 -0
  15. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
  16. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +376 -0
  17. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
  18. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
  19. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
  20. data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
  21. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +344 -1055
  22. data/lib/arel/visitors/sqlserver.rb +389 -0
  23. metadata +60 -83
  24. data/README.rdoc +0 -190
  25. data/RUNNING_UNIT_TESTS +0 -65
  26. data/Rakefile +0 -41
  27. data/autotest/discover.rb +0 -4
  28. data/autotest/railssqlserver.rb +0 -16
  29. data/autotest/sqlserver.rb +0 -54
  30. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
  31. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
  32. data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
  33. data/test/cases/adapter_test_sqlserver.rb +0 -756
  34. data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
  35. data/test/cases/basics_test_sqlserver.rb +0 -21
  36. data/test/cases/calculations_test_sqlserver.rb +0 -20
  37. data/test/cases/column_test_sqlserver.rb +0 -285
  38. data/test/cases/connection_test_sqlserver.rb +0 -146
  39. data/test/cases/eager_association_test_sqlserver.rb +0 -42
  40. data/test/cases/execute_procedure_test_sqlserver.rb +0 -44
  41. data/test/cases/inheritance_test_sqlserver.rb +0 -28
  42. data/test/cases/method_scoping_test_sqlserver.rb +0 -28
  43. data/test/cases/migration_test_sqlserver.rb +0 -123
  44. data/test/cases/named_scope_test_sqlserver.rb +0 -21
  45. data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
  46. data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
  47. data/test/cases/query_cache_test_sqlserver.rb +0 -24
  48. data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
  49. data/test/cases/specific_schema_test_sqlserver.rb +0 -97
  50. data/test/cases/sqlserver_helper.rb +0 -127
  51. data/test/cases/table_name_test_sqlserver.rb +0 -38
  52. data/test/cases/transaction_test_sqlserver.rb +0 -93
  53. data/test/cases/unicode_test_sqlserver.rb +0 -50
  54. data/test/cases/validations_test_sqlserver.rb +0 -35
  55. data/test/connections/native_sqlserver/connection.rb +0 -25
  56. data/test/connections/native_sqlserver_odbc/connection.rb +0 -27
  57. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
  58. 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
+