sic-activerecord-sqlserver-adapter 4.0.0

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 (25) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +603 -0
  3. data/MIT-LICENSE +20 -0
  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/explain.rb +41 -0
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
  10. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  11. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
  12. data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
  13. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +109 -0
  14. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
  15. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +395 -0
  16. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -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/utils.rb +32 -0
  20. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +545 -0
  21. data/lib/active_record/sqlserver_test_case.rb +17 -0
  22. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  23. data/lib/arel/select_manager_sqlserver.rb +64 -0
  24. data/lib/arel/visitors/sqlserver.rb +326 -0
  25. metadata +98 -0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2011
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 4.0.0
@@ -0,0 +1,42 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module CoreExt
5
+ module ActiveRecord
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :coerced_sqlserver_date_columns, :coerced_sqlserver_time_columns
11
+ self.coerced_sqlserver_date_columns = Set.new
12
+ self.coerced_sqlserver_time_columns = Set.new
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ def execute_procedure(proc_name, *variables)
18
+ if connection.respond_to?(:execute_procedure)
19
+ connection.execute_procedure(proc_name,*variables)
20
+ else
21
+ []
22
+ end
23
+ end
24
+
25
+ def coerce_sqlserver_date(*attributes)
26
+ self.coerced_sqlserver_date_columns += attributes.map(&:to_s)
27
+ end
28
+
29
+ def coerce_sqlserver_time(*attributes)
30
+ self.coerced_sqlserver_time_columns += attributes.map(&:to_s)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+ ActiveRecord::Base.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ActiveRecord
@@ -0,0 +1,41 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module CoreExt
5
+ module Explain
6
+
7
+ SQLSERVER_STATEMENT_PREFIX = "EXEC sp_executesql "
8
+ SQLSERVER_PARAM_MATCHER = /@\d+ =/
9
+
10
+ def exec_explain(queries)
11
+ unprepared_queries = queries.map { |sql, bind| [unprepare_sqlserver_statement(sql), bind] }
12
+ super(unprepared_queries)
13
+ end
14
+
15
+ private
16
+
17
+ # This is somewhat hacky, but it should reliably reformat our prepared sql statment
18
+ # which uses sp_executesql to just the first argument, then unquote it. Likewise our
19
+ # do_exec_query method should substitude the @n args withe the quoted values.
20
+ def unprepare_sqlserver_statement(sql)
21
+ if sql.starts_with?(SQLSERVER_STATEMENT_PREFIX)
22
+ executesql = sql.from(SQLSERVER_STATEMENT_PREFIX.length)
23
+ executesql_args = executesql.split(', ')
24
+ found_args = executesql_args.reject! { |arg| arg =~ SQLSERVER_PARAM_MATCHER }
25
+ executesql_args.pop if found_args && executesql_args.many?
26
+ executesql = executesql_args.join(', ').strip.match(/N'(.*)'/m)[1]
27
+ Utils.unquote_string(executesql)
28
+ else
29
+ sql
30
+ end
31
+ end
32
+
33
+
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ ActiveRecord::Base.extend ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::Explain
41
+ ActiveRecord::Relation.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::Explain
@@ -0,0 +1,4 @@
1
+ silence_warnings do
2
+ # Already defined in Rails
3
+ ActiveRecord::ExplainSubscriber::EXPLAINED_SQLS = /(select|update|delete|insert)\b/i
4
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module CoreExt
5
+ module ODBC
6
+
7
+ module Statement
8
+
9
+ def finished?
10
+ begin
11
+ connected?
12
+ false
13
+ rescue ::ODBC::Error
14
+ true
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ module Database
21
+
22
+ def run_block(*args)
23
+ yield sth = run(*args)
24
+ sth.drop
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+
36
+ ODBC::Statement.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Statement
37
+ ODBC::Database.send :include, ActiveRecord::ConnectionAdapters::Sqlserver::CoreExt::ODBC::Database
38
+
@@ -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
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module DatabaseLimits
5
+
6
+ def table_alias_length
7
+ 128
8
+ end
9
+
10
+ def column_name_length
11
+ 128
12
+ end
13
+
14
+ def table_name_length
15
+ 128
16
+ end
17
+
18
+ def index_name_length
19
+ 128
20
+ end
21
+
22
+ def columns_per_table
23
+ 1024
24
+ end
25
+
26
+ def indexes_per_table
27
+ 999
28
+ end
29
+
30
+ def columns_per_multicolumn_index
31
+ 16
32
+ end
33
+
34
+ def in_clause_length
35
+ 65536
36
+ end
37
+
38
+ def sql_query_length
39
+ 65536 * 4096
40
+ end
41
+
42
+ def joins_per_query
43
+ 256
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,458 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module DatabaseStatements
5
+
6
+ def select_rows(sql, name = nil, binds = [])
7
+ raw_select sql, name, binds, :fetch => :rows
8
+ end
9
+
10
+ def execute(sql, name = nil)
11
+ if id_insert_table_name = query_requires_identity_insert?(sql)
12
+ with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
13
+ else
14
+ do_execute(sql,name)
15
+ end
16
+ end
17
+
18
+ # TODO I bet there's a better way than a regex to take care of this
19
+ def exec_query(sql, name = 'SQL', binds = [], sqlserver_options = {})
20
+ # We can't update Identiy columns in sqlserver. So, strip out the id from the update.
21
+ if sql =~ /UPDATE/
22
+ # take off a comma before or after. This could probably be done better
23
+ if sql =~ /, \[id\] = @?[0-9]*/
24
+ sql.gsub! /, \[id\] = @?[0-9]*/, ''
25
+ elsif sql =~ /\s\[id\] = @?[0-9]*,/
26
+ sql.gsub! /\s\[id\] = @?[0-9]*,/, ''
27
+ end
28
+ end
29
+
30
+ if id_insert_table_name = sqlserver_options[:insert] ? query_requires_identity_insert?(sql) : nil
31
+ with_identity_insert_enabled(id_insert_table_name) { do_exec_query(sql, name, binds) }
32
+ else
33
+ do_exec_query(sql, name, binds)
34
+ end
35
+ end
36
+
37
+ #The abstract adapter ignores the last two parameters also
38
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
39
+ exec_query sql, name, binds, :insert => true
40
+ end
41
+
42
+ def exec_delete(sql, name, binds)
43
+ sql << "; SELECT @@ROWCOUNT AS AffectedRows"
44
+ super.rows.first.first
45
+ end
46
+
47
+ def exec_update(sql, name, binds)
48
+ sql << "; SELECT @@ROWCOUNT AS AffectedRows"
49
+ super.rows.first.first
50
+ end
51
+
52
+ def supports_statement_cache?
53
+ true
54
+ end
55
+
56
+ def begin_db_transaction
57
+ do_execute "BEGIN TRANSACTION"
58
+ end
59
+
60
+ def commit_db_transaction
61
+ disable_auto_reconnect { do_execute "COMMIT TRANSACTION" }
62
+ end
63
+
64
+ def rollback_db_transaction
65
+ do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
66
+ end
67
+
68
+ def create_savepoint
69
+ disable_auto_reconnect { do_execute "SAVE TRANSACTION #{current_savepoint_name}" }
70
+ end
71
+
72
+ def release_savepoint
73
+ end
74
+
75
+ def rollback_to_savepoint
76
+ disable_auto_reconnect { do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}" }
77
+ end
78
+
79
+ def add_limit_offset!(sql, options)
80
+ raise NotImplementedError, 'This has been moved to the SQLServerCompiler in Arel.'
81
+ end
82
+
83
+ def empty_insert_statement_value
84
+ "DEFAULT VALUES"
85
+ end
86
+
87
+ def case_sensitive_modifier(node)
88
+ node.acts_like?(:string) ? Arel::Nodes::Bin.new(node) : node
89
+ end
90
+
91
+ # === SQLServer Specific ======================================== #
92
+
93
+ def execute_procedure(proc_name, *variables)
94
+ vars = if variables.any? && variables.first.is_a?(Hash)
95
+ variables.first.map { |k,v| "@#{k} = #{quote(v)}" }
96
+ else
97
+ variables.map { |v| quote(v) }
98
+ end.join(', ')
99
+ sql = "EXEC #{proc_name} #{vars}".strip
100
+ name = 'Execute Procedure'
101
+ log(sql, name) do
102
+ case @connection_options[:mode]
103
+ when :dblib
104
+ result = @connection.execute(sql)
105
+ result.each(:as => :hash, :cache_rows => true) do |row|
106
+ r = row.with_indifferent_access
107
+ yield(r) if block_given?
108
+ end
109
+ result.each.map{ |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
110
+ when :odbc
111
+ results = []
112
+ raw_connection_run(sql) do |handle|
113
+ get_rows = lambda {
114
+ rows = handle_to_names_and_values handle, :fetch => :all
115
+ rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
116
+ results << rows
117
+ }
118
+ get_rows.call
119
+ while handle_more_results?(handle)
120
+ get_rows.call
121
+ end
122
+ end
123
+ results.many? ? results : results.first
124
+ end
125
+ end
126
+ end
127
+
128
+ def use_database(database=nil)
129
+ return if sqlserver_azure?
130
+ database ||= @connection_options[:database]
131
+ do_execute "USE #{quote_table_name(database)}" unless database.blank?
132
+ end
133
+
134
+ def user_options
135
+ return {} if sqlserver_azure?
136
+ select_rows("dbcc useroptions",'SCHEMA').inject(HashWithIndifferentAccess.new) do |values,row|
137
+ if row.instance_of? Hash
138
+ set_option = row.values[0].gsub(/\s+/,'_')
139
+ user_value = row.values[1]
140
+ elsif row.instance_of? Array
141
+ set_option = row[0].gsub(/\s+/,'_')
142
+ user_value = row[1]
143
+ end
144
+ values[set_option] = user_value
145
+ values
146
+ end
147
+ end
148
+
149
+ # TODO Rails 4 now supports isolation levels
150
+ def user_options_dateformat
151
+ if sqlserver_azure?
152
+ select_value 'SELECT [dateformat] FROM [sys].[syslanguages] WHERE [langid] = @@LANGID', 'SCHEMA'
153
+ else
154
+ user_options['dateformat']
155
+ end
156
+ end
157
+
158
+ def user_options_isolation_level
159
+ if sqlserver_azure?
160
+ sql = %|SELECT CASE [transaction_isolation_level]
161
+ WHEN 0 THEN NULL
162
+ WHEN 1 THEN 'READ UNCOMITTED'
163
+ WHEN 2 THEN 'READ COMITTED'
164
+ WHEN 3 THEN 'REPEATABLE READ'
165
+ WHEN 4 THEN 'SERIALIZABLE'
166
+ WHEN 5 THEN 'SNAPSHOT' END AS [isolation_level]
167
+ FROM [sys].[dm_exec_sessions]
168
+ WHERE [session_id] = @@SPID|.squish
169
+ select_value sql, 'SCHEMA'
170
+ else
171
+ user_options['isolation_level']
172
+ end
173
+ end
174
+
175
+ def user_options_language
176
+ if sqlserver_azure?
177
+ select_value 'SELECT @@LANGUAGE AS [language]', 'SCHEMA'
178
+ else
179
+ user_options['language']
180
+ end
181
+ end
182
+
183
+ def run_with_isolation_level(isolation_level)
184
+ raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{valid_isolation_levels.to_sentence}." if !valid_isolation_levels.include?(isolation_level.upcase)
185
+ initial_isolation_level = user_options_isolation_level || "READ COMMITTED"
186
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
187
+ begin
188
+ yield
189
+ ensure
190
+ do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
191
+ end if block_given?
192
+ end
193
+
194
+ def newid_function
195
+ select_value "SELECT NEWID()"
196
+ end
197
+
198
+ def newsequentialid_function
199
+ select_value "SELECT NEWSEQUENTIALID()"
200
+ end
201
+
202
+ def activity_stats
203
+ select_all %|
204
+ SELECT
205
+ [session_id] = s.session_id,
206
+ [user_process] = CONVERT(CHAR(1), s.is_user_process),
207
+ [login] = s.login_name,
208
+ [database] = ISNULL(db_name(r.database_id), N''),
209
+ [task_state] = ISNULL(t.task_state, N''),
210
+ [command] = ISNULL(r.command, N''),
211
+ [application] = ISNULL(s.program_name, N''),
212
+ [wait_time_ms] = ISNULL(w.wait_duration_ms, 0),
213
+ [wait_type] = ISNULL(w.wait_type, N''),
214
+ [wait_resource] = ISNULL(w.resource_description, N''),
215
+ [blocked_by] = ISNULL(CONVERT (varchar, w.blocking_session_id), ''),
216
+ [head_blocker] =
217
+ CASE
218
+ -- session has an active request, is blocked, but is blocking others
219
+ WHEN r2.session_id IS NOT NULL AND r.blocking_session_id = 0 THEN '1'
220
+ -- session is idle but has an open tran and is blocking others
221
+ WHEN r.session_id IS NULL THEN '1'
222
+ ELSE ''
223
+ END,
224
+ [total_cpu_ms] = s.cpu_time,
225
+ [total_physical_io_mb] = (s.reads + s.writes) * 8 / 1024,
226
+ [memory_use_kb] = s.memory_usage * 8192 / 1024,
227
+ [open_transactions] = ISNULL(r.open_transaction_count,0),
228
+ [login_time] = s.login_time,
229
+ [last_request_start_time] = s.last_request_start_time,
230
+ [host_name] = ISNULL(s.host_name, N''),
231
+ [net_address] = ISNULL(c.client_net_address, N''),
232
+ [execution_context_id] = ISNULL(t.exec_context_id, 0),
233
+ [request_id] = ISNULL(r.request_id, 0),
234
+ [workload_group] = N''
235
+ FROM sys.dm_exec_sessions s LEFT OUTER JOIN sys.dm_exec_connections c ON (s.session_id = c.session_id)
236
+ LEFT OUTER JOIN sys.dm_exec_requests r ON (s.session_id = r.session_id)
237
+ LEFT OUTER JOIN sys.dm_os_tasks t ON (r.session_id = t.session_id AND r.request_id = t.request_id)
238
+ LEFT OUTER JOIN
239
+ (SELECT *, ROW_NUMBER() OVER (PARTITION BY waiting_task_address ORDER BY wait_duration_ms DESC) AS row_num
240
+ FROM sys.dm_os_waiting_tasks
241
+ ) w ON (t.task_address = w.waiting_task_address) AND w.row_num = 1
242
+ LEFT OUTER JOIN sys.dm_exec_requests r2 ON (r.session_id = r2.blocking_session_id)
243
+ WHERE db_name(r.database_id) = '#{current_database}'
244
+ ORDER BY s.session_id|
245
+ end
246
+
247
+ # === SQLServer Specific (Rake/Test Helpers) ==================== #
248
+
249
+ def recreate_database
250
+ remove_database_connections_and_rollback do
251
+ do_execute "EXEC sp_MSforeachtable 'DROP TABLE ?'"
252
+ end
253
+ end
254
+
255
+ def recreate_database!(database=nil)
256
+ current_db = current_database
257
+ database ||= current_db
258
+ this_db = database.to_s == current_db
259
+ do_execute 'USE master' if this_db
260
+ drop_database(database)
261
+ create_database(database)
262
+ ensure
263
+ use_database(current_db) if this_db
264
+ end
265
+
266
+ def drop_database(database)
267
+ retry_count = 0
268
+ max_retries = 1
269
+ begin
270
+ do_execute "DROP DATABASE #{quote_table_name(database)}"
271
+ rescue ActiveRecord::StatementInvalid => err
272
+ if err.message =~ /because it is currently in use/i
273
+ raise if retry_count >= max_retries
274
+ retry_count += 1
275
+ remove_database_connections_and_rollback(database)
276
+ retry
277
+ elsif err.message =~ /does not exist/i
278
+ nil
279
+ else
280
+ raise
281
+ end
282
+ end
283
+ end
284
+
285
+ def create_database(database, collation=@connection_options[:collation])
286
+ if collation
287
+ do_execute "CREATE DATABASE #{quote_table_name(database)} COLLATE #{collation}"
288
+ else
289
+ do_execute "CREATE DATABASE #{quote_table_name(database)}"
290
+ end
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)
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