activerecord-sqlserver-adapter 2.3.24 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +5 -108
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +33 -61
  4. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +57 -0
  5. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +57 -0
  6. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
  7. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +336 -0
  8. data/lib/active_record/connection_adapters/sqlserver/errors.rb +33 -0
  9. data/lib/active_record/connection_adapters/sqlserver/query_cache.rb +17 -0
  10. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +61 -0
  11. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +373 -0
  12. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +131 -1121
  13. data/lib/arel/engines/sql/compilers/sqlserver_compiler.rb +267 -0
  14. metadata +26 -76
  15. data/RUNNING_UNIT_TESTS +0 -31
  16. data/Rakefile +0 -60
  17. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/active_record.rb +0 -151
  18. data/lib/active_record/connection_adapters/sqlserver_adapter/core_ext/odbc.rb +0 -40
  19. data/test/cases/aaaa_create_tables_test_sqlserver.rb +0 -19
  20. data/test/cases/adapter_test_sqlserver.rb +0 -755
  21. data/test/cases/attribute_methods_test_sqlserver.rb +0 -33
  22. data/test/cases/basics_test_sqlserver.rb +0 -86
  23. data/test/cases/calculations_test_sqlserver.rb +0 -20
  24. data/test/cases/column_test_sqlserver.rb +0 -354
  25. data/test/cases/connection_test_sqlserver.rb +0 -148
  26. data/test/cases/eager_association_test_sqlserver.rb +0 -42
  27. data/test/cases/execute_procedure_test_sqlserver.rb +0 -35
  28. data/test/cases/inheritance_test_sqlserver.rb +0 -28
  29. data/test/cases/method_scoping_test_sqlserver.rb +0 -28
  30. data/test/cases/migration_test_sqlserver.rb +0 -108
  31. data/test/cases/named_scope_test_sqlserver.rb +0 -21
  32. data/test/cases/offset_and_limit_test_sqlserver.rb +0 -108
  33. data/test/cases/pessimistic_locking_test_sqlserver.rb +0 -125
  34. data/test/cases/query_cache_test_sqlserver.rb +0 -24
  35. data/test/cases/schema_dumper_test_sqlserver.rb +0 -72
  36. data/test/cases/specific_schema_test_sqlserver.rb +0 -154
  37. data/test/cases/sqlserver_helper.rb +0 -140
  38. data/test/cases/table_name_test_sqlserver.rb +0 -38
  39. data/test/cases/transaction_test_sqlserver.rb +0 -93
  40. data/test/cases/unicode_test_sqlserver.rb +0 -54
  41. data/test/cases/validations_test_sqlserver.rb +0 -18
  42. data/test/connections/native_sqlserver/connection.rb +0 -26
  43. data/test/connections/native_sqlserver_odbc/connection.rb +0 -28
  44. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +0 -11
  45. data/test/schema/sqlserver_specific_schema.rb +0 -113
@@ -0,0 +1,373 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sqlserver
4
+ module SchemaStatements
5
+
6
+ def native_database_types
7
+ {
8
+ :primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
9
+ :string => { :name => native_string_database_type, :limit => 255 },
10
+ :text => { :name => native_text_database_type },
11
+ :integer => { :name => "int", :limit => 4 },
12
+ :float => { :name => "float", :limit => 8 },
13
+ :decimal => { :name => "decimal" },
14
+ :datetime => { :name => "datetime" },
15
+ :timestamp => { :name => "datetime" },
16
+ :time => { :name => native_time_database_type },
17
+ :date => { :name => native_date_database_type },
18
+ :binary => { :name => native_binary_database_type },
19
+ :boolean => { :name => "bit"},
20
+ # These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
21
+ :char => { :name => 'char' },
22
+ :varchar_max => { :name => 'varchar(max)' },
23
+ :nchar => { :name => "nchar" },
24
+ :nvarchar => { :name => "nvarchar", :limit => 255 },
25
+ :nvarchar_max => { :name => "nvarchar(max)" },
26
+ :ntext => { :name => "ntext" }
27
+ }
28
+ end
29
+
30
+ def tables(name = nil)
31
+ info_schema_query do
32
+ select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
33
+ end
34
+ end
35
+
36
+ def table_exists?(table_name)
37
+ unquoted_table_name = unqualify_table_name(table_name)
38
+ super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
39
+ end
40
+
41
+ def indexes(table_name, name = nil)
42
+ unquoted_table_name = unqualify_table_name(table_name)
43
+ select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name).inject([]) do |indexes,index|
44
+ index = index.with_indifferent_access
45
+ if index[:index_description] =~ /primary key/
46
+ indexes
47
+ else
48
+ name = index[:index_name]
49
+ unique = index[:index_description] =~ /unique/
50
+ columns = index[:index_keys].split(',').map do |column|
51
+ column.strip!
52
+ column.gsub! '(-)', '' if column.ends_with?('(-)')
53
+ column
54
+ end
55
+ indexes << IndexDefinition.new(table_name, name, unique, columns)
56
+ end
57
+ end
58
+ end
59
+
60
+ def columns(table_name, name = nil)
61
+ return [] if table_name.blank?
62
+ cache_key = unqualify_table_name(table_name)
63
+ @sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
64
+ sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
65
+ SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
66
+ end
67
+ end
68
+
69
+ def create_table(table_name, options = {})
70
+ super
71
+ remove_sqlserver_columns_cache_for(table_name)
72
+ end
73
+
74
+ def rename_table(table_name, new_name)
75
+ do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
76
+ end
77
+
78
+ def drop_table(table_name, options = {})
79
+ super
80
+ remove_sqlserver_columns_cache_for(table_name)
81
+ end
82
+
83
+ def add_column(table_name, column_name, type, options = {})
84
+ super
85
+ remove_sqlserver_columns_cache_for(table_name)
86
+ end
87
+
88
+ def remove_column(table_name, *column_names)
89
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
90
+ column_names.flatten.each do |column_name|
91
+ remove_check_constraints(table_name, column_name)
92
+ remove_default_constraint(table_name, column_name)
93
+ remove_indexes(table_name, column_name)
94
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
95
+ end
96
+ remove_sqlserver_columns_cache_for(table_name)
97
+ end
98
+
99
+ def change_column(table_name, column_name, type, options = {})
100
+ sql_commands = []
101
+ column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
102
+ 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])}"
103
+ change_column_sql << " NOT NULL" if options[:null] == false
104
+ sql_commands << change_column_sql
105
+ if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
106
+ remove_default_constraint(table_name,column_name)
107
+ end
108
+ if options_include_default?(options)
109
+ remove_sqlserver_columns_cache_for(table_name)
110
+ 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)}"
111
+ end
112
+ sql_commands.each { |c| do_execute(c) }
113
+ remove_sqlserver_columns_cache_for(table_name)
114
+ end
115
+
116
+ def change_column_default(table_name, column_name, default)
117
+ remove_default_constraint(table_name, column_name)
118
+ 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)}"
119
+ remove_sqlserver_columns_cache_for(table_name)
120
+ end
121
+
122
+ def rename_column(table_name, column_name, new_column_name)
123
+ detect_column_for!(table_name,column_name)
124
+ do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
125
+ remove_sqlserver_columns_cache_for(table_name)
126
+ end
127
+
128
+ def remove_index!(table_name, index_name)
129
+ do_execute "DROP INDEX #{quote_table_name(table_name)}.#{quote_column_name(index_name)}"
130
+ end
131
+
132
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
133
+ type_limitable = ['string','integer','float','char','nchar','varchar','nvarchar'].include?(type.to_s)
134
+ limit = nil unless type_limitable
135
+ case type.to_s
136
+ when 'integer'
137
+ case limit
138
+ when 1..2 then 'smallint'
139
+ when 3..4, nil then 'integer'
140
+ when 5..8 then 'bigint'
141
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
142
+ end
143
+ else
144
+ super
145
+ end
146
+ end
147
+
148
+ def change_column_null(table_name, column_name, null, default = nil)
149
+ column = detect_column_for!(table_name,column_name)
150
+ unless null || default.nil?
151
+ do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
152
+ end
153
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
154
+ sql << " NOT NULL" unless null
155
+ do_execute sql
156
+ end
157
+
158
+ # === SQLServer Specific ======================================== #
159
+
160
+ def views(name = nil)
161
+ @sqlserver_views_cache ||=
162
+ info_schema_query { select_values("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')") }
163
+ end
164
+
165
+
166
+ protected
167
+
168
+ # === SQLServer Specific ======================================== #
169
+
170
+ def column_definitions(table_name)
171
+ db_name = unqualify_db_name(table_name)
172
+ db_name_with_period = "#{db_name}." if db_name
173
+ table_name = unqualify_table_name(table_name)
174
+ sql = %{
175
+ SELECT
176
+ columns.TABLE_NAME as table_name,
177
+ columns.COLUMN_NAME as name,
178
+ columns.DATA_TYPE as type,
179
+ columns.COLUMN_DEFAULT as default_value,
180
+ columns.NUMERIC_SCALE as numeric_scale,
181
+ columns.NUMERIC_PRECISION as numeric_precision,
182
+ CASE
183
+ WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
184
+ ELSE COL_LENGTH(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
185
+ END as length,
186
+ CASE
187
+ WHEN columns.IS_NULLABLE = 'YES' THEN 1
188
+ ELSE NULL
189
+ END as is_nullable,
190
+ CASE
191
+ WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
192
+ ELSE 1
193
+ END as is_identity
194
+ FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
195
+ WHERE columns.TABLE_NAME = '#{table_name}'
196
+ ORDER BY columns.ordinal_position
197
+ }.gsub(/[ \t\r\n]+/,' ')
198
+ results = info_schema_query { select(sql,nil) }
199
+ results.collect do |ci|
200
+ ci = ci.symbolize_keys
201
+ ci[:type] = case ci[:type]
202
+ when /^bit|image|text|ntext|datetime$/
203
+ ci[:type]
204
+ when /^numeric|decimal$/i
205
+ "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
206
+ when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
207
+ ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
208
+ else
209
+ ci[:type]
210
+ end
211
+ if ci[:default_value].nil? && views.include?(table_name)
212
+ real_table_name = table_name_or_views_table_name(table_name)
213
+ real_column_name = views_real_column_name(table_name,ci[:name])
214
+ 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}'"
215
+ ci[:default_value] = info_schema_query { select_value(col_default_sql) }
216
+ end
217
+ ci[:default_value] = case ci[:default_value]
218
+ when nil, '(null)', '(NULL)'
219
+ nil
220
+ when /\A\((\w+\(\))\)\Z/
221
+ ci[:default_function] = $1
222
+ nil
223
+ else
224
+ match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
225
+ match_data ? match_data[1] : nil
226
+ end
227
+ ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
228
+ ci
229
+ end
230
+ end
231
+
232
+ def remove_check_constraints(table_name, column_name)
233
+ 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)}'") }
234
+ constraints.each do |constraint|
235
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
236
+ end
237
+ end
238
+
239
+ def remove_default_constraint(table_name, column_name)
240
+ select_all("EXEC sp_helpconstraint '#{quote_string(table_name)}','nomsg'").select do |row|
241
+ row['constraint_type'] == "DEFAULT on column #{column_name}"
242
+ end.each do |row|
243
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
244
+ end
245
+ end
246
+
247
+ def remove_indexes(table_name, column_name)
248
+ indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
249
+ remove_index(table_name, {:name => index.name})
250
+ end
251
+ end
252
+
253
+ # === SQLServer Specific (Misc Helpers) ========================= #
254
+
255
+ def info_schema_query
256
+ log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
257
+ end
258
+
259
+ def unqualify_table_name(table_name)
260
+ table_name.to_s.split('.').last.tr('[]','')
261
+ end
262
+
263
+ def unqualify_db_name(table_name)
264
+ table_names = table_name.to_s.split('.')
265
+ table_names.length == 3 ? table_names.first.tr('[]','') : nil
266
+ end
267
+
268
+ def get_table_name(sql)
269
+ if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
270
+ $1 || $2
271
+ elsif sql =~ /from\s+([^\(\s]+)\s*/i
272
+ $1
273
+ else
274
+ nil
275
+ end
276
+ end
277
+
278
+ def default_constraint_name(table_name, column_name)
279
+ "DF_#{table_name}_#{column_name}"
280
+ end
281
+
282
+ def detect_column_for!(table_name, column_name)
283
+ unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
284
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
285
+ end
286
+ column
287
+ end
288
+
289
+ # === SQLServer Specific (View Reflection) ====================== #
290
+
291
+ def view_table_name(table_name)
292
+ view_info = view_information(table_name)
293
+ view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
294
+ end
295
+
296
+ def view_information(table_name)
297
+ table_name = unqualify_table_name(table_name)
298
+ @sqlserver_view_information_cache[table_name] ||= begin
299
+ view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
300
+ if view_info
301
+ view_info = view_info.with_indifferent_access
302
+ if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
303
+ view_info[:VIEW_DEFINITION] = info_schema_query { select_values("EXEC sp_helptext #{table_name}").join }
304
+ end
305
+ end
306
+ view_info
307
+ end
308
+ end
309
+
310
+ def table_name_or_views_table_name(table_name)
311
+ unquoted_table_name = unqualify_table_name(table_name)
312
+ views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
313
+ end
314
+
315
+ def views_real_column_name(table_name,column_name)
316
+ view_definition = view_information(table_name)[:VIEW_DEFINITION]
317
+ match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
318
+ match_data ? match_data[1] : column_name
319
+ end
320
+
321
+ # === SQLServer Specific (Column/View Caches) =================== #
322
+
323
+ def remove_sqlserver_columns_cache_for(table_name)
324
+ cache_key = unqualify_table_name(table_name)
325
+ @sqlserver_columns_cache[cache_key] = nil
326
+ initialize_sqlserver_caches(false)
327
+ end
328
+
329
+ def initialize_sqlserver_caches(reset_columns=true)
330
+ @sqlserver_columns_cache = {} if reset_columns
331
+ @sqlserver_views_cache = nil
332
+ @sqlserver_view_information_cache = {}
333
+ end
334
+
335
+ # === SQLServer Specific (Identity Inserts) ===================== #
336
+
337
+ def query_requires_identity_insert?(sql)
338
+ if insert_sql?(sql)
339
+ table_name = get_table_name(sql)
340
+ id_column = identity_column(table_name)
341
+ id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
342
+ else
343
+ false
344
+ end
345
+ end
346
+
347
+ def insert_sql?(sql)
348
+ !(sql =~ /^\s*INSERT/i).nil?
349
+ end
350
+
351
+ def with_identity_insert_enabled(table_name)
352
+ table_name = quote_table_name(table_name_or_views_table_name(table_name))
353
+ set_identity_insert(table_name, true)
354
+ yield
355
+ ensure
356
+ set_identity_insert(table_name, false)
357
+ end
358
+
359
+ def set_identity_insert(table_name, enable = true)
360
+ sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
361
+ do_execute(sql,'IDENTITY_INSERT')
362
+ rescue Exception => e
363
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
364
+ end
365
+
366
+ def identity_column(table_name)
367
+ columns(table_name).detect(&:is_identity?)
368
+ end
369
+
370
+ end
371
+ end
372
+ end
373
+ end
@@ -1,6 +1,14 @@
1
1
  require 'active_record'
2
2
  require 'active_record/connection_adapters/abstract_adapter'
3
- require 'active_record/connection_adapters/sqlserver_adapter/core_ext/active_record'
3
+ require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
4
+ require 'active_record/connection_adapters/sqlserver/database_limits'
5
+ require 'active_record/connection_adapters/sqlserver/database_statements'
6
+ require 'active_record/connection_adapters/sqlserver/errors'
7
+ require 'active_record/connection_adapters/sqlserver/query_cache'
8
+ require 'active_record/connection_adapters/sqlserver/schema_statements'
9
+ require 'active_record/connection_adapters/sqlserver/quoting'
10
+ require 'active_support/core_ext/kernel/requires'
11
+ require 'active_support/core_ext/string'
4
12
  require 'base64'
5
13
 
6
14
  module ActiveRecord
@@ -9,22 +17,25 @@ module ActiveRecord
9
17
 
10
18
  def self.sqlserver_connection(config) #:nodoc:
11
19
  config = config.dup.symbolize_keys!
12
- config.reverse_merge! :mode => :dblib, :host => 'localhost', :username => 'sa', :password => ''
20
+ config.reverse_merge! :mode => :odbc, :host => 'localhost', :username => 'sa', :password => ''
13
21
  mode = config[:mode].to_s.downcase.underscore.to_sym
14
22
  case mode
15
- when :dblib
16
- require_library_or_gem 'tiny_tds'
17
- warn("TinyTds v0.4.3 or higher required. Using #{TinyTds::VERSION}") unless TinyTds::Client.instance_methods.map(&:to_s).include?("active?")
18
23
  when :odbc
19
- require_library_or_gem 'odbc' unless defined?(ODBC)
20
- require 'active_record/connection_adapters/sqlserver_adapter/core_ext/odbc'
21
24
  raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
25
+ if RUBY_VERSION < '1.9'
26
+ require_library_or_gem 'odbc'
27
+ else
28
+ begin
29
+ # TODO: [ODBC] Change this to 'odbc_utf8'
30
+ require_library_or_gem 'odbc'
31
+ rescue LoadError
32
+ require_library_or_gem 'odbc'
33
+ end
34
+ end unless ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].any? { |odbc_ns| odbc_ns.constantize rescue nil }
35
+ require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
22
36
  when :adonet
23
37
  require 'System.Data'
24
38
  raise ArgumentError, 'Missing :database configuration.' unless config.has_key?(:database)
25
- when :ado
26
- raise NotImplementedError, 'Please use version 2.3.1 of the adapter for ADO connections. Future versions support ADO.NET.'
27
- raise ArgumentError, 'Missing :database configuration.' unless config.has_key?(:database)
28
39
  else
29
40
  raise ArgumentError, "Unknown connection mode in #{config.inspect}."
30
41
  end
@@ -54,26 +65,44 @@ module ActiveRecord
54
65
 
55
66
  class << self
56
67
 
68
+ def string_to_utf8_encoding(value)
69
+ value.force_encoding('UTF-8') rescue value
70
+ end
71
+
57
72
  def string_to_binary(value)
73
+ value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
58
74
  "0x#{value.unpack("H*")[0]}"
59
75
  end
60
76
 
61
77
  def binary_to_string(value)
78
+ value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding)
62
79
  value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
63
80
  end
64
81
 
65
82
  end
66
83
 
67
- def is_identity?
68
- @sqlserver_options[:is_identity]
84
+ def type_cast(value)
85
+ if value && type == :string && is_utf8?
86
+ self.class.string_to_utf8_encoding(value)
87
+ else
88
+ super
89
+ end
90
+ end
91
+
92
+ def type_cast_code(var_name)
93
+ if type == :string && is_utf8?
94
+ "#{self.class.name}.string_to_utf8_encoding(#{var_name})"
95
+ else
96
+ super
97
+ end
69
98
  end
70
99
 
71
- def is_special?
72
- @sql_type =~ /^text|ntext|image$/
100
+ def is_identity?
101
+ @sqlserver_options[:is_identity]
73
102
  end
74
103
 
75
104
  def is_utf8?
76
- @sql_type =~ /nvarchar|ntext|nchar/i
105
+ sql_type =~ /nvarchar|ntext|nchar/i
77
106
  end
78
107
 
79
108
  def default_function
@@ -124,7 +153,6 @@ module ActiveRecord
124
153
  when /uniqueidentifier/i then :string
125
154
  when /datetime/i then simplified_datetime
126
155
  when /varchar\(max\)/ then :text
127
- when /timestamp/ then :binary
128
156
  else super
129
157
  end
130
158
  end
@@ -141,55 +169,38 @@ module ActiveRecord
141
169
  end
142
170
  end
143
171
 
144
- end #SQLServerColumn
172
+ end #class SQLServerColumn
145
173
 
146
-
147
174
  class SQLServerAdapter < AbstractAdapter
148
175
 
176
+ include Sqlserver::Quoting
177
+ include Sqlserver::DatabaseStatements
178
+ include Sqlserver::SchemaStatements
179
+ include Sqlserver::DatabaseLimits
180
+ include Sqlserver::QueryCache
181
+ include Sqlserver::Errors
182
+
149
183
  ADAPTER_NAME = 'SQLServer'.freeze
150
- VERSION = '2.3.24'.freeze
184
+ VERSION = '3.0.0'.freeze
151
185
  DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
152
- SUPPORTED_VERSIONS = [2000,2005,2008].freeze
153
- LIMITABLE_TYPES = ['string','integer','float','char','nchar','varchar','nvarchar'].to_set.freeze
154
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
155
- LOST_CONNECTION_EXCEPTIONS = {
156
- :dblib => ['TinyTds::Error'],
157
- :odbc => ['ODBC::Error'],
158
- :adonet => ['TypeError','System::Data::SqlClient::SqlException']
159
- }
160
- LOST_CONNECTION_MESSAGES = {
161
- :dblib => [/closed connection/, /dead or not enabled/, /server failed/i],
162
- :odbc => [/link failure/, /server failed/, /connection was already closed/, /invalid handle/i],
163
- :adonet => [/current state is closed/, /network-related/]
164
- }
186
+ SUPPORTED_VERSIONS = [2005,2008].freeze
165
187
 
166
- attr_reader :database_version, :database_year,
167
- :connection_supports_native_types
168
188
  cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
169
- :log_info_schema_queries, :enable_default_unicode_types, :auto_connect
170
-
171
- class << self
172
-
173
- def type_limitable?(type)
174
- LIMITABLE_TYPES.include?(type.to_s)
175
- end
176
-
177
- end
189
+ :log_info_schema_queries, :enable_default_unicode_types, :auto_connect,
190
+ :cs_equality_operator
178
191
 
179
192
  def initialize(logger,config)
180
193
  @connection_options = config
181
194
  connect
182
- super(@connection, logger)
183
- @database_version = info_schema_query { select_value('SELECT @@version') }
184
- @database_year = DATABASE_VERSION_REGEXP.match(@database_version)[1].to_i rescue 0
195
+ super(raw_connection, logger)
185
196
  initialize_sqlserver_caches
186
197
  use_database
187
- unless SUPPORTED_VERSIONS.include?(@database_year)
198
+ unless SUPPORTED_VERSIONS.include?(database_year)
188
199
  raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported."
189
200
  end
190
201
  end
191
202
 
192
- # ABSTRACT ADAPTER =========================================#
203
+ # === Abstract Adapter ========================================== #
193
204
 
194
205
  def adapter_name
195
206
  ADAPTER_NAME
@@ -199,116 +210,22 @@ module ActiveRecord
199
210
  true
200
211
  end
201
212
 
202
- def supports_ddl_transactions?
213
+ def supports_primary_key?
203
214
  true
204
215
  end
205
216
 
206
- def supports_savepoints?
217
+ def supports_count_distinct?
207
218
  true
208
219
  end
209
220
 
210
- def sqlserver?
221
+ def supports_ddl_transactions?
211
222
  true
212
223
  end
213
224
 
214
- def sqlserver_2000?
215
- @database_year == 2000
216
- end
217
-
218
- def sqlserver_2005?
219
- @database_year == 2005
220
- end
221
-
222
- def sqlserver_2008?
223
- @database_year == 2008
224
- end
225
-
226
- def version
227
- self.class::VERSION
228
- end
229
-
230
- def inspect
231
- "#<#{self.class} version: #{version}, year: #{@database_year}, connection_options: #{@connection_options.inspect}>"
232
- end
233
-
234
- def auto_connect
235
- @@auto_connect.is_a?(FalseClass) ? false : true
236
- end
237
-
238
- def native_string_database_type
239
- @@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
240
- end
241
-
242
- def native_text_database_type
243
- @@native_text_database_type ||
244
- if sqlserver_2005? || sqlserver_2008?
245
- enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
246
- else
247
- enable_default_unicode_types ? 'ntext' : 'text'
248
- end
249
- end
250
-
251
- def native_time_database_type
252
- sqlserver_2008? ? 'time' : 'datetime'
253
- end
254
-
255
- def native_date_database_type
256
- sqlserver_2008? ? 'date' : 'datetime'
257
- end
258
-
259
- def native_binary_database_type
260
- @@native_binary_database_type || ((sqlserver_2005? || sqlserver_2008?) ? 'varbinary(max)' : 'image')
261
- end
262
-
263
-
264
- # QUOTING ==================================================#
265
-
266
- def quote(value, column = nil)
267
- case value
268
- when String, ActiveSupport::Multibyte::Chars
269
- if column && column.type == :binary
270
- column.class.string_to_binary(value)
271
- elsif value.is_utf8? || (column && column.type == :string)
272
- "N'#{quote_string(value)}'"
273
- else
274
- super
275
- end
276
- else
277
- super
278
- end
279
- end
280
-
281
- def quote_string(string)
282
- string.to_s.gsub(/\'/, "''")
283
- end
284
-
285
- def quote_column_name(name)
286
- @sqlserver_quoted_column_and_table_names[name] ||=
287
- name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
288
- end
289
-
290
- def quote_table_name(name)
291
- quote_column_name(name)
292
- end
293
-
294
- def quoted_true
295
- QUOTED_TRUE
296
- end
297
-
298
- def quoted_false
299
- QUOTED_FALSE
300
- end
301
-
302
- def quoted_date(value)
303
- if value.acts_like?(:time) && value.respond_to?(:usec)
304
- "#{super}.#{sprintf("%03d",value.usec/1000)}"
305
- else
306
- super
307
- end
225
+ def supports_savepoints?
226
+ true
308
227
  end
309
228
 
310
- # REFERENTIAL INTEGRITY ====================================#
311
-
312
229
  def disable_referential_integrity
313
230
  do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
314
231
  yield
@@ -316,13 +233,9 @@ module ActiveRecord
316
233
  do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
317
234
  end
318
235
 
319
- # CONNECTION MANAGEMENT ====================================#
236
+ # === Abstract Adapter (Connection Management) ================== #
320
237
 
321
238
  def active?
322
- case @connection_options[:mode]
323
- when :dblib
324
- @connection.active?
325
- end
326
239
  raw_connection_do("SELECT 1")
327
240
  true
328
241
  rescue *lost_connection_exceptions
@@ -336,521 +249,113 @@ module ActiveRecord
336
249
  end
337
250
 
338
251
  def disconnect!
339
- case @connection_options[:mode]
340
- when :dblib
341
- @connection.close rescue nil
252
+ case connection_mode
342
253
  when :odbc
343
- @connection.disconnect rescue nil
254
+ raw_connection.disconnect rescue nil
344
255
  else :adonet
345
- @connection.close rescue nil
256
+ raw_connection.close rescue nil
346
257
  end
347
258
  end
348
259
 
349
- # DATABASE STATEMENTS ======================================#
350
-
351
- def user_options
352
- info_schema_query do
353
- select_rows("dbcc useroptions").inject(HashWithIndifferentAccess.new) do |values,row|
354
- set_option = row[0].gsub(/\s+/,'_')
355
- user_value = row[1]
356
- values[set_option] = user_value
357
- values
358
- end
359
- end
360
- end
361
-
362
- VALID_ISOLATION_LEVELS = ["READ COMMITTED", "READ UNCOMMITTED", "REPEATABLE READ", "SERIALIZABLE", "SNAPSHOT"]
363
-
364
- def run_with_isolation_level(isolation_level)
365
- raise ArgumentError, "Invalid isolation level, #{isolation_level}. Supported levels include #{VALID_ISOLATION_LEVELS.to_sentence}." if !VALID_ISOLATION_LEVELS.include?(isolation_level.upcase)
366
- initial_isolation_level = user_options[:isolation_level] || "READ COMMITTED"
367
- do_execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
368
- begin
369
- yield
370
- ensure
371
- do_execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
372
- end if block_given?
373
- end
374
-
375
- def select_one(sql, name = nil)
376
- result = raw_select sql, name, :fetch => :one
377
- (result && result.first.present?) ? result.first : nil
378
- end
379
-
380
- def select_one_with_query_cache(*args)
381
- if @query_cache_enabled
382
- cache_sql(args.first) { select_one_without_query_cache(*args) }
383
- else
384
- select_one_without_query_cache(*args)
385
- end
386
- end
387
- alias_method_chain :select_one, :query_cache
388
-
389
- def select_rows(sql, name = nil)
390
- raw_select sql, name, :fetch => :rows
391
- end
392
-
393
- def execute(sql, name = nil, skip_logging = false)
394
- if id_insert_table_name = query_requires_identity_insert?(sql)
395
- with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
396
- else
397
- do_execute(sql,name)
398
- end
399
- end
400
-
401
- def execute_procedure(proc_name, *variables)
402
- vars = variables.map{ |v| quote(v) }.join(', ')
403
- sql = "EXEC #{proc_name} #{vars}".strip
404
- name = 'Execute Procedure'
405
- log(sql, name) do
406
- case @connection_options[:mode]
407
- when :dblib
408
- result = @connection.execute(sql)
409
- result.each(:as => :hash, :cache_rows => true) do |row|
410
- r = row.with_indifferent_access
411
- yield(r) if block_given?
412
- end
413
- result.each.map{ |row| row.is_a?(Hash) ? row.with_indifferent_access : row }
414
- when :odbc
415
- results = []
416
- raw_connection_run(sql) do |handle|
417
- get_rows = lambda {
418
- rows = handle_to_names_and_values handle, :fetch => :all
419
- rows.each_with_index { |r,i| rows[i] = r.with_indifferent_access }
420
- results << rows
421
- }
422
- get_rows.call
423
- while handle_more_results?(handle)
424
- get_rows.call
425
- end
426
- end
427
- results.many? ? results : results.first
428
- when :adonet
429
- results = []
430
- results << select(sql, name).map { |r| r.with_indifferent_access }
431
- results.many? ? results : results.first
432
- end
433
- end
434
- end
435
-
436
- def use_database(database=nil)
437
- database ||= @connection_options[:database]
438
- do_execute "USE #{quote_table_name(database)}" unless database.blank?
439
- end
440
-
441
- def outside_transaction?
442
- info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
260
+ def reset!
261
+ remove_database_connections_and_rollback { }
443
262
  end
444
263
 
445
- def begin_db_transaction
446
- do_execute "BEGIN TRANSACTION"
447
- end
448
-
449
- def commit_db_transaction
450
- do_execute "COMMIT TRANSACTION"
451
- end
452
-
453
- def rollback_db_transaction
454
- do_execute "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"
455
- end
264
+ # === Abstract Adapter (Misc Support) =========================== #
456
265
 
457
- def create_savepoint
458
- do_execute "SAVE TRANSACTION #{current_savepoint_name}"
266
+ def pk_and_sequence_for(table_name)
267
+ idcol = identity_column(table_name)
268
+ idcol ? [idcol.name,nil] : nil
459
269
  end
460
270
 
461
- def release_savepoint
462
- end
463
-
464
- def rollback_to_savepoint
465
- do_execute "ROLLBACK TRANSACTION #{current_savepoint_name}"
466
- end
467
-
468
- def add_limit_offset!(sql, options)
469
- # Validate and/or convert integers for :limit and :offets options.
470
- if options[:offset]
471
- raise ArgumentError, "offset should have a limit" unless options[:limit]
472
- unless options[:offset].kind_of?(Integer)
473
- if options[:offset] =~ /^\d+$/
474
- options[:offset] = options[:offset].to_i
475
- else
476
- raise ArgumentError, "offset should be an integer"
477
- end
478
- end
479
- end
480
- if options[:limit] && !(options[:limit].kind_of?(Integer))
481
- if options[:limit] =~ /^\d+$/
482
- options[:limit] = options[:limit].to_i
483
- else
484
- raise ArgumentError, "limit should be an integer"
485
- end
486
- end
487
- # The business of adding limit/offset
488
- if options[:limit] and options[:offset]
489
- tally_sql = "SELECT count(*) as TotalRows from (#{sql.sub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally"
490
- add_lock! tally_sql, options
491
- total_rows = select_value(tally_sql).to_i
492
- if (options[:limit] + options[:offset]) >= total_rows
493
- options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
494
- end
495
- # Make sure we do not need a special limit/offset for association limiting. http://gist.github.com/25118
496
- add_limit_offset_for_association_limiting!(sql,options) and return if sql_for_association_limiting?(sql)
497
- # Wrap the SQL query in a bunch of outer SQL queries that emulate proper LIMIT,OFFSET support.
498
- sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]}")
499
- sql << ") AS tmp1"
500
- if options[:order]
501
- order = options[:order].split(',').map do |field|
502
- order_by_column, order_direction = field.split(" ")
503
- order_by_column = quote_column_name(order_by_column)
504
- # Investigate the SQL query to figure out if the order_by_column has been renamed.
505
- if sql =~ /#{Regexp.escape(order_by_column)} AS (t\d+_r\d+)/
506
- # Fx "[foo].[bar] AS t4_r2" was found in the SQL. Use the column alias (ie 't4_r2') for the subsequent orderings
507
- order_by_column = $1
508
- elsif order_by_column =~ /\w+\.\[?(\w+)\]?/
509
- order_by_column = $1
510
- else
511
- # It doesn't appear that the column name has been renamed as part of the query. Use just the column
512
- # name rather than the full identifier for the outer queries.
513
- order_by_column = order_by_column.split('.').last
514
- end
515
- # Put the column name and eventual direction back together
516
- [order_by_column, order_direction].join(' ').strip
517
- end.join(', ')
518
- sql << " ORDER BY #{change_order_direction(order)}) AS tmp2 ORDER BY #{order}"
519
- else
520
- sql << ") AS tmp2"
521
- end
522
- elsif options[:limit] && sql !~ /^\s*SELECT (@@|COUNT\()/i
523
- if md = sql.match(/^(\s*SELECT)(\s+DISTINCT)?(.*)/im)
524
- sql.replace "#{md[1]}#{md[2]} TOP #{options[:limit]}#{md[3]}"
525
- else
526
- # Account for building SQL fragments without SELECT yet. See #update_all and #limited_update_conditions.
527
- sql.replace "TOP #{options[:limit]} #{sql}"
528
- end
529
- end
271
+ def primary_key(table_name)
272
+ identity_column(table_name).try(:name)
530
273
  end
531
274
 
532
- def add_lock!(sql, options)
533
- # http://blog.sqlauthority.com/2007/04/27/sql-server-2005-locking-hints-and-examples/
534
- return unless options[:lock]
535
- lock_type = options[:lock] == true ? 'WITH(HOLDLOCK, ROWLOCK)' : options[:lock]
536
- sql.gsub! %r|LEFT OUTER JOIN\s+(.*?)\s+ON|im, "LEFT OUTER JOIN \\1 #{lock_type} ON"
537
- sql.gsub! %r{FROM\s([\w\[\]\.]+)}im, "FROM \\1 #{lock_type}"
538
- end
275
+ # === SQLServer Specific (DB Reflection) ======================== #
539
276
 
540
- def empty_insert_statement(table_name)
541
- "INSERT INTO #{quote_table_name(table_name)} DEFAULT VALUES"
277
+ def database_version
278
+ @database_version ||= info_schema_query { select_value('SELECT @@version') }
542
279
  end
543
280
 
544
- def case_sensitive_equality_operator
545
- "COLLATE Latin1_General_CS_AS ="
546
- end
547
-
548
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
549
- match_data = where_sql.match(/^(.*?[\]\) ])WHERE[\[\( ]/)
550
- limit = match_data[1]
551
- where_sql.sub!(limit,'')
552
- "WHERE #{quoted_primary_key} IN (SELECT #{limit} #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
553
- end
554
-
555
- def newid_function
556
- select_value "SELECT NEWID()"
557
- end
558
-
559
- def newsequentialid_function
560
- select_value "SELECT NEWSEQUENTIALID()"
561
- end
562
-
563
- # SCHEMA STATEMENTS ========================================#
564
-
565
- def native_database_types
566
- @native_database_types ||= initialize_native_database_types.freeze
567
- end
568
-
569
- def table_alias_length
570
- 128
571
- end
572
-
573
- def tables(name = nil)
574
- info_schema_query do
575
- select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
576
- end
281
+ def database_year
282
+ DATABASE_VERSION_REGEXP.match(database_version)[1].to_i
577
283
  end
578
284
 
579
- def views(name = nil)
580
- @sqlserver_views_cache ||=
581
- info_schema_query { select_values("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME NOT IN ('sysconstraints','syssegments')") }
285
+ def sqlserver?
286
+ true
582
287
  end
583
288
 
584
- def view_information(table_name)
585
- table_name = unqualify_table_name(table_name)
586
- @sqlserver_view_information_cache[table_name] ||= begin
587
- view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
588
- if view_info
589
- view_info = view_info.with_indifferent_access
590
- if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
591
- view_info[:VIEW_DEFINITION] = info_schema_query do
592
- begin
593
- select_values("EXEC sp_helptext #{quote_table_name(table_name)}").join
594
- rescue
595
- warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
596
- end
597
- end
598
- end
599
- end
600
- view_info
601
- end
289
+ def sqlserver_2005?
290
+ database_year == 2005
602
291
  end
603
292
 
604
- def view_table_name(table_name)
605
- view_info = view_information(table_name)
606
- view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
293
+ def sqlserver_2008?
294
+ database_year == 2008
607
295
  end
608
296
 
609
- def table_exists?(table_name)
610
- super || tables.include?(unqualify_table_name(table_name)) || views.include?(table_name.to_s)
297
+ def version
298
+ self.class::VERSION
611
299
  end
612
300
 
613
- def indexes(table_name, name = nil)
614
- unquoted_table_name = unqualify_table_name(table_name)
615
- data = select("EXEC sp_helpindex #{quote_table_name(unquoted_table_name)}",name) rescue []
616
- data.inject([]) do |indexes,index|
617
- index = index.with_indifferent_access
618
- if index[:index_description] =~ /primary key/
619
- indexes
620
- else
621
- name = index[:index_name]
622
- unique = index[:index_description] =~ /unique/
623
- columns = index[:index_keys].split(',').map do |column|
624
- column.strip!
625
- column.gsub! '(-)', '' if column.ends_with?('(-)')
626
- column
627
- end
628
- indexes << IndexDefinition.new(table_name, name, unique, columns)
629
- end
630
- end
301
+ def inspect
302
+ "#<#{self.class} version: #{version}, year: #{database_year}, connection_options: #{@connection_options.inspect}>"
631
303
  end
632
304
 
633
- def columns(table_name, name = nil)
634
- return [] if table_name.blank?
635
- cache_key = unqualify_table_name(table_name)
636
- @sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
637
- sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
638
- SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
639
- end
305
+ def auto_connect
306
+ @@auto_connect.is_a?(FalseClass) ? false : true
640
307
  end
641
308
 
642
- def create_table(table_name, options = {})
643
- super
644
- remove_sqlserver_columns_cache_for(table_name)
309
+ def native_string_database_type
310
+ @@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
645
311
  end
646
312
 
647
- def rename_table(table_name, new_name)
648
- do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
313
+ def native_text_database_type
314
+ @@native_text_database_type || enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
649
315
  end
650
316
 
651
- def drop_table(table_name, options = {})
652
- super
653
- remove_sqlserver_columns_cache_for(table_name)
317
+ def native_time_database_type
318
+ sqlserver_2008? ? 'time' : 'datetime'
654
319
  end
655
320
 
656
- def add_column(table_name, column_name, type, options = {})
657
- super
658
- remove_sqlserver_columns_cache_for(table_name)
321
+ def native_date_database_type
322
+ sqlserver_2008? ? 'date' : 'datetime'
659
323
  end
660
324
 
661
- def remove_column(table_name, *column_names)
662
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
663
- column_names.flatten.each do |column_name|
664
- remove_check_constraints(table_name, column_name)
665
- remove_default_constraint(table_name, column_name)
666
- remove_indexes(table_name, column_name)
667
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
668
- end
669
- remove_sqlserver_columns_cache_for(table_name)
325
+ def native_binary_database_type
326
+ @@native_binary_database_type || 'varbinary(max)'
670
327
  end
671
328
 
672
- def change_column(table_name, column_name, type, options = {})
673
- sql_commands = []
674
- column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
675
- 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])}"
676
- change_column_sql << " NOT NULL" if options[:null] == false
677
- sql_commands << change_column_sql
678
- if options_include_default?(options) || (column_object && column_object.type != type.to_sym)
679
- remove_default_constraint(table_name,column_name)
680
- end
681
- if options_include_default?(options)
682
- remove_sqlserver_columns_cache_for(table_name)
683
- sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
684
- end
685
- sql_commands.each { |c| do_execute(c) }
686
- remove_sqlserver_columns_cache_for(table_name)
329
+ def cs_equality_operator
330
+ @@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS ='
687
331
  end
688
332
 
689
- def change_column_default(table_name, column_name, default)
690
- remove_default_constraint(table_name, column_name)
691
- do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
692
- remove_sqlserver_columns_cache_for(table_name)
693
- end
694
333
 
695
- def rename_column(table_name, column_name, new_column_name)
696
- column_for(table_name,column_name)
697
- do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
698
- remove_sqlserver_columns_cache_for(table_name)
699
- end
334
+ protected
700
335
 
701
- def remove_index(table_name, options = {})
702
- do_execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}" rescue nil
703
- end
336
+ # === Abstract Adapter (Misc Support) =========================== #
704
337
 
705
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
706
- limit = nil unless self.class.type_limitable?(type)
707
- case type.to_s
708
- when 'integer'
709
- case limit
710
- when 1..2 then 'smallint'
711
- when 3..4, nil then 'integer'
712
- when 5..8 then 'bigint'
713
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
714
- end
338
+ def translate_exception(e, message)
339
+ case message
340
+ when /cannot insert duplicate key .* with unique index/i
341
+ RecordNotUnique.new(message,e)
342
+ when /conflicted with the foreign key constraint/i
343
+ InvalidForeignKey.new(message,e)
344
+ when *lost_connection_messages
345
+ LostConnection.new(message,e)
715
346
  else
716
347
  super
717
348
  end
718
349
  end
719
350
 
720
- def add_order_by_for_association_limiting!(sql, options)
721
- # Disertation http://gist.github.com/24073
722
- # Information http://weblogs.sqlteam.com/jeffs/archive/2007/12/13/select-distinct-order-by-error.aspx
723
- return sql if options[:order].blank?
724
- columns = sql.match(/SELECT\s+DISTINCT(.*?)FROM/)[1].strip
725
- sql.sub!(/SELECT\s+DISTINCT/,'SELECT')
726
- sql << "GROUP BY #{columns} ORDER BY #{order_to_min_set(options[:order])}"
727
- end
728
-
729
- def change_column_null(table_name, column_name, null, default = nil)
730
- column = column_for(table_name,column_name)
731
- unless null || default.nil?
732
- do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
733
- end
734
- sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
735
- sql << " NOT NULL" unless null
736
- do_execute sql
737
- end
738
-
739
- def pk_and_sequence_for(table_name)
740
- idcol = identity_column(table_name)
741
- idcol ? [idcol.name,nil] : nil
742
- end
743
-
744
- # RAKE UTILITY METHODS =====================================#
745
-
746
- def recreate_database
747
- remove_database_connections_and_rollback do
748
- do_execute "EXEC sp_MSforeachtable 'DROP TABLE ?'"
749
- end
750
- end
751
-
752
- def recreate_database!(database=nil)
753
- current_db = current_database
754
- database ||= current_db
755
- this_db = database.to_s == current_db
756
- do_execute 'USE master' if this_db
757
- drop_database(database)
758
- create_database(database)
759
- ensure
760
- use_database(current_db) if this_db
761
- end
762
-
763
- # Remove existing connections and rollback any transactions if we received the message
764
- # 'Cannot drop the database 'test' because it is currently in use'
765
- def drop_database(database)
766
- retry_count = 0
767
- max_retries = 1
768
- begin
769
- do_execute "DROP DATABASE #{quote_table_name(database)}"
770
- rescue ActiveRecord::StatementInvalid => err
771
- if err.message =~ /because it is currently in use/i
772
- raise if retry_count >= max_retries
773
- retry_count += 1
774
- remove_database_connections_and_rollback(database)
775
- retry
776
- elsif err.message =~ /does not exist/i
777
- nil
778
- else
779
- raise
780
- end
781
- end
782
- end
783
-
784
- def create_database(database)
785
- do_execute "CREATE DATABASE #{quote_table_name(database)}"
786
- end
787
-
788
- def current_database
789
- select_value 'SELECT DB_NAME()'
790
- end
791
-
792
- def charset
793
- select_value "SELECT SERVERPROPERTY('SqlCharSetName')"
794
- end
795
-
796
- # This should disconnect all other users and rollback any transactions for SQL 2000 and 2005
797
- # http://sqlserver2000.databases.aspfaq.com/how-do-i-drop-a-sql-server-database.html
798
- def remove_database_connections_and_rollback(database=nil)
799
- database ||= current_database
800
- do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
801
- begin
802
- yield
803
- ensure
804
- do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
805
- end if block_given?
806
- end
807
-
808
-
809
-
810
- protected
811
-
812
- # CONNECTION MANAGEMENT ====================================#
351
+ # === SQLServer Specific (Connection Management) ================ #
813
352
 
814
353
  def connect
815
354
  config = @connection_options
816
- @connection = case @connection_options[:mode]
817
- when :dblib
818
- appname = config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
819
- login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
820
- timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
821
- encoding = config[:encoding].present? ? config[:encoding] : nil
822
- TinyTds::Client.new({
823
- :dataserver => config[:dataserver],
824
- :host => config[:host],
825
- :port => config[:port],
826
- :username => config[:username],
827
- :password => config[:password],
828
- :database => config[:database],
829
- :appname => appname,
830
- :login_timeout => login_timeout,
831
- :timeout => timeout,
832
- :encoding => encoding
833
- }).tap do |client|
834
- client.execute("SET ANSI_DEFAULTS ON").do
835
- client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
836
- client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
837
- end
355
+ @connection = case connection_mode
838
356
  when :odbc
839
- if config[:dsn].include?(';')
840
- driver = ODBC::Driver.new.tap do |d|
841
- d.name = config[:dsn_name] || 'Driver1'
842
- d.attrs = config[:dsn].split(';').map{ |atr| atr.split('=') }.reject{ |kv| kv.size != 2 }.inject({}){ |h,kv| k,v = kv ; h[k] = v ; h }
843
- end
844
- ODBC::Database.new.drvconnect(driver)
845
- else
846
- ODBC.connect config[:dsn], config[:username], config[:password]
847
- end.tap do |c|
848
- if c.respond_to?(:use_time)
849
- c.use_time = true
850
- c.use_utc = ActiveRecord::Base.default_timezone == :utc
851
- @connection_supports_native_types = true
852
- end
853
- end
357
+ odbc = ['::ODBC','::ODBC_UTF8','::ODBC_NONE'].detect{ |odbc_ns| odbc_ns.constantize rescue nil }.constantize
358
+ odbc.connect config[:dsn], config[:username], config[:password]
854
359
  when :adonet
855
360
  System::Data::SqlClient::SqlConnection.new.tap do |connection|
856
361
  connection.connection_string = System::Data::SqlClient::SqlConnectionStringBuilder.new.tap do |cs|
@@ -868,40 +373,25 @@ module ActiveRecord
868
373
  connection.open
869
374
  end
870
375
  end
871
- configure_connection
872
376
  rescue
873
377
  raise unless @auto_connecting
874
378
  end
875
379
 
876
- # Override this method so every connection can be configured to your needs.
877
- # For example:
878
- # do_execute "SET TEXTSIZE #{64.megabytes}"
879
- # do_execute "SET CONCAT_NULL_YIELDS_NULL ON"
880
- def configure_connection
881
- end
882
-
883
- # Override this method so every connection can have a unique name. Max 30 characters. Used by TinyTDS only.
884
- # For example:
885
- # "myapp_#{$$}_#{Thread.current.object_id}".to(29)
886
- def configure_application_name
887
- end
888
-
889
- def lost_connection_exceptions
890
- exceptions = LOST_CONNECTION_EXCEPTIONS[@connection_options[:mode]]
891
- @lost_connection_exceptions ||= exceptions ? exceptions.map(&:constantize) : []
892
- end
893
-
894
- def lost_connection_messages
895
- LOST_CONNECTION_MESSAGES[@connection_options[:mode]]
380
+ def remove_database_connections_and_rollback(database=nil)
381
+ database ||= current_database
382
+ do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
383
+ begin
384
+ yield
385
+ ensure
386
+ do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
387
+ end if block_given?
896
388
  end
897
389
 
898
390
  def with_auto_reconnect
899
391
  begin
900
392
  yield
901
- rescue *lost_connection_exceptions => e
902
- if lost_connection_messages.any? { |lcm| e.message =~ lcm }
903
- retry if auto_reconnected?
904
- end
393
+ rescue Exception => e
394
+ retry if translate_exception(e,e.message).is_a?(LostConnection) && auto_reconnected?
905
395
  raise
906
396
  end
907
397
  end
@@ -922,488 +412,8 @@ module ActiveRecord
922
412
  @auto_connecting = false
923
413
  end
924
414
 
925
- def raw_connection_run(sql)
926
- with_auto_reconnect do
927
- case @connection_options[:mode]
928
- when :dblib
929
- @connection.execute(sql)
930
- when :odbc
931
- block_given? ? @connection.run_block(sql) { |handle| yield(handle) } : @connection.run(sql)
932
- else :adonet
933
- @connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_reader
934
- end
935
- end
936
- end
937
-
938
- def raw_connection_do(sql)
939
- case @connection_options[:mode]
940
- when :dblib
941
- @insert_sql ? @connection.execute(sql).insert : @connection.execute(sql).do
942
- when :odbc
943
- @connection.do(sql)
944
- else :adonet
945
- @connection.create_command.tap{ |cmd| cmd.command_text = sql }.execute_non_query
946
- end
947
- end
948
-
949
- def finish_statement_handle(handle)
950
- case @connection_options[:mode]
951
- when :dblib
952
- handle.cancel if handle
953
- when :odbc
954
- handle.drop if handle && handle.respond_to?(:drop) && !handle.finished?
955
- when :adonet
956
- handle.close if handle && handle.respond_to?(:close) && !handle.is_closed
957
- handle.dispose if handle && handle.respond_to?(:dispose)
958
- end
959
- handle
960
- end
961
-
962
- # DATABASE STATEMENTS ======================================
963
-
964
- def select(sql, name = nil)
965
- raw_select sql, name, :fetch => :all
966
- end
967
-
968
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
969
- @insert_sql = true
970
- case @connection_options[:mode]
971
- when :dblib
972
- execute(sql, name) || id_value
973
- else
974
- super || select_value("SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident")
975
- end
976
- ensure
977
- @insert_sql = false
978
- end
979
-
980
- def update_sql(sql, name = nil)
981
- @update_sql = true
982
- case @connection_options[:mode]
983
- when :dblib
984
- sqlserver_2000? ? execute(sql, name) && select_value('SELECT @@ROWCOUNT AS AffectedRows') : execute(sql, name)
985
- else
986
- execute(sql, name)
987
- select_value('SELECT @@ROWCOUNT AS AffectedRows')
988
- end
989
- ensure
990
- @update_sql = false
991
- end
992
-
993
- def info_schema_query
994
- log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
995
- end
996
-
997
- def do_execute(sql,name=nil)
998
- log(sql, name || 'EXECUTE') do
999
- with_auto_reconnect { raw_connection_do(sql) }
1000
- end
1001
- end
1002
-
1003
- def raw_select(sql, name=nil, options={})
1004
- log(sql,name) do
1005
- begin
1006
- handle = raw_connection_run(sql)
1007
- handle_to_names_and_values(handle, options)
1008
- ensure
1009
- finish_statement_handle(handle)
1010
- end
1011
- end
1012
- end
1013
-
1014
- def handle_more_results?(handle)
1015
- case @connection_options[:mode]
1016
- when :dblib
1017
- when :odbc
1018
- handle.more_results
1019
- when :adonet
1020
- handle.next_result
1021
- end
1022
- end
1023
-
1024
- def handle_to_names_and_values(handle, options={})
1025
- case @connection_options[:mode]
1026
- when :dblib
1027
- handle_to_names_and_values_dblib(handle, options)
1028
- when :odbc
1029
- handle_to_names_and_values_odbc(handle, options)
1030
- when :adonet
1031
- handle_to_names_and_values_adonet(handle, options)
1032
- end
1033
- end
1034
-
1035
- def handle_to_names_and_values_dblib(handle, options={})
1036
- query_options = {}.tap do |qo|
1037
- qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
1038
- qo[:first] = true if options[:fetch] == :one
1039
- qo[:as] = options[:fetch] == :rows ? :array : :hash
1040
- end
1041
- handle.each(query_options)
1042
- end
1043
-
1044
- def handle_to_names_and_values_odbc(handle, options={})
1045
- @connection.use_utc = ActiveRecord::Base.default_timezone == :utc if @connection_supports_native_types
1046
- case options[:fetch]
1047
- when :all, :one
1048
- if @connection_supports_native_types
1049
- if options[:fetch] == :all
1050
- handle.each_hash || []
1051
- else
1052
- row = handle.fetch_hash
1053
- rows = row ? [row] : [[]]
1054
- end
1055
- else
1056
- rows = if options[:fetch] == :all
1057
- handle.fetch_all || []
1058
- else
1059
- row = handle.fetch
1060
- row ? [row] : [[]]
1061
- end
1062
- names = handle.columns(true).map{ |c| c.name }
1063
- names_and_values = []
1064
- rows.each do |row|
1065
- h = {}
1066
- i = 0
1067
- while i < row.size
1068
- v = row[i]
1069
- h[names[i]] = v.respond_to?(:to_sqlserver_string) ? v.to_sqlserver_string : v
1070
- i += 1
1071
- end
1072
- names_and_values << h
1073
- end
1074
- names_and_values
1075
- end
1076
- when :rows
1077
- rows = handle.fetch_all || []
1078
- return rows if @connection_supports_native_types
1079
- rows.each do |row|
1080
- i = 0
1081
- while i < row.size
1082
- v = row[i]
1083
- row[i] = v.to_sqlserver_string if v.respond_to?(:to_sqlserver_string)
1084
- i += 1
1085
- end
1086
- end
1087
- rows
1088
- end
1089
- end
1090
-
1091
- def handle_to_names_and_values_adonet(handle, options={})
1092
- if handle.has_rows
1093
- names = []
1094
- rows = []
1095
- fields_named = options[:fetch] == :rows
1096
- one_row_only = options[:fetch] == :one
1097
- while handle.read
1098
- row = []
1099
- handle.visible_field_count.times do |row_index|
1100
- value = handle.get_value(row_index)
1101
- value = case value
1102
- when System::String
1103
- value.to_s
1104
- when System::DBNull
1105
- nil
1106
- when System::DateTime
1107
- value.to_string("yyyy-MM-dd HH:mm:ss.fff").to_s
1108
- when @@array_of_bytes ||= System::Array[System::Byte]
1109
- String.new(value)
1110
- else
1111
- value
1112
- end
1113
- row << value
1114
- names << handle.get_name(row_index).to_s unless fields_named
1115
- break if one_row_only
1116
- end
1117
- rows << row
1118
- fields_named = true
1119
- end
1120
- else
1121
- rows = []
1122
- end
1123
- if options[:fetch] != :rows
1124
- names_and_values = []
1125
- rows.each do |row|
1126
- h = {}
1127
- i = 0
1128
- while i < row.size
1129
- h[names[i]] = row[i]
1130
- i += 1
1131
- end
1132
- names_and_values << h
1133
- end
1134
- names_and_values
1135
- else
1136
- rows
1137
- end
1138
- end
1139
-
1140
- def add_limit_offset_for_association_limiting!(sql, options)
1141
- sql.replace %|
1142
- SET NOCOUNT ON
1143
- DECLARE @row_number TABLE (row int identity(1,1), id int)
1144
- INSERT INTO @row_number (id)
1145
- #{sql}
1146
- SET NOCOUNT OFF
1147
- SELECT id FROM (
1148
- SELECT TOP #{options[:limit]} * FROM (
1149
- SELECT TOP #{options[:limit] + options[:offset]} * FROM @row_number ORDER BY row
1150
- ) AS tmp1 ORDER BY row DESC
1151
- ) AS tmp2 ORDER BY row
1152
- |.gsub(/[ \t\r\n]+/,' ')
1153
- end
1154
-
1155
- # SCHEMA STATEMENTS ========================================#
1156
-
1157
- def remove_check_constraints(table_name, column_name)
1158
- 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)}'") }
1159
- constraints.each do |constraint|
1160
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
1161
- end
1162
- end
1163
-
1164
- def remove_default_constraint(table_name, column_name)
1165
- select_all("EXEC sp_helpconstraint '#{quote_string(table_name)}','nomsg'").flatten.select do |row|
1166
- row['constraint_type'] == "DEFAULT on column #{column_name}"
1167
- end.each do |row|
1168
- do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{row['constraint_name']}"
1169
- end
1170
- end
1171
-
1172
- def remove_indexes(table_name, column_name)
1173
- indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
1174
- remove_index(table_name, {:name => index.name})
1175
- end
1176
- end
1177
-
1178
- def default_name(table_name, column_name)
1179
- "DF_#{table_name}_#{column_name}"
1180
- end
1181
-
1182
- # IDENTITY INSERTS =========================================#
1183
-
1184
- def with_identity_insert_enabled(table_name)
1185
- table_name = quote_table_name(table_name_or_views_table_name(table_name))
1186
- set_identity_insert(table_name, true)
1187
- yield
1188
- ensure
1189
- set_identity_insert(table_name, false)
1190
- end
1191
-
1192
- def set_identity_insert(table_name, enable = true)
1193
- sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
1194
- do_execute(sql,'IDENTITY_INSERT')
1195
- rescue Exception => e
1196
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
1197
- end
1198
-
1199
- def query_requires_identity_insert?(sql)
1200
- if insert_sql?(sql)
1201
- table_name = get_table_name(sql)
1202
- id_column = identity_column(table_name)
1203
- id_column && sql =~ /^\s*INSERT[^(]+\([^)]*\b(#{id_column.name})\b,?[^)]*\)/i ? quote_table_name(table_name) : false
1204
- else
1205
- false
1206
- end
1207
- end
1208
-
1209
- def identity_column(table_name)
1210
- columns(table_name).detect(&:is_identity?)
1211
- end
1212
-
1213
- def table_name_or_views_table_name(table_name)
1214
- unquoted_table_name = unqualify_table_name(table_name)
1215
- views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
1216
- end
1217
-
1218
- # HELPER METHODS ===========================================#
1219
-
1220
- def insert_sql?(sql)
1221
- !(sql =~ /^\s*INSERT/i).nil?
1222
- end
1223
-
1224
- def unqualify_table_name(table_name)
1225
- table_name.to_s.split('.').last.gsub(/[\[\]]/,'')
1226
- end
1227
-
1228
- def unqualify_db_name(table_name)
1229
- table_names = table_name.to_s.split('.')
1230
- table_names.length == 3 ? table_names.first.tr('[]','') : nil
1231
- end
1232
-
1233
- def get_table_name(sql)
1234
- if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
1235
- $1 || $2
1236
- elsif sql =~ /from\s+([^\(\s]+)\s*/i
1237
- $1
1238
- else
1239
- nil
1240
- end
1241
- end
1242
-
1243
- def orders_and_dirs_set(order)
1244
- orders = order.sub('ORDER BY','').split(',').map(&:strip).reject(&:blank?)
1245
- orders_dirs = orders.map do |ord|
1246
- dir = nil
1247
- ord.sub!(/\b(asc|desc)$/i) do |match|
1248
- if match
1249
- dir = match.upcase.strip
1250
- ''
1251
- end
1252
- end
1253
- [ord.strip, dir]
1254
- end
1255
- end
1256
-
1257
- def views_real_column_name(table_name,column_name)
1258
- view_definition = view_information(table_name)[:VIEW_DEFINITION]
1259
- match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
1260
- match_data ? match_data[1] : column_name
1261
- end
1262
-
1263
- def order_to_min_set(order)
1264
- orders_dirs = orders_and_dirs_set(order)
1265
- orders_dirs.map do |o,d|
1266
- "MIN(#{o}) #{d}".strip
1267
- end.join(', ')
1268
- end
1269
-
1270
- def sql_for_association_limiting?(sql)
1271
- if md = sql.match(/^\s*SELECT(.*)FROM.*GROUP BY.*ORDER BY.*/im)
1272
- select_froms = md[1].split(',')
1273
- select_froms.size == 1 && !select_froms.first.include?('*')
1274
- end
1275
- end
1276
-
1277
- def remove_sqlserver_columns_cache_for(table_name)
1278
- cache_key = unqualify_table_name(table_name)
1279
- @sqlserver_columns_cache[cache_key] = nil
1280
- initialize_sqlserver_caches(false)
1281
- end
1282
-
1283
- def initialize_sqlserver_caches(reset_columns=true)
1284
- @sqlserver_columns_cache = {} if reset_columns
1285
- @sqlserver_views_cache = nil
1286
- @sqlserver_view_information_cache = {}
1287
- @sqlserver_quoted_column_and_table_names = {}
1288
- end
1289
-
1290
- def initialize_native_database_types
1291
- {
1292
- :primary_key => "int NOT NULL IDENTITY(1,1) PRIMARY KEY",
1293
- :string => { :name => native_string_database_type, :limit => 255 },
1294
- :text => { :name => native_text_database_type },
1295
- :integer => { :name => "int", :limit => 4 },
1296
- :float => { :name => "float", :limit => 8 },
1297
- :decimal => { :name => "decimal" },
1298
- :datetime => { :name => "datetime" },
1299
- :timestamp => { :name => "datetime" },
1300
- :time => { :name => native_time_database_type },
1301
- :date => { :name => native_date_database_type },
1302
- :binary => { :name => native_binary_database_type },
1303
- :boolean => { :name => "bit"},
1304
- # These are custom types that may move somewhere else for good schema_dumper.rb hacking to output them.
1305
- :char => { :name => 'char' },
1306
- :varchar_max => { :name => 'varchar(max)' },
1307
- :nchar => { :name => "nchar" },
1308
- :nvarchar => { :name => "nvarchar", :limit => 255 },
1309
- :nvarchar_max => { :name => "nvarchar(max)" },
1310
- :ntext => { :name => "ntext" },
1311
- :ss_timestamp => { :name => 'timestamp'}
1312
- }
1313
- end
1314
-
1315
- def column_definitions(table_name)
1316
- db_name = unqualify_db_name(table_name)
1317
- db_name_with_period = "#{db_name}." if db_name
1318
- table_name = unqualify_table_name(table_name)
1319
- sql = %{
1320
- SELECT
1321
- columns.TABLE_NAME as table_name,
1322
- columns.COLUMN_NAME as name,
1323
- columns.DATA_TYPE as type,
1324
- columns.COLUMN_DEFAULT as default_value,
1325
- columns.NUMERIC_SCALE as numeric_scale,
1326
- columns.NUMERIC_PRECISION as numeric_precision,
1327
- CASE
1328
- WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
1329
- ELSE COL_LENGTH(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
1330
- END as length,
1331
- CASE
1332
- WHEN columns.IS_NULLABLE = 'YES' THEN 1
1333
- ELSE NULL
1334
- END as is_nullable,
1335
- CASE
1336
- WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
1337
- ELSE 1
1338
- END as is_identity
1339
- FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
1340
- WHERE columns.TABLE_NAME = '#{table_name}'
1341
- ORDER BY columns.ordinal_position
1342
- }.gsub(/[ \t\r\n]+/,' ')
1343
- results = info_schema_query { select(sql,nil) }
1344
- results.collect do |ci|
1345
- ci = ci.symbolize_keys
1346
- ci[:type] = case ci[:type]
1347
- when /^bit|image|text|ntext|datetime$/
1348
- ci[:type]
1349
- when /^numeric|decimal$/i
1350
- "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
1351
- when /^float|real$/i
1352
- "#{ci[:type]}(#{ci[:numeric_precision]})"
1353
- when /^char|nchar|varchar|nvarchar|varbinary|bigint|int|smallint$/
1354
- ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
1355
- else
1356
- ci[:type]
1357
- end
1358
- if ci[:default_value].nil? && views.include?(table_name)
1359
- real_table_name = table_name_or_views_table_name(table_name)
1360
- real_column_name = views_real_column_name(table_name,ci[:name])
1361
- 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}'"
1362
- ci[:default_value] = info_schema_query { select_value(col_default_sql) }
1363
- end
1364
- ci[:default_value] = case ci[:default_value]
1365
- when nil, '(null)', '(NULL)'
1366
- nil
1367
- when /\A\((\w+\(\))\)\Z/
1368
- ci[:default_function] = $1
1369
- nil
1370
- else
1371
- match_data = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/m)
1372
- match_data ? match_data[1] : nil
1373
- end
1374
- ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
1375
- ci
1376
- end
1377
- end
1378
-
1379
- def column_for(table_name, column_name)
1380
- unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
1381
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
1382
- end
1383
- column
1384
- end
1385
-
1386
- def change_order_direction(order)
1387
- order.split(",").collect {|fragment|
1388
- case fragment
1389
- when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
1390
- when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
1391
- else String.new(fragment).split(',').join(' DESC,') + ' DESC'
1392
- end
1393
- }.join(",")
1394
- end
1395
-
1396
- def special_columns(table_name)
1397
- columns(table_name).select(&:is_special?).map(&:name)
1398
- end
1399
-
1400
- def repair_special_columns(sql)
1401
- special_cols = special_columns(get_table_name(sql))
1402
- for col in special_cols.to_a
1403
- sql.gsub!(/((\.|\s|\()\[?#{col.to_s}\]?)\s?=\s?/, '\1 LIKE ')
1404
- sql.gsub!(/ORDER BY #{col.to_s}/i, '')
1405
- end
1406
- sql
415
+ def connection_mode
416
+ @connection_options[:mode]
1407
417
  end
1408
418
 
1409
419
  end #class SQLServerAdapter < AbstractAdapter