activerecord-sqlserver-adapter 3.1.1 → 3.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG +253 -63
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +0 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +97 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +26 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +169 -61
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +3 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +12 -4
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +102 -123
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +185 -122
- data/lib/arel/visitors/sqlserver.rb +32 -9
- metadata +46 -63
- data/README.rdoc +0 -176
- data/lib/active_record/connection_adapters/sqlserver/version.rb +0 -11
@@ -2,19 +2,18 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Sqlserver
|
4
4
|
module SchemaStatements
|
5
|
-
|
5
|
+
|
6
6
|
def native_database_types
|
7
7
|
@native_database_types ||= initialize_native_database_types.freeze
|
8
8
|
end
|
9
9
|
|
10
|
-
def tables(
|
11
|
-
|
12
|
-
select_values "SELECT #{lowercase_schema_reflection_sql('TABLE_NAME')} FROM INFORMATION_SCHEMA.TABLES #{"WHERE TABLE_TYPE = '#{table_type}'" if table_type} ORDER BY TABLE_NAME"
|
13
|
-
end
|
10
|
+
def tables(table_type = 'BASE TABLE')
|
11
|
+
select_values "SELECT #{lowercase_schema_reflection_sql('TABLE_NAME')} FROM INFORMATION_SCHEMA.TABLES #{"WHERE TABLE_TYPE = '#{table_type}'" if table_type} ORDER BY TABLE_NAME", 'SCHEMA'
|
14
12
|
end
|
15
13
|
|
16
14
|
def table_exists?(table_name)
|
17
|
-
|
15
|
+
return false if table_name.blank?
|
16
|
+
unquoted_table_name = Utils.unqualify_table_name(table_name)
|
18
17
|
super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
|
19
18
|
end
|
20
19
|
|
@@ -39,70 +38,62 @@ module ActiveRecord
|
|
39
38
|
|
40
39
|
def columns(table_name, name = nil)
|
41
40
|
return [] if table_name.blank?
|
42
|
-
|
41
|
+
column_definitions(table_name).collect do |ci|
|
43
42
|
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
|
44
43
|
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
48
|
-
def create_table(table_name, options = {})
|
49
|
-
super
|
50
|
-
clear_cache!
|
51
|
-
end
|
52
|
-
|
53
47
|
def rename_table(table_name, new_name)
|
54
48
|
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
55
49
|
end
|
56
|
-
|
57
|
-
def drop_table(table_name, options = {})
|
58
|
-
super
|
59
|
-
clear_cache!
|
60
|
-
end
|
61
50
|
|
62
|
-
def add_column(table_name, column_name, type, options = {})
|
63
|
-
super
|
64
|
-
clear_cache!
|
65
|
-
end
|
66
|
-
|
67
51
|
def remove_column(table_name, *column_names)
|
68
52
|
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
53
|
+
ActiveSupport::Deprecation.warn 'Passing array to remove_columns is deprecated, please use multiple arguments, like: `remove_columns(:posts, :foo, :bar)`', caller if column_names.flatten!
|
69
54
|
column_names.flatten.each do |column_name|
|
70
55
|
remove_check_constraints(table_name, column_name)
|
71
56
|
remove_default_constraint(table_name, column_name)
|
72
57
|
remove_indexes(table_name, column_name)
|
73
58
|
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
74
59
|
end
|
75
|
-
clear_cache!
|
76
60
|
end
|
77
61
|
|
78
62
|
def change_column(table_name, column_name, type, options = {})
|
79
63
|
sql_commands = []
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
sql_commands << change_column_sql
|
64
|
+
indexes = []
|
65
|
+
column_object = schema_cache.columns[table_name].detect { |c| c.name.to_s == column_name.to_s }
|
66
|
+
|
84
67
|
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
85
|
-
|
68
|
+
remove_default_constraint(table_name,column_name)
|
69
|
+
indexes = indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }
|
70
|
+
remove_indexes(table_name, column_name)
|
86
71
|
end
|
72
|
+
sql_commands << "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(options[:default])} WHERE #{quote_column_name(column_name)} IS NULL" if !options[:null].nil? && options[:null] == false && !options[:default].nil?
|
73
|
+
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
74
|
+
sql_commands[-1] << " NOT NULL" if !options[:null].nil? && options[:null] == false
|
87
75
|
if options_include_default?(options)
|
88
76
|
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
|
89
77
|
end
|
78
|
+
|
79
|
+
#Add any removed indexes back
|
80
|
+
indexes.each do |index|
|
81
|
+
sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.collect {|c|quote_column_name(c)}.join(', ')})"
|
82
|
+
end
|
90
83
|
sql_commands.each { |c| do_execute(c) }
|
91
|
-
clear_cache!
|
92
84
|
end
|
93
85
|
|
94
86
|
def change_column_default(table_name, column_name, default)
|
95
87
|
remove_default_constraint(table_name, column_name)
|
96
88
|
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
|
97
|
-
clear_cache!
|
98
89
|
end
|
99
90
|
|
100
91
|
def rename_column(table_name, column_name, new_column_name)
|
101
|
-
|
92
|
+
schema_cache.clear_table_cache!(table_name)
|
93
|
+
detect_column_for! table_name, column_name
|
102
94
|
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
103
|
-
clear_cache!
|
104
95
|
end
|
105
|
-
|
96
|
+
|
106
97
|
def remove_index!(table_name, index_name)
|
107
98
|
do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
108
99
|
end
|
@@ -123,27 +114,27 @@ module ActiveRecord
|
|
123
114
|
end
|
124
115
|
end
|
125
116
|
|
126
|
-
def change_column_null(table_name, column_name,
|
127
|
-
column = detect_column_for!
|
128
|
-
|
117
|
+
def change_column_null(table_name, column_name, allow_null, default = nil)
|
118
|
+
column = detect_column_for! table_name, column_name
|
119
|
+
if !allow_null.nil? && allow_null == false && !default.nil?
|
129
120
|
do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
130
121
|
end
|
131
122
|
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
132
|
-
sql << " NOT NULL"
|
123
|
+
sql << " NOT NULL" if !allow_null.nil? && allow_null == false
|
133
124
|
do_execute sql
|
134
125
|
end
|
135
|
-
|
126
|
+
|
136
127
|
# === SQLServer Specific ======================================== #
|
137
|
-
|
138
|
-
def views
|
139
|
-
|
128
|
+
|
129
|
+
def views
|
130
|
+
tables('VIEW')
|
140
131
|
end
|
141
|
-
|
142
|
-
|
132
|
+
|
133
|
+
|
143
134
|
protected
|
144
|
-
|
135
|
+
|
145
136
|
# === SQLServer Specific ======================================== #
|
146
|
-
|
137
|
+
|
147
138
|
def initialize_native_database_types
|
148
139
|
{
|
149
140
|
:primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
@@ -170,12 +161,12 @@ module ActiveRecord
|
|
170
161
|
end
|
171
162
|
|
172
163
|
def column_definitions(table_name)
|
173
|
-
db_name = unqualify_db_name(table_name)
|
164
|
+
db_name = Utils.unqualify_db_name(table_name)
|
174
165
|
db_name_with_period = "#{db_name}." if db_name
|
175
|
-
table_schema = unqualify_table_schema(table_name)
|
176
|
-
table_name = unqualify_table_name(table_name)
|
166
|
+
table_schema = Utils.unqualify_table_schema(table_name)
|
167
|
+
table_name = Utils.unqualify_table_name(table_name)
|
177
168
|
sql = %{
|
178
|
-
SELECT DISTINCT
|
169
|
+
SELECT DISTINCT
|
179
170
|
#{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
|
180
171
|
#{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
|
181
172
|
columns.DATA_TYPE AS type,
|
@@ -185,28 +176,44 @@ module ActiveRecord
|
|
185
176
|
columns.ordinal_position,
|
186
177
|
CASE
|
187
178
|
WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
188
|
-
ELSE COL_LENGTH(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
179
|
+
ELSE COL_LENGTH('#{db_name_with_period}'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
189
180
|
END AS [length],
|
190
181
|
CASE
|
191
182
|
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
192
183
|
ELSE NULL
|
193
184
|
END AS [is_nullable],
|
194
|
-
CASE
|
195
|
-
WHEN
|
196
|
-
WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 1 THEN 1
|
185
|
+
CASE
|
186
|
+
WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
|
197
187
|
ELSE NULL
|
198
|
-
END AS [
|
188
|
+
END AS [is_primary],
|
189
|
+
c.is_identity AS [is_identity]
|
199
190
|
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
|
200
|
-
LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
201
|
-
|
191
|
+
LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
|
192
|
+
ON TC.TABLE_NAME = columns.TABLE_NAME
|
193
|
+
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
|
194
|
+
LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
|
195
|
+
ON KCU.COLUMN_NAME = columns.COLUMN_NAME
|
196
|
+
AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
|
197
|
+
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
|
198
|
+
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
|
199
|
+
INNER JOIN #{db_name}.sys.schemas AS s
|
200
|
+
ON s.name = columns.TABLE_SCHEMA
|
201
|
+
AND s.schema_id = s.schema_id
|
202
|
+
INNER JOIN #{db_name}.sys.objects AS o
|
203
|
+
ON s.schema_id = o.schema_id
|
204
|
+
AND o.is_ms_shipped = 0
|
205
|
+
AND o.type IN ('U', 'V')
|
206
|
+
AND o.name = columns.TABLE_NAME
|
207
|
+
INNER JOIN #{db_name}.sys.columns AS c
|
208
|
+
ON o.object_id = c.object_id
|
209
|
+
AND c.name = columns.COLUMN_NAME
|
202
210
|
WHERE columns.TABLE_NAME = @0
|
203
211
|
AND columns.TABLE_SCHEMA = #{table_schema.blank? ? "schema_name()" : "@1"}
|
204
212
|
ORDER BY columns.ordinal_position
|
205
|
-
|
206
213
|
}.gsub(/[ \t\r\n]+/,' ')
|
207
214
|
binds = [['table_name', table_name]]
|
208
215
|
binds << ['table_schema',table_schema] unless table_schema.blank?
|
209
|
-
results =
|
216
|
+
results = do_exec_query(sql, 'SCHEMA', binds)
|
210
217
|
results.collect do |ci|
|
211
218
|
ci = ci.symbolize_keys
|
212
219
|
ci[:type] = case ci[:type]
|
@@ -221,11 +228,11 @@ module ActiveRecord
|
|
221
228
|
else
|
222
229
|
ci[:type]
|
223
230
|
end
|
224
|
-
if ci[:default_value].nil? &&
|
231
|
+
if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
|
225
232
|
real_table_name = table_name_or_views_table_name(table_name)
|
226
233
|
real_column_name = views_real_column_name(table_name,ci[:name])
|
227
234
|
col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
|
228
|
-
ci[:default_value] =
|
235
|
+
ci[:default_value] = select_value col_default_sql, 'SCHEMA'
|
229
236
|
end
|
230
237
|
ci[:default_value] = case ci[:default_value]
|
231
238
|
when nil, '(null)', '(NULL)'
|
@@ -238,13 +245,14 @@ module ActiveRecord
|
|
238
245
|
match_data ? match_data[1] : nil
|
239
246
|
end
|
240
247
|
ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
|
241
|
-
ci[:
|
248
|
+
ci[:is_primary] = ci[:is_primary].to_i == 1
|
249
|
+
ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
|
242
250
|
ci
|
243
251
|
end
|
244
252
|
end
|
245
|
-
|
253
|
+
|
246
254
|
def remove_check_constraints(table_name, column_name)
|
247
|
-
constraints =
|
255
|
+
constraints = select_values "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'", 'SCHEMA'
|
248
256
|
constraints.each do |constraint|
|
249
257
|
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
250
258
|
end
|
@@ -252,7 +260,7 @@ module ActiveRecord
|
|
252
260
|
|
253
261
|
def remove_default_constraint(table_name, column_name)
|
254
262
|
# If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
|
255
|
-
|
263
|
+
execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
|
256
264
|
row['constraint_type'] == "DEFAULT on column #{column_name}"
|
257
265
|
end.each do |row|
|
258
266
|
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
@@ -264,26 +272,9 @@ module ActiveRecord
|
|
264
272
|
remove_index(table_name, {:name => index.name})
|
265
273
|
end
|
266
274
|
end
|
267
|
-
|
268
|
-
# === SQLServer Specific (Misc Helpers) ========================= #
|
269
|
-
|
270
|
-
def info_schema_query
|
271
|
-
log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
|
272
|
-
end
|
273
|
-
|
274
|
-
def unqualify_table_name(table_name)
|
275
|
-
table_name.to_s.split('.').last.tr('[]','')
|
276
|
-
end
|
277
275
|
|
278
|
-
|
279
|
-
table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
|
280
|
-
end
|
276
|
+
# === SQLServer Specific (Misc Helpers) ========================= #
|
281
277
|
|
282
|
-
def unqualify_db_name(table_name)
|
283
|
-
table_names = table_name.to_s.split('.')
|
284
|
-
table_names.length == 3 ? table_names.first.tr('[]','') : nil
|
285
|
-
end
|
286
|
-
|
287
278
|
def get_table_name(sql)
|
288
279
|
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
289
280
|
$2 || $3
|
@@ -293,69 +284,57 @@ module ActiveRecord
|
|
293
284
|
nil
|
294
285
|
end
|
295
286
|
end
|
296
|
-
|
287
|
+
|
297
288
|
def default_constraint_name(table_name, column_name)
|
298
289
|
"DF_#{table_name}_#{column_name}"
|
299
290
|
end
|
300
|
-
|
291
|
+
|
301
292
|
def detect_column_for!(table_name, column_name)
|
302
|
-
unless column = columns
|
293
|
+
unless column = schema_cache.columns[table_name].detect { |c| c.name == column_name.to_s }
|
303
294
|
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
304
295
|
end
|
305
296
|
column
|
306
297
|
end
|
307
|
-
|
298
|
+
|
308
299
|
def lowercase_schema_reflection_sql(node)
|
309
300
|
lowercase_schema_reflection ? "LOWER(#{node})" : node
|
310
301
|
end
|
311
|
-
|
302
|
+
|
312
303
|
# === SQLServer Specific (View Reflection) ====================== #
|
313
|
-
|
304
|
+
|
314
305
|
def view_table_name(table_name)
|
315
|
-
view_info = view_information(table_name)
|
306
|
+
view_info = schema_cache.view_information(table_name)
|
316
307
|
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
|
317
308
|
end
|
318
|
-
|
309
|
+
|
319
310
|
def view_information(table_name)
|
320
|
-
table_name = unqualify_table_name(table_name)
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
end
|
332
|
-
end
|
333
|
-
end
|
311
|
+
table_name = Utils.unqualify_table_name(table_name)
|
312
|
+
view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'", 'SCHEMA'
|
313
|
+
if view_info
|
314
|
+
view_info = view_info.with_indifferent_access
|
315
|
+
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
|
316
|
+
view_info[:VIEW_DEFINITION] = begin
|
317
|
+
select_values("EXEC sp_helptext #{quote_table_name(table_name)}", 'SCHEMA').join
|
318
|
+
rescue
|
319
|
+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
|
320
|
+
nil
|
321
|
+
end
|
334
322
|
end
|
335
|
-
view_info
|
336
323
|
end
|
324
|
+
view_info
|
337
325
|
end
|
338
|
-
|
326
|
+
|
339
327
|
def table_name_or_views_table_name(table_name)
|
340
|
-
unquoted_table_name = unqualify_table_name(table_name)
|
341
|
-
|
328
|
+
unquoted_table_name = Utils.unqualify_table_name(table_name)
|
329
|
+
schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
|
342
330
|
end
|
343
|
-
|
331
|
+
|
344
332
|
def views_real_column_name(table_name,column_name)
|
345
|
-
view_definition = view_information(table_name)[:VIEW_DEFINITION]
|
333
|
+
view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
|
346
334
|
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
347
335
|
match_data ? match_data[1] : column_name
|
348
336
|
end
|
349
|
-
|
350
|
-
# === SQLServer Specific (Column/View Caches) =================== #
|
351
337
|
|
352
|
-
def initialize_sqlserver_caches
|
353
|
-
@sqlserver_columns_cache = {}
|
354
|
-
@sqlserver_views_cache = nil
|
355
|
-
@sqlserver_view_information_cache = {}
|
356
|
-
@sqlserver_quoted_column_and_table_names = {}
|
357
|
-
end
|
358
|
-
|
359
338
|
# === SQLServer Specific (Identity Inserts) ===================== #
|
360
339
|
|
361
340
|
def query_requires_identity_insert?(sql)
|
@@ -367,11 +346,11 @@ module ActiveRecord
|
|
367
346
|
false
|
368
347
|
end
|
369
348
|
end
|
370
|
-
|
349
|
+
|
371
350
|
def insert_sql?(sql)
|
372
351
|
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
373
352
|
end
|
374
|
-
|
353
|
+
|
375
354
|
def with_identity_insert_enabled(table_name)
|
376
355
|
table_name = quote_table_name(table_name_or_views_table_name(table_name))
|
377
356
|
set_identity_insert(table_name, true)
|
@@ -382,13 +361,13 @@ module ActiveRecord
|
|
382
361
|
|
383
362
|
def set_identity_insert(table_name, enable = true)
|
384
363
|
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
385
|
-
do_execute sql,'
|
364
|
+
do_execute sql, 'SCHEMA'
|
386
365
|
rescue Exception => e
|
387
366
|
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
388
367
|
end
|
389
368
|
|
390
369
|
def identity_column(table_name)
|
391
|
-
|
370
|
+
schema_cache.columns[table_name].detect(&:is_identity?)
|
392
371
|
end
|
393
372
|
|
394
373
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module Showplan
|
5
|
+
class PrinterTable
|
6
|
+
|
7
|
+
cattr_accessor :max_column_width, :cell_padding
|
8
|
+
self.max_column_width = 50
|
9
|
+
self.cell_padding = 1
|
10
|
+
|
11
|
+
attr_reader :result
|
12
|
+
|
13
|
+
def initialize(result)
|
14
|
+
@result = result
|
15
|
+
end
|
16
|
+
|
17
|
+
def pp
|
18
|
+
@widths = compute_column_widths
|
19
|
+
@separator = build_separator
|
20
|
+
pp = []
|
21
|
+
pp << @separator
|
22
|
+
pp << build_cells(result.columns)
|
23
|
+
pp << @separator
|
24
|
+
result.rows.each do |row|
|
25
|
+
pp << build_cells(row)
|
26
|
+
end
|
27
|
+
pp << @separator
|
28
|
+
pp.join("\n") + "\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def compute_column_widths
|
34
|
+
[].tap do |computed_widths|
|
35
|
+
result.columns.each_with_index do |column, i|
|
36
|
+
cells_in_column = [column] + result.rows.map { |r| cast_item(r[i]) }
|
37
|
+
computed_width = cells_in_column.map(&:length).max
|
38
|
+
final_width = computed_width > max_column_width ? max_column_width : computed_width
|
39
|
+
computed_widths << final_width
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_separator
|
45
|
+
'+' + @widths.map {|w| '-' * (w + (cell_padding*2))}.join('+') + '+'
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_cells(items)
|
49
|
+
cells = []
|
50
|
+
items.each_with_index do |item, i|
|
51
|
+
cells << cast_item(item).ljust(@widths[i])
|
52
|
+
end
|
53
|
+
"| #{cells.join(' | ')} |"
|
54
|
+
end
|
55
|
+
|
56
|
+
def cast_item(item)
|
57
|
+
case item
|
58
|
+
when NilClass then 'NULL'
|
59
|
+
when Float then item.to_s.to(9)
|
60
|
+
else item.to_s.truncate(max_column_width)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module Showplan
|
5
|
+
class PrinterXml
|
6
|
+
|
7
|
+
def initialize(result)
|
8
|
+
@result = result
|
9
|
+
end
|
10
|
+
|
11
|
+
def pp
|
12
|
+
xml = @result.rows.first.first
|
13
|
+
if defined?(Nokogiri)
|
14
|
+
Nokogiri::XML(xml).to_xml :indent => 2, :encoding => 'UTF-8'
|
15
|
+
else
|
16
|
+
xml
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_record/connection_adapters/sqlserver/showplan/printer_table'
|
2
|
+
require 'active_record/connection_adapters/sqlserver/showplan/printer_xml'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionAdapters
|
6
|
+
module Sqlserver
|
7
|
+
module Showplan
|
8
|
+
|
9
|
+
OPTION_ALL = 'SHOWPLAN_ALL'
|
10
|
+
OPTION_TEXT = 'SHOWPLAN_TEXT'
|
11
|
+
OPTION_XML = 'SHOWPLAN_XML'
|
12
|
+
OPTIONS = [OPTION_ALL, OPTION_TEXT, OPTION_XML]
|
13
|
+
|
14
|
+
def explain(arel, binds = [])
|
15
|
+
sql = to_sql(arel)
|
16
|
+
result = with_showplan_on { do_exec_query(sql, 'EXPLAIN', binds) }
|
17
|
+
printer = showplan_printer.new(result)
|
18
|
+
printer.pp
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def with_showplan_on
|
25
|
+
set_showplan_option(true)
|
26
|
+
yield
|
27
|
+
ensure
|
28
|
+
set_showplan_option(false)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_showplan_option(enable = true)
|
32
|
+
sql = "SET #{option} #{enable ? 'ON' : 'OFF'}"
|
33
|
+
raw_connection_do(sql)
|
34
|
+
rescue Exception => e
|
35
|
+
raise ActiveRecordError, "#{option} could not be turned #{enable ? 'ON' : 'OFF'}, perhaps you do not have SHOWPLAN permissions?"
|
36
|
+
end
|
37
|
+
|
38
|
+
def option
|
39
|
+
(SQLServerAdapter.showplan_option || OPTION_ALL).tap do |opt|
|
40
|
+
raise(ArgumentError, "Unknown SHOWPLAN option #{opt.inspect} found.") if OPTIONS.exclude?(opt)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def showplan_all?
|
45
|
+
option == OPTION_ALL
|
46
|
+
end
|
47
|
+
|
48
|
+
def showplan_text?
|
49
|
+
option == OPTION_TEXT
|
50
|
+
end
|
51
|
+
|
52
|
+
def showplan_xml?
|
53
|
+
option == OPTION_XML
|
54
|
+
end
|
55
|
+
|
56
|
+
def showplan_printer
|
57
|
+
case option
|
58
|
+
when OPTION_XML then PrinterXml
|
59
|
+
when OPTION_ALL, OPTION_TEXT then PrinterTable
|
60
|
+
else PrinterTable
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
class Utils
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def unquote_string(string)
|
9
|
+
string.to_s.gsub(/\'\'/, "'")
|
10
|
+
end
|
11
|
+
|
12
|
+
def unqualify_table_name(table_name)
|
13
|
+
table_name.to_s.split('.').last.tr('[]','')
|
14
|
+
end
|
15
|
+
|
16
|
+
def unqualify_table_schema(table_name)
|
17
|
+
table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def unqualify_db_name(table_name)
|
21
|
+
table_names = table_name.to_s.split('.')
|
22
|
+
table_names.length == 3 ? table_names.first.tr('[]','') : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|