sic-activerecord-sqlserver-adapter 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +603 -0
- data/MIT-LICENSE +20 -0
- 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/explain.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -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 +109 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +395 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -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/utils.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +545 -0
- data/lib/active_record/sqlserver_test_case.rb +17 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/select_manager_sqlserver.rb +64 -0
- data/lib/arel/visitors/sqlserver.rb +326 -0
- metadata +98 -0
data/MIT-LICENSE
ADDED
@@ -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,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
|