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.
@@ -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(name = nil, table_type = 'BASE TABLE')
11
- info_schema_query do
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
- unquoted_table_name = unqualify_table_name(table_name)
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
- @sqlserver_columns_cache[table_name] ||= column_definitions(table_name).collect do |ci|
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
- column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
81
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
82
- change_column_sql << " NOT NULL" if options[:null] == false
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
- remove_default_constraint(table_name,column_name)
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
- detect_column_for!(table_name,column_name)
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, null, default = nil)
127
- column = detect_column_for!(table_name,column_name)
128
- unless null || default.nil?
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" unless 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(name = nil)
139
- @sqlserver_views_cache ||= tables(name,'VIEW')
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 CCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
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 [is_identity]
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 ON TC.TABLE_NAME = columns.TABLE_NAME AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
201
- LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS CCU ON TC.CONSTRAINT_NAME = CCU.CONSTRAINT_NAME AND CCU.COLUMN_NAME = columns.COLUMN_NAME
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 = info_schema_query { do_exec_query(sql, 'InfoSchema::ColumnDefinitions', binds) }
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? && views.include?(table_name)
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] = info_schema_query { select_value(col_default_sql) }
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[:is_identity] = ci[:is_identity].to_i == 1
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 = info_schema_query { 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)}'") }
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
- select_all("EXEC sp_helpconstraint '#{quote_string(table_name)}','nomsg'").flatten.select do |row|
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
- def unqualify_table_schema(table_name)
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(table_name).detect { |c| c.name == column_name.to_s }
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
- @sqlserver_view_information_cache[table_name] ||= begin
322
- view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
323
- if view_info
324
- view_info = view_info.with_indifferent_access
325
- if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
326
- view_info[:VIEW_DEFINITION] = info_schema_query do
327
- begin
328
- select_values("EXEC sp_helptext #{quote_table_name(table_name)}").join
329
- rescue
330
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
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
- views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
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,'InfoSchema::SetIdentityInsert'
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
- columns(table_name).detect(&:primary) || columns(table_name).detect(&:is_identity?)
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
+