sic-activerecord-sqlserver-adapter 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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