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.
- 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
|