activerecord-sqlserver-adapter 2.3.7 → 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 +385 -61
- data/MIT-LICENSE +1 -1
- 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/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/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 +113 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +376 -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/showplan.rb +67 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +344 -1055
- data/lib/arel/visitors/sqlserver.rb +389 -0
- metadata +60 -83
- data/README.rdoc +0 -190
- data/RUNNING_UNIT_TESTS +0 -65
- data/Rakefile +0 -41
- data/autotest/discover.rb +0 -4
- data/autotest/railssqlserver.rb +0 -16
- data/autotest/sqlserver.rb +0 -54
- data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
- data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
- data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
- data/test/cases/adapter_test_sqlserver.rb +0 -756
- data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
- data/test/cases/basics_test_sqlserver.rb +0 -21
- data/test/cases/calculations_test_sqlserver.rb +0 -20
- data/test/cases/column_test_sqlserver.rb +0 -285
- data/test/cases/connection_test_sqlserver.rb +0 -146
- data/test/cases/eager_association_test_sqlserver.rb +0 -42
- data/test/cases/execute_procedure_test_sqlserver.rb +0 -44
- data/test/cases/inheritance_test_sqlserver.rb +0 -28
- data/test/cases/method_scoping_test_sqlserver.rb +0 -28
- data/test/cases/migration_test_sqlserver.rb +0 -123
- data/test/cases/named_scope_test_sqlserver.rb +0 -21
- data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
- data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
- data/test/cases/query_cache_test_sqlserver.rb +0 -24
- data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
- data/test/cases/specific_schema_test_sqlserver.rb +0 -97
- data/test/cases/sqlserver_helper.rb +0 -127
- data/test/cases/table_name_test_sqlserver.rb +0 -38
- data/test/cases/transaction_test_sqlserver.rb +0 -93
- data/test/cases/unicode_test_sqlserver.rb +0 -50
- data/test/cases/validations_test_sqlserver.rb +0 -35
- data/test/connections/native_sqlserver/connection.rb +0 -25
- data/test/connections/native_sqlserver_odbc/connection.rb +0 -27
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
- data/test/schema/sqlserver_specific_schema.rb +0 -94
@@ -0,0 +1,376 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module SchemaStatements
|
5
|
+
|
6
|
+
def native_database_types
|
7
|
+
@native_database_types ||= initialize_native_database_types.freeze
|
8
|
+
end
|
9
|
+
|
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'
|
12
|
+
end
|
13
|
+
|
14
|
+
def table_exists?(table_name)
|
15
|
+
return false if table_name.blank?
|
16
|
+
unquoted_table_name = Utils.unqualify_table_name(table_name)
|
17
|
+
super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def indexes(table_name, name = nil)
|
21
|
+
data = select("EXEC sp_helpindex #{quote(table_name)}",name) rescue []
|
22
|
+
data.inject([]) do |indexes,index|
|
23
|
+
index = index.with_indifferent_access
|
24
|
+
if index[:index_description] =~ /primary key/
|
25
|
+
indexes
|
26
|
+
else
|
27
|
+
name = index[:index_name]
|
28
|
+
unique = index[:index_description] =~ /unique/
|
29
|
+
columns = index[:index_keys].split(',').map do |column|
|
30
|
+
column.strip!
|
31
|
+
column.gsub! '(-)', '' if column.ends_with?('(-)')
|
32
|
+
column
|
33
|
+
end
|
34
|
+
indexes << IndexDefinition.new(table_name, name, unique, columns)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def columns(table_name, name = nil)
|
40
|
+
return [] if table_name.blank?
|
41
|
+
column_definitions(table_name).collect do |ci|
|
42
|
+
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
|
43
|
+
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def rename_table(table_name, new_name)
|
48
|
+
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove_column(table_name, *column_names)
|
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!
|
54
|
+
column_names.flatten.each do |column_name|
|
55
|
+
remove_check_constraints(table_name, column_name)
|
56
|
+
remove_default_constraint(table_name, column_name)
|
57
|
+
remove_indexes(table_name, column_name)
|
58
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def change_column(table_name, column_name, type, options = {})
|
63
|
+
sql_commands = []
|
64
|
+
indexes = []
|
65
|
+
column_object = schema_cache.columns[table_name].detect { |c| c.name.to_s == column_name.to_s }
|
66
|
+
|
67
|
+
if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
|
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)
|
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
|
75
|
+
if options_include_default?(options)
|
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)}"
|
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
|
83
|
+
sql_commands.each { |c| do_execute(c) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def change_column_default(table_name, column_name, default)
|
87
|
+
remove_default_constraint(table_name, column_name)
|
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)}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def rename_column(table_name, column_name, new_column_name)
|
92
|
+
schema_cache.clear_table_cache!(table_name)
|
93
|
+
detect_column_for! table_name, column_name
|
94
|
+
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
|
95
|
+
end
|
96
|
+
|
97
|
+
def remove_index!(table_name, index_name)
|
98
|
+
do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
102
|
+
type_limitable = ['string','integer','float','char','nchar','varchar','nvarchar'].include?(type.to_s)
|
103
|
+
limit = nil unless type_limitable
|
104
|
+
case type.to_s
|
105
|
+
when 'integer'
|
106
|
+
case limit
|
107
|
+
when 1..2 then 'smallint'
|
108
|
+
when 3..4, nil then 'integer'
|
109
|
+
when 5..8 then 'bigint'
|
110
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
|
111
|
+
end
|
112
|
+
else
|
113
|
+
super
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
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?
|
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")
|
121
|
+
end
|
122
|
+
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
|
123
|
+
sql << " NOT NULL" if !allow_null.nil? && allow_null == false
|
124
|
+
do_execute sql
|
125
|
+
end
|
126
|
+
|
127
|
+
# === SQLServer Specific ======================================== #
|
128
|
+
|
129
|
+
def views
|
130
|
+
tables('VIEW')
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
# === SQLServer Specific ======================================== #
|
137
|
+
|
138
|
+
def initialize_native_database_types
|
139
|
+
{
|
140
|
+
:primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
|
141
|
+
:string => { :name => native_string_database_type, :limit => 255 },
|
142
|
+
:text => { :name => native_text_database_type },
|
143
|
+
:integer => { :name => "int", :limit => 4 },
|
144
|
+
:float => { :name => "float", :limit => 8 },
|
145
|
+
:decimal => { :name => "decimal" },
|
146
|
+
:datetime => { :name => "datetime" },
|
147
|
+
:timestamp => { :name => "datetime" },
|
148
|
+
:time => { :name => native_time_database_type },
|
149
|
+
:date => { :name => native_date_database_type },
|
150
|
+
:binary => { :name => native_binary_database_type },
|
151
|
+
:boolean => { :name => "bit"},
|
152
|
+
# These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
|
153
|
+
:char => { :name => 'char' },
|
154
|
+
:varchar_max => { :name => 'varchar(max)' },
|
155
|
+
:nchar => { :name => "nchar" },
|
156
|
+
:nvarchar => { :name => "nvarchar", :limit => 255 },
|
157
|
+
:nvarchar_max => { :name => "nvarchar(max)" },
|
158
|
+
:ntext => { :name => "ntext" },
|
159
|
+
:ss_timestamp => { :name => 'timestamp' }
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def column_definitions(table_name)
|
164
|
+
db_name = Utils.unqualify_db_name(table_name)
|
165
|
+
db_name_with_period = "#{db_name}." if db_name
|
166
|
+
table_schema = Utils.unqualify_table_schema(table_name)
|
167
|
+
table_name = Utils.unqualify_table_name(table_name)
|
168
|
+
sql = %{
|
169
|
+
SELECT DISTINCT
|
170
|
+
#{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
|
171
|
+
#{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
|
172
|
+
columns.DATA_TYPE AS type,
|
173
|
+
columns.COLUMN_DEFAULT AS default_value,
|
174
|
+
columns.NUMERIC_SCALE AS numeric_scale,
|
175
|
+
columns.NUMERIC_PRECISION AS numeric_precision,
|
176
|
+
columns.ordinal_position,
|
177
|
+
CASE
|
178
|
+
WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
|
179
|
+
ELSE COL_LENGTH('#{db_name_with_period}'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
|
180
|
+
END AS [length],
|
181
|
+
CASE
|
182
|
+
WHEN columns.IS_NULLABLE = 'YES' THEN 1
|
183
|
+
ELSE NULL
|
184
|
+
END AS [is_nullable],
|
185
|
+
CASE
|
186
|
+
WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
|
187
|
+
ELSE NULL
|
188
|
+
END AS [is_primary],
|
189
|
+
c.is_identity AS [is_identity]
|
190
|
+
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
|
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
|
210
|
+
WHERE columns.TABLE_NAME = @0
|
211
|
+
AND columns.TABLE_SCHEMA = #{table_schema.blank? ? "schema_name()" : "@1"}
|
212
|
+
ORDER BY columns.ordinal_position
|
213
|
+
}.gsub(/[ \t\r\n]+/,' ')
|
214
|
+
binds = [['table_name', table_name]]
|
215
|
+
binds << ['table_schema',table_schema] unless table_schema.blank?
|
216
|
+
results = do_exec_query(sql, 'SCHEMA', binds)
|
217
|
+
results.collect do |ci|
|
218
|
+
ci = ci.symbolize_keys
|
219
|
+
ci[:type] = case ci[:type]
|
220
|
+
when /^bit|image|text|ntext|datetime$/
|
221
|
+
ci[:type]
|
222
|
+
when /^numeric|decimal$/i
|
223
|
+
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
|
224
|
+
when /^float|real$/i
|
225
|
+
"#{ci[:type]}(#{ci[:numeric_precision]})"
|
226
|
+
when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
|
227
|
+
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
|
228
|
+
else
|
229
|
+
ci[:type]
|
230
|
+
end
|
231
|
+
if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
|
232
|
+
real_table_name = table_name_or_views_table_name(table_name)
|
233
|
+
real_column_name = views_real_column_name(table_name,ci[:name])
|
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}'"
|
235
|
+
ci[:default_value] = select_value col_default_sql, 'SCHEMA'
|
236
|
+
end
|
237
|
+
ci[:default_value] = case ci[:default_value]
|
238
|
+
when nil, '(null)', '(NULL)'
|
239
|
+
nil
|
240
|
+
when /\A\((\w+\(\))\)\Z/
|
241
|
+
ci[:default_function] = $1
|
242
|
+
nil
|
243
|
+
else
|
244
|
+
match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
|
245
|
+
match_data ? match_data[1] : nil
|
246
|
+
end
|
247
|
+
ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
|
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)
|
250
|
+
ci
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def remove_check_constraints(table_name, 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'
|
256
|
+
constraints.each do |constraint|
|
257
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def remove_default_constraint(table_name, column_name)
|
262
|
+
# If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
|
263
|
+
execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
|
264
|
+
row['constraint_type'] == "DEFAULT on column #{column_name}"
|
265
|
+
end.each do |row|
|
266
|
+
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def remove_indexes(table_name, column_name)
|
271
|
+
indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
|
272
|
+
remove_index(table_name, {:name => index.name})
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# === SQLServer Specific (Misc Helpers) ========================= #
|
277
|
+
|
278
|
+
def get_table_name(sql)
|
279
|
+
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
|
280
|
+
$2 || $3
|
281
|
+
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
|
282
|
+
$1
|
283
|
+
else
|
284
|
+
nil
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def default_constraint_name(table_name, column_name)
|
289
|
+
"DF_#{table_name}_#{column_name}"
|
290
|
+
end
|
291
|
+
|
292
|
+
def detect_column_for!(table_name, column_name)
|
293
|
+
unless column = schema_cache.columns[table_name].detect { |c| c.name == column_name.to_s }
|
294
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
295
|
+
end
|
296
|
+
column
|
297
|
+
end
|
298
|
+
|
299
|
+
def lowercase_schema_reflection_sql(node)
|
300
|
+
lowercase_schema_reflection ? "LOWER(#{node})" : node
|
301
|
+
end
|
302
|
+
|
303
|
+
# === SQLServer Specific (View Reflection) ====================== #
|
304
|
+
|
305
|
+
def view_table_name(table_name)
|
306
|
+
view_info = schema_cache.view_information(table_name)
|
307
|
+
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
|
308
|
+
end
|
309
|
+
|
310
|
+
def view_information(table_name)
|
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
|
322
|
+
end
|
323
|
+
end
|
324
|
+
view_info
|
325
|
+
end
|
326
|
+
|
327
|
+
def table_name_or_views_table_name(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
|
330
|
+
end
|
331
|
+
|
332
|
+
def views_real_column_name(table_name,column_name)
|
333
|
+
view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
|
334
|
+
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
|
335
|
+
match_data ? match_data[1] : column_name
|
336
|
+
end
|
337
|
+
|
338
|
+
# === SQLServer Specific (Identity Inserts) ===================== #
|
339
|
+
|
340
|
+
def query_requires_identity_insert?(sql)
|
341
|
+
if insert_sql?(sql)
|
342
|
+
table_name = get_table_name(sql)
|
343
|
+
id_column = identity_column(table_name)
|
344
|
+
id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
|
345
|
+
else
|
346
|
+
false
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def insert_sql?(sql)
|
351
|
+
!(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
|
352
|
+
end
|
353
|
+
|
354
|
+
def with_identity_insert_enabled(table_name)
|
355
|
+
table_name = quote_table_name(table_name_or_views_table_name(table_name))
|
356
|
+
set_identity_insert(table_name, true)
|
357
|
+
yield
|
358
|
+
ensure
|
359
|
+
set_identity_insert(table_name, false)
|
360
|
+
end
|
361
|
+
|
362
|
+
def set_identity_insert(table_name, enable = true)
|
363
|
+
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
364
|
+
do_execute sql, 'SCHEMA'
|
365
|
+
rescue Exception => e
|
366
|
+
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
|
367
|
+
end
|
368
|
+
|
369
|
+
def identity_column(table_name)
|
370
|
+
schema_cache.columns[table_name].detect(&:is_identity?)
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
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
|
+
|