sic-activerecord-sqlserver-adapter 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +603 -0
  3. data/MIT-LICENSE +20 -0
  4. data/VERSION +1 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +42 -0
  6. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
  7. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
  8. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
  9. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
  10. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  11. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
  12. data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
  13. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +109 -0
  14. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
  15. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +395 -0
  16. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
  17. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
  18. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
  19. data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
  20. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +545 -0
  21. data/lib/active_record/sqlserver_test_case.rb +17 -0
  22. data/lib/activerecord-sqlserver-adapter.rb +1 -0
  23. data/lib/arel/select_manager_sqlserver.rb +64 -0
  24. data/lib/arel/visitors/sqlserver.rb +326 -0
  25. metadata +98 -0
@@ -0,0 +1,36 @@
1
+ module ActiveRecord
2
+
3
+ class LostConnection < WrappedDatabaseException
4
+ end
5
+
6
+ class DeadlockVictim < WrappedDatabaseException
7
+ end
8
+
9
+ module ConnectionAdapters
10
+ module Sqlserver
11
+ module Errors
12
+
13
+ LOST_CONNECTION_EXCEPTIONS = {
14
+ :dblib => ['TinyTds::Error'],
15
+ :odbc => ['ODBC::Error']
16
+ }.freeze
17
+
18
+ LOST_CONNECTION_MESSAGES = {
19
+ :dblib => [/closed connection/, /dead or not enabled/, /server failed/i],
20
+ :odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i]
21
+ }.freeze
22
+
23
+
24
+ def lost_connection_exceptions
25
+ exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
26
+ @lost_connection_exceptions ||= exceptions ? exceptions.map{ |e| e.constantize rescue nil }.compact : []
27
+ end
28
+
29
+ def lost_connection_messages
30
+ LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,109 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module Quoting
5
+
6
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
7
+ QUOTED_STRING_PREFIX = 'N'
8
+
9
+ def quote(value, column = nil)
10
+ case value
11
+ when String, ActiveSupport::Multibyte::Chars
12
+ if column && column.type == :integer && value.blank?
13
+ value.to_i.to_s
14
+ elsif column && column.type == :binary
15
+ column.class.string_to_binary(value)
16
+ elsif value.is_utf8? || (column && column.type == :string)
17
+ "#{quoted_string_prefix}'#{quote_string(value)}'"
18
+ else
19
+ super
20
+ end
21
+ when Date, Time
22
+ if column && column.sql_type == 'datetime'
23
+ "'#{quoted_datetime(value)}'"
24
+ elsif column && (column.sql_type == 'datetimeoffset' || column.sql_type == 'time')
25
+ "'#{quoted_full_iso8601(value)}'"
26
+ else
27
+ super
28
+ end
29
+ when nil
30
+ column.respond_to?(:sql_type) && column.sql_type == 'timestamp' ? 'DEFAULT' : super
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def quoted_string_prefix
37
+ QUOTED_STRING_PREFIX
38
+ end
39
+
40
+ def quote_string(string)
41
+ string.to_s.gsub(/\'/, "''")
42
+ end
43
+
44
+ def quote_column_name(name)
45
+ schema_cache.quote_name(name)
46
+ end
47
+
48
+ def quote_table_name(name)
49
+ quote_column_name(name)
50
+ end
51
+
52
+ def substitute_at(column, index)
53
+ if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
54
+ nil
55
+ else
56
+ Arel.sql "@#{index}"
57
+ end
58
+ end
59
+
60
+ def quoted_true
61
+ QUOTED_TRUE
62
+ end
63
+
64
+ def quoted_false
65
+ QUOTED_FALSE
66
+ end
67
+
68
+ def quoted_datetime(value)
69
+ if value.acts_like?(:time)
70
+ time_zone_qualified_value = quoted_value_acts_like_time_filter(value)
71
+ if value.is_a?(Date)
72
+ time_zone_qualified_value.iso8601(3).to(18)
73
+ else
74
+ time_zone_qualified_value.iso8601(3).to(22)
75
+ end
76
+ else
77
+ quoted_date(value)
78
+ end
79
+ end
80
+
81
+ def quoted_full_iso8601(value)
82
+ if value.acts_like?(:time)
83
+ value.is_a?(Date) ? quoted_value_acts_like_time_filter(value).to_time.xmlschema.to(18) : quoted_value_acts_like_time_filter(value).iso8601(7).to(22)
84
+ else
85
+ quoted_date(value)
86
+ end
87
+ end
88
+
89
+ def quoted_date(value)
90
+ if value.acts_like?(:time) && value.respond_to?(:usec)
91
+ "#{super}.#{sprintf("%03d",value.usec/1000)}"
92
+ elsif value.acts_like?(:date)
93
+ value.to_s(:_sqlserver_dateformat)
94
+ else
95
+ super
96
+ end
97
+ end
98
+
99
+ protected
100
+
101
+ def quoted_value_acts_like_time_filter(value)
102
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
103
+ value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
104
+ end
105
+
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,85 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
5
+
6
+ attr_reader :view_information
7
+
8
+ def initialize(conn)
9
+ super
10
+ @table_names = nil
11
+ @view_names = nil
12
+ @view_information = {}
13
+ @quoted_names = {}
14
+ end
15
+
16
+ # Superclass Overrides
17
+
18
+ def table_exists?(table_name)
19
+ return false if table_name.blank?
20
+ key = table_name_key(table_name)
21
+ return @tables[key] if @tables.key? key
22
+ @tables[key] = connection.table_exists?(table_name)
23
+ end
24
+
25
+ def clear!
26
+ super
27
+ @table_names = nil
28
+ @view_names = nil
29
+ @view_information.clear
30
+ @quoted_names.clear
31
+ end
32
+
33
+ def clear_table_cache!(table_name)
34
+ key = table_name_key(table_name)
35
+ super(key)
36
+ super(table_name)
37
+ # SQL Server Specific
38
+ if @table_names
39
+ @table_names.delete key
40
+ @table_names.delete table_name
41
+ end
42
+ if @view_names
43
+ @view_names.delete key
44
+ @view_names.delete table_name
45
+ end
46
+ @view_information.delete key
47
+ end
48
+
49
+ # SQL Server Specific
50
+
51
+ def table_names
52
+ @table_names ||= connection.tables
53
+ end
54
+
55
+ def view_names
56
+ @view_names ||= connection.views
57
+ end
58
+
59
+ def view_exists?(table_name)
60
+ table_exists?(table_name)
61
+ end
62
+
63
+ def view_information(table_name)
64
+ key = table_name_key(table_name)
65
+ return @view_information[key] if @view_information.key? key
66
+ @view_information[key] = connection.send(:view_information, table_name)
67
+ end
68
+
69
+ def quote_name(name)
70
+ return @quoted_names[name] if @quoted_names.key? name
71
+ @quoted_names[name] = name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
72
+ end
73
+
74
+
75
+ private
76
+
77
+ def table_name_key(table_name)
78
+ Utils.unqualify_table_name(table_name)
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -0,0 +1,395 @@
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
+ # like postgres, sqlserver requires the ORDER BY columns in the select list for distinct queries, and
48
+ # requires that the ORDER BY include the distinct column.
49
+ # this method is idental to the postgres method
50
+ def columns_for_distinct(columns, orders) #:nodoc:
51
+ order_columns = orders.map{ |s|
52
+ # Convert Arel node to string
53
+ s = s.to_sql unless s.is_a?(String)
54
+ # Remove any ASC/DESC modifiers
55
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
56
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
57
+
58
+ [super, *order_columns].join(', ')
59
+ end
60
+
61
+ def rename_table(table_name, new_name)
62
+ do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
63
+ rename_table_indexes(table_name, new_name)
64
+ end
65
+
66
+ def remove_column(table_name, column_name, type = nil)
67
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if (column_name.is_a? Array)
68
+ remove_check_constraints(table_name, column_name)
69
+ remove_default_constraint(table_name, column_name)
70
+ remove_indexes(table_name, column_name)
71
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
72
+ end
73
+
74
+ def change_column(table_name, column_name, type, options = {})
75
+ sql_commands = []
76
+ indexes = []
77
+ column_object = schema_cache.columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
78
+
79
+ if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
80
+ remove_default_constraint(table_name,column_name)
81
+ indexes = indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }
82
+ remove_indexes(table_name, column_name)
83
+ end
84
+ 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?
85
+ 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])}"
86
+ sql_commands[-1] << " NOT NULL" if !options[:null].nil? && options[:null] == false
87
+ if options_include_default?(options)
88
+ 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
+ end
90
+
91
+ #Add any removed indexes back
92
+ indexes.each do |index|
93
+ sql_commands << "CREATE INDEX #{quote_table_name(index.name)} ON #{quote_table_name(table_name)} (#{index.columns.collect {|c|quote_column_name(c)}.join(', ')})"
94
+ end
95
+ sql_commands.each { |c| do_execute(c) }
96
+ end
97
+
98
+ def change_column_default(table_name, column_name, default)
99
+ remove_default_constraint(table_name, column_name)
100
+ 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)}"
101
+ end
102
+
103
+ def rename_column(table_name, column_name, new_column_name)
104
+ schema_cache.clear_table_cache!(table_name)
105
+ detect_column_for! table_name, column_name
106
+ do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
107
+ rename_column_indexes(table_name, column_name, new_column_name)
108
+ schema_cache.clear_table_cache!(table_name)
109
+ end
110
+
111
+ def rename_index(table_name, old_name, new_name)
112
+ execute "EXEC sp_rename N'#{table_name}.#{old_name}', N'#{new_name}', N'INDEX'"
113
+ end
114
+
115
+ def remove_index!(table_name, index_name)
116
+ do_execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
117
+ end
118
+
119
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
120
+ type_limitable = ['string','integer','float','char','nchar','varchar','nvarchar'].include?(type.to_s)
121
+ limit = nil unless type_limitable
122
+ case type.to_s
123
+ when 'integer'
124
+ case limit
125
+ when 1..2 then 'smallint'
126
+ when 3..4, nil then 'integer'
127
+ when 5..8 then 'bigint'
128
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
129
+ end
130
+ else
131
+ super
132
+ end
133
+ end
134
+
135
+ def change_column_null(table_name, column_name, allow_null, default = nil)
136
+ column = detect_column_for! table_name, column_name
137
+ if !allow_null.nil? && allow_null == false && !default.nil?
138
+ do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
139
+ end
140
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
141
+ sql << " NOT NULL" if !allow_null.nil? && allow_null == false
142
+ do_execute sql
143
+ end
144
+
145
+ # === SQLServer Specific ======================================== #
146
+
147
+ def views
148
+ tables('VIEW')
149
+ end
150
+
151
+
152
+ protected
153
+
154
+ # === SQLServer Specific ======================================== #
155
+
156
+ def initialize_native_database_types
157
+ {
158
+ :primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
159
+ :string => { :name => native_string_database_type, :limit => 255 },
160
+ :text => { :name => native_text_database_type },
161
+ :integer => { :name => "int", :limit => 4 },
162
+ :float => { :name => "float", :limit => 8 },
163
+ :decimal => { :name => "decimal" },
164
+ :datetime => { :name => "datetime" },
165
+ :timestamp => { :name => "datetime" },
166
+ :time => { :name => native_time_database_type },
167
+ :date => { :name => native_date_database_type },
168
+ :binary => { :name => native_binary_database_type },
169
+ :boolean => { :name => "bit"},
170
+ # These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
171
+ :char => { :name => 'char' },
172
+ :varchar_max => { :name => 'varchar(max)' },
173
+ :nchar => { :name => "nchar" },
174
+ :nvarchar => { :name => "nvarchar", :limit => 255 },
175
+ :nvarchar_max => { :name => "nvarchar(max)" },
176
+ :ntext => { :name => "ntext" },
177
+ :ss_timestamp => { :name => 'timestamp' }
178
+ }
179
+ end
180
+
181
+ def column_definitions(table_name)
182
+ db_name = Utils.unqualify_db_name(table_name)
183
+ db_name_with_period = "#{db_name}." if db_name
184
+ table_schema = Utils.unqualify_table_schema(table_name)
185
+ table_name = Utils.unqualify_table_name(table_name)
186
+ sql = %{
187
+ SELECT DISTINCT
188
+ #{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
189
+ #{lowercase_schema_reflection_sql('columns.COLUMN_NAME')} AS name,
190
+ columns.DATA_TYPE AS type,
191
+ columns.COLUMN_DEFAULT AS default_value,
192
+ columns.NUMERIC_SCALE AS numeric_scale,
193
+ columns.NUMERIC_PRECISION AS numeric_precision,
194
+ columns.ordinal_position,
195
+ CASE
196
+ WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
197
+ ELSE COL_LENGTH('#{db_name_with_period}'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
198
+ END AS [length],
199
+ CASE
200
+ WHEN columns.IS_NULLABLE = 'YES' THEN 1
201
+ ELSE NULL
202
+ END AS [is_nullable],
203
+ CASE
204
+ WHEN KCU.COLUMN_NAME IS NOT NULL AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY' THEN 1
205
+ ELSE NULL
206
+ END AS [is_primary],
207
+ c.is_identity AS [is_identity]
208
+ FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
209
+ LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
210
+ ON TC.TABLE_NAME = columns.TABLE_NAME
211
+ AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
212
+ LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
213
+ ON KCU.COLUMN_NAME = columns.COLUMN_NAME
214
+ AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
215
+ AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
216
+ AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
217
+ INNER JOIN #{db_name}.sys.schemas AS s
218
+ ON s.name = columns.TABLE_SCHEMA
219
+ AND s.schema_id = s.schema_id
220
+ INNER JOIN #{db_name}.sys.objects AS o
221
+ ON s.schema_id = o.schema_id
222
+ AND o.is_ms_shipped = 0
223
+ AND o.type IN ('U', 'V')
224
+ AND o.name = columns.TABLE_NAME
225
+ INNER JOIN #{db_name}.sys.columns AS c
226
+ ON o.object_id = c.object_id
227
+ AND c.name = columns.COLUMN_NAME
228
+ WHERE columns.TABLE_NAME = @0
229
+ AND columns.TABLE_SCHEMA = #{table_schema.blank? ? "schema_name()" : "@1"}
230
+ ORDER BY columns.ordinal_position
231
+ }.gsub(/[ \t\r\n]+/,' ')
232
+ binds = [['table_name', table_name]]
233
+ binds << ['table_schema',table_schema] unless table_schema.blank?
234
+ results = do_exec_query(sql, 'SCHEMA', binds)
235
+ results.collect do |ci|
236
+ ci = ci.symbolize_keys
237
+ ci[:type] = case ci[:type]
238
+ when /^bit|image|text|ntext|datetime$/
239
+ ci[:type]
240
+ when /^numeric|decimal$/i
241
+ "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
242
+ when /^float|real$/i
243
+ "#{ci[:type]}(#{ci[:numeric_precision]})"
244
+ when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
245
+ ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
246
+ else
247
+ ci[:type]
248
+ end
249
+ if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
250
+ real_table_name = table_name_or_views_table_name(table_name)
251
+ real_column_name = views_real_column_name(table_name,ci[:name])
252
+ 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}'"
253
+ ci[:default_value] = select_value col_default_sql, 'SCHEMA'
254
+ end
255
+ ci[:default_value] = case ci[:default_value]
256
+ when nil, '(null)', '(NULL)'
257
+ nil
258
+ when /\A\((\w+\(\))\)\Z/
259
+ ci[:default_function] = $1
260
+ nil
261
+ else
262
+ match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
263
+ match_data ? match_data[1] : nil
264
+ end
265
+ ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
266
+ ci[:is_primary] = ci[:is_primary].to_i == 1
267
+ ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
268
+ ci
269
+ end
270
+ end
271
+
272
+ def remove_check_constraints(table_name, column_name)
273
+ 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'
274
+ constraints.each do |constraint|
275
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
276
+ end
277
+ end
278
+
279
+ def remove_default_constraint(table_name, column_name)
280
+ # If their are foreign keys in this table, we could still get back a 2D array, so flatten just in case.
281
+ execute_procedure(:sp_helpconstraint, table_name, 'nomsg').flatten.select do |row|
282
+ row['constraint_type'] == "DEFAULT on column #{column_name}"
283
+ end.each do |row|
284
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
285
+ end
286
+ end
287
+
288
+ def remove_indexes(table_name, column_name)
289
+ indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
290
+ remove_index(table_name, {:name => index.name})
291
+ end
292
+ end
293
+
294
+ # === SQLServer Specific (Misc Helpers) ========================= #
295
+
296
+ def get_table_name(sql)
297
+ if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
298
+ $3 || $4
299
+ elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
300
+ $1
301
+ else
302
+ nil
303
+ end
304
+ end
305
+
306
+ def default_constraint_name(table_name, column_name)
307
+ "DF_#{table_name}_#{column_name}"
308
+ end
309
+
310
+ def detect_column_for!(table_name, column_name)
311
+ unless column = schema_cache.columns(table_name).detect { |c| c.name == column_name.to_s }
312
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
313
+ end
314
+ column
315
+ end
316
+
317
+ def lowercase_schema_reflection_sql(node)
318
+ lowercase_schema_reflection ? "LOWER(#{node})" : node
319
+ end
320
+
321
+ # === SQLServer Specific (View Reflection) ====================== #
322
+
323
+ def view_table_name(table_name)
324
+ view_info = schema_cache.view_information(table_name)
325
+ view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
326
+ end
327
+
328
+ def view_information(table_name)
329
+ table_name = Utils.unqualify_table_name(table_name)
330
+ view_info = select_one "SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'", 'SCHEMA'
331
+ if view_info
332
+ view_info = view_info.with_indifferent_access
333
+ if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
334
+ view_info[:VIEW_DEFINITION] = begin
335
+ select_values("EXEC sp_helptext #{quote_table_name(table_name)}", 'SCHEMA').join
336
+ rescue
337
+ warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
338
+ nil
339
+ end
340
+ end
341
+ end
342
+ view_info
343
+ end
344
+
345
+ def table_name_or_views_table_name(table_name)
346
+ unquoted_table_name = Utils.unqualify_table_name(table_name)
347
+ schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
348
+ end
349
+
350
+ def views_real_column_name(table_name,column_name)
351
+ view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
352
+ match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
353
+ match_data ? match_data[1] : column_name
354
+ end
355
+
356
+ # === SQLServer Specific (Identity Inserts) ===================== #
357
+
358
+ def query_requires_identity_insert?(sql)
359
+
360
+ if insert_sql?(sql)
361
+ table_name = get_table_name(sql)
362
+ id_column = identity_column(table_name)
363
+ id_column && sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
364
+ else
365
+ false
366
+ end
367
+ end
368
+
369
+ def insert_sql?(sql)
370
+ !(sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)/i).nil?
371
+ end
372
+
373
+ def with_identity_insert_enabled(table_name)
374
+ table_name = quote_table_name(table_name_or_views_table_name(table_name))
375
+ set_identity_insert(table_name, true)
376
+ yield
377
+ ensure
378
+ set_identity_insert(table_name, false)
379
+ end
380
+
381
+ def set_identity_insert(table_name, enable = true)
382
+ sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
383
+ do_execute sql, 'SCHEMA'
384
+ rescue Exception => e
385
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
386
+ end
387
+
388
+ def identity_column(table_name)
389
+ schema_cache.columns(table_name).detect(&:is_identity?)
390
+ end
391
+
392
+ end
393
+ end
394
+ end
395
+ end