activerecord-sqlserver-adapter 2.3.24 → 3.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 (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