rails-sqlserver-2000-2005-adapter 1.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.
@@ -0,0 +1,913 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require_library_or_gem 'dbi' unless defined?(DBI)
3
+ require 'core_ext/dbi'
4
+ require 'core_ext/active_record'
5
+ require 'base64'
6
+
7
+ module ActiveRecord
8
+
9
+ class Base
10
+
11
+ def self.sqlserver_connection(config) #:nodoc:
12
+ config.symbolize_keys!
13
+ mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
14
+ username = config[:username] ? config[:username].to_s : 'sa'
15
+ password = config[:password] ? config[:password].to_s : ''
16
+ if mode == "ODBC"
17
+ raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
18
+ dsn = config[:dsn]
19
+ driver_url = "DBI:ODBC:#{dsn}"
20
+ else
21
+ raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
22
+ database = config[:database]
23
+ host = config[:host] ? config[:host].to_s : 'localhost'
24
+ driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User ID=#{username};Password=#{password};"
25
+ end
26
+ conn = DBI.connect(driver_url, username, password)
27
+ conn["AutoCommit"] = true
28
+ ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
29
+ end
30
+
31
+ end
32
+
33
+ module ConnectionAdapters
34
+
35
+ class SQLServerColumn < Column
36
+
37
+ def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
38
+ @sqlserver_options = sqlserver_options
39
+ super(name, default, sql_type, null)
40
+ end
41
+
42
+ class << self
43
+
44
+ def string_to_binary(value)
45
+ "0x#{value.unpack("H*")[0]}"
46
+ end
47
+
48
+ def binary_to_string(value)
49
+ value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
50
+ end
51
+
52
+ end
53
+
54
+ def is_identity?
55
+ @sqlserver_options[:is_identity]
56
+ end
57
+
58
+ def is_special?
59
+ # TODO: Not sure if these should be added: varbinary(max), nchar, nvarchar(max)
60
+ sql_type =~ /^text|ntext|image$/
61
+ end
62
+
63
+ def is_utf8?
64
+ sql_type =~ /nvarchar|ntext|nchar|nvarchar(max)/i
65
+ end
66
+
67
+ def table_name
68
+ @sqlserver_options[:table_name]
69
+ end
70
+
71
+ def table_klass
72
+ @table_klass ||= table_name.classify.constantize rescue nil
73
+ (@table_klass && @table_klass < ActiveRecord::Base) ? @table_klass : nil
74
+ end
75
+
76
+ private
77
+
78
+ def extract_limit(sql_type)
79
+ case sql_type
80
+ when /^smallint/i then 2
81
+ when /^int/i then 4
82
+ when /^bigint/i then 8
83
+ else super
84
+ end
85
+ end
86
+
87
+ def simplified_type(field_type)
88
+ case field_type
89
+ when /real/i then :float
90
+ when /money/i then :decimal
91
+ when /image/i then :binary
92
+ when /bit/i then :boolean
93
+ when /uniqueidentifier/i then :string
94
+ when /datetime/i then simplified_datetime
95
+ else super
96
+ end
97
+ end
98
+
99
+ def simplified_datetime
100
+ if table_klass && table_klass.coerced_sqlserver_date_columns.include?(name)
101
+ :date
102
+ elsif table_klass && table_klass.coerced_sqlserver_time_columns.include?(name)
103
+ :time
104
+ else
105
+ :datetime
106
+ end
107
+ end
108
+
109
+ end #SQLServerColumn
110
+
111
+ # In ADO mode, this adapter will ONLY work on Windows systems,
112
+ # since it relies on Win32OLE, which, to my knowledge, is only
113
+ # available on Windows.
114
+ #
115
+ # This mode also relies on the ADO support in the DBI module. If you are using the
116
+ # one-click installer of Ruby, then you already have DBI installed, but
117
+ # the ADO module is *NOT* installed. You will need to get the latest
118
+ # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
119
+ # unzip it, and copy the file
120
+ # <tt>src/lib/dbd_ado/ADO.rb</tt>
121
+ # to
122
+ # <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
123
+ # (you will more than likely need to create the ADO directory).
124
+ # Once you've installed that file, you are ready to go.
125
+ #
126
+ # In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
127
+ # the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing,
128
+ # and it is available at http://www.ch-werner.de/rubyodbc/
129
+ #
130
+ # Options:
131
+ #
132
+ # * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
133
+ # * <tt>:username</tt> -- Defaults to sa.
134
+ # * <tt>:password</tt> -- Defaults to empty string.
135
+ # * <tt>:windows_auth</tt> -- Defaults to "User ID=#{username};Password=#{password}"
136
+ #
137
+ # ADO specific options:
138
+ #
139
+ # * <tt>:host</tt> -- Defaults to localhost.
140
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
141
+ # * <tt>:windows_auth</tt> -- Use windows authentication instead of username/password.
142
+ #
143
+ # ODBC specific options:
144
+ #
145
+ # * <tt>:dsn</tt> -- Defaults to nothing.
146
+ #
147
+ # ADO code tested on Windows 2000 and higher systems,
148
+ # running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
149
+ #
150
+ # ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63,
151
+ # unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
152
+ # [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
153
+ class SQLServerAdapter < AbstractAdapter
154
+
155
+ ADAPTER_NAME = 'SQLServer'.freeze
156
+ DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+(\d{4})/
157
+ SUPPORTED_VERSIONS = [2000,2005].freeze
158
+ LIMITABLE_TYPES = [:string,:integer,:float].freeze
159
+
160
+ cattr_accessor :native_text_database_type
161
+
162
+ class << self
163
+
164
+ def type_limitable?(type)
165
+ LIMITABLE_TYPES.include?(type.to_sym)
166
+ end
167
+
168
+ end
169
+
170
+ def initialize(connection, logger, connection_options=nil)
171
+ super(connection, logger)
172
+ @connection_options = connection_options
173
+ @sqlserver_columns_cache = {}
174
+ unless SUPPORTED_VERSIONS.include?(database_year)
175
+ raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported."
176
+ end
177
+ end
178
+
179
+ # ABSTRACT ADAPTER =========================================#
180
+
181
+ def adapter_name
182
+ ADAPTER_NAME
183
+ end
184
+
185
+ def supports_migrations?
186
+ true
187
+ end
188
+
189
+ def supports_ddl_transactions?
190
+ true
191
+ end
192
+
193
+ def database_version
194
+ @database_version ||= select_value('SELECT @@version')
195
+ end
196
+
197
+ def database_year
198
+ DATABASE_VERSION_REGEXP.match(database_version)[1].to_i
199
+ end
200
+
201
+ def sqlserver_2000?
202
+ database_year == 2000
203
+ end
204
+
205
+ def sqlserver_2005?
206
+ database_year == 2005
207
+ end
208
+
209
+ def inspect
210
+ "#<#{self.class} year: #{database_year}, connection_options: #{@connection_options.inspect}>"
211
+ end
212
+
213
+ def native_text_database_type
214
+ self.class.native_text_database_type || (sqlserver_2005? ? 'varchar(max)' : 'text')
215
+ end
216
+
217
+ # QUOTING ==================================================#
218
+
219
+ def quote(value, column = nil)
220
+ if value.kind_of?(String) && column && column.type == :binary
221
+ column.class.string_to_binary(value)
222
+ else
223
+ super
224
+ end
225
+ end
226
+
227
+ def quote_string(string)
228
+ string.to_s.gsub(/\'/, "''")
229
+ end
230
+
231
+ def quote_column_name(column_name)
232
+ column_name.to_s.split('.').map{ |name| "[#{name}]" }.join('.')
233
+ end
234
+
235
+ def quoted_true
236
+ '1'
237
+ end
238
+
239
+ def quoted_false
240
+ '0'
241
+ end
242
+
243
+ def quoted_date(value)
244
+ if value.acts_like?(:time) && value.respond_to?(:usec)
245
+ "#{super}.#{sprintf("%06d",value.usec)[0..2]}"
246
+ else
247
+ super
248
+ end
249
+ end
250
+
251
+ # REFERENTIAL INTEGRITY ====================================#
252
+
253
+ def disable_referential_integrity(&block)
254
+ do_execute "EXEC sp_MSForEachTable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
255
+ yield
256
+ ensure
257
+ do_execute "EXEC sp_MSForEachTable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
258
+ end
259
+
260
+ # CONNECTION MANAGEMENT ====================================#
261
+
262
+ def active?
263
+ raw_connection.execute("SELECT 1").finish
264
+ true
265
+ rescue DBI::DatabaseError, DBI::InterfaceError
266
+ false
267
+ end
268
+
269
+ def reconnect!
270
+ disconnect!
271
+ @connection = DBI.connect(*@connection_options)
272
+ rescue DBI::DatabaseError => e
273
+ @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
274
+ false
275
+ end
276
+
277
+ def disconnect!
278
+ raw_connection.disconnect rescue nil
279
+ end
280
+
281
+ def finish_statement_handle(handle)
282
+ handle.finish if handle && handle.respond_to?(:finish) && !handle.finished?
283
+ handle
284
+ end
285
+
286
+ # DATABASE STATEMENTS ======================================#
287
+
288
+ def select_rows(sql, name = nil)
289
+ raw_select(sql,name).last
290
+ end
291
+
292
+ def execute(sql, name = nil, &block)
293
+ if table_name = query_requires_identity_insert?(sql)
294
+ handle = with_identity_insert_enabled(table_name) { raw_execute(sql,name,&block) }
295
+ else
296
+ handle = raw_execute(sql,name,&block)
297
+ end
298
+ finish_statement_handle(handle)
299
+ end
300
+
301
+ def begin_db_transaction
302
+ do_execute "BEGIN TRANSACTION"
303
+ end
304
+
305
+ def commit_db_transaction
306
+ do_execute "COMMIT TRANSACTION"
307
+ end
308
+
309
+ def rollback_db_transaction
310
+ do_execute "ROLLBACK TRANSACTION" rescue nil
311
+ end
312
+
313
+ def add_limit_offset!(sql, options)
314
+ # Validate and/or convert integers for :limit and :offets options.
315
+ if options[:offset]
316
+ raise ArgumentError, "offset should have a limit" unless options[:limit]
317
+ unless options[:offset].kind_of?(Integer)
318
+ if options[:offset] =~ /^\d+$/
319
+ options[:offset] = options[:offset].to_i
320
+ else
321
+ raise ArgumentError, "offset should be an integer"
322
+ end
323
+ end
324
+ end
325
+ if options[:limit] && !(options[:limit].kind_of?(Integer))
326
+ if options[:limit] =~ /^\d+$/
327
+ options[:limit] = options[:limit].to_i
328
+ else
329
+ raise ArgumentError, "limit should be an integer"
330
+ end
331
+ end
332
+ # The business of adding limit/offset
333
+ if options[:limit] and options[:offset]
334
+ total_rows = select_value("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally").to_i
335
+ if (options[:limit] + options[:offset]) >= total_rows
336
+ options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
337
+ end
338
+ # Make sure we do not need a special limit/offset for association limiting. http://gist.github.com/25118
339
+ add_limit_offset_for_association_limiting!(sql,options) and return if sql_for_association_limiting?(sql)
340
+ # Wrap the SQL query in a bunch of outer SQL queries that emulate proper LIMIT,OFFSET support.
341
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]}")
342
+ sql << ") AS tmp1"
343
+ if options[:order]
344
+ order = options[:order].split(',').map do |field|
345
+ order_by_column, order_direction = field.split(" ")
346
+ order_by_column = quote_column_name(order_by_column)
347
+ # Investigate the SQL query to figure out if the order_by_column has been renamed.
348
+ if sql =~ /#{Regexp.escape(order_by_column)} AS (t\d_r\d\d?)/
349
+ # Fx "[foo].[bar] AS t4_r2" was found in the SQL. Use the column alias (ie 't4_r2') for the subsequent orderings
350
+ order_by_column = $1
351
+ elsif order_by_column =~ /\w+\.\[?(\w+)\]?/
352
+ order_by_column = $1
353
+ else
354
+ # It doesn't appear that the column name has been renamed as part of the query. Use just the column
355
+ # name rather than the full identifier for the outer queries.
356
+ order_by_column = order_by_column.split('.').last
357
+ end
358
+ # Put the column name and eventual direction back together
359
+ [order_by_column, order_direction].join(' ').strip
360
+ end.join(', ')
361
+ sql << " ORDER BY #{change_order_direction(order)}) AS tmp2 ORDER BY #{order}"
362
+ else
363
+ sql << ") AS tmp2"
364
+ end
365
+ elsif options[:limit] && sql !~ /^\s*SELECT (@@|COUNT\()/i
366
+ if md = sql.match(/^(\s*SELECT)(\s+DISTINCT)?(.*)/im)
367
+ sql.replace "#{md[1]}#{md[2]} TOP #{options[:limit]}#{md[3]}"
368
+ else
369
+ # Account for building SQL fragments without SELECT yet. See #update_all and #limited_update_conditions.
370
+ sql.replace "TOP #{options[:limit]} #{sql}"
371
+ end
372
+ end
373
+ end
374
+
375
+ def add_lock!(sql, options)
376
+ # http://blog.sqlauthority.com/2007/04/27/sql-server-2005-locking-hints-and-examples/
377
+ return unless options[:lock]
378
+ lock_type = options[:lock] == true ? 'WITH(HOLDLOCK, ROWLOCK)' : options[:lock]
379
+ from_table = sql.match(/FROM(.*)WHERE/im)[1]
380
+ sql.sub! from_table, "#{from_table}#{lock_type} "
381
+ end
382
+
383
+ def empty_insert_statement(table_name)
384
+ "INSERT INTO #{quote_table_name(table_name)} DEFAULT VALUES"
385
+ end
386
+
387
+ def case_sensitive_equality_operator
388
+ "COLLATE Latin1_General_CS_AS ="
389
+ end
390
+
391
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
392
+ match_data = where_sql.match(/(.*)WHERE/)
393
+ limit = match_data[1]
394
+ where_sql.sub!(limit,'')
395
+ "WHERE #{quoted_primary_key} IN (SELECT #{limit} #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
396
+ end
397
+
398
+ # SCHEMA STATEMENTS ========================================#
399
+
400
+ def native_database_types
401
+ binary = sqlserver_2005? ? "varbinary(max)" : "image"
402
+ {
403
+ :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
404
+ :string => { :name => "varchar", :limit => 255 },
405
+ :text => { :name => native_text_database_type },
406
+ :integer => { :name => "int", :limit => 4 },
407
+ :float => { :name => "float", :limit => 8 },
408
+ :decimal => { :name => "decimal" },
409
+ :datetime => { :name => "datetime" },
410
+ :timestamp => { :name => "datetime" },
411
+ :time => { :name => "datetime" },
412
+ :date => { :name => "datetime" },
413
+ :binary => { :name => binary },
414
+ :boolean => { :name => "bit"}
415
+ }
416
+ end
417
+
418
+ def table_alias_length
419
+ 128
420
+ end
421
+
422
+ def tables(name = nil)
423
+ select_values "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME <> 'dtproperties'"
424
+ end
425
+
426
+ def indexes(table_name, name = nil)
427
+ select("EXEC sp_helpindex #{quote_table_name(table_name)}",name).inject([]) do |indexes,index|
428
+ if index['index_description'] =~ /primary key/
429
+ indexes
430
+ else
431
+ name = index['index_name']
432
+ unique = index['index_description'] =~ /unique/
433
+ columns = index['index_keys'].split(',').map do |column|
434
+ column.strip!
435
+ column.gsub! '(-)', '' if column.ends_with?('(-)')
436
+ column
437
+ end
438
+ indexes << IndexDefinition.new(table_name, name, unique, columns)
439
+ end
440
+ end
441
+ end
442
+
443
+ def columns(table_name, name = nil)
444
+ return [] if table_name.blank?
445
+ cache_key = unqualify_table_name(table_name)
446
+ @sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
447
+ sqlserver_options = ci.except(:name,:default_value,:type,:null)
448
+ SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
449
+ end
450
+ end
451
+
452
+ def create_table(table_name, options = {})
453
+ super
454
+ remove_sqlserver_columns_cache_for(table_name)
455
+ end
456
+
457
+ def rename_table(table_name, new_name)
458
+ do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
459
+ end
460
+
461
+ def drop_table(table_name, options = {})
462
+ super
463
+ remove_sqlserver_columns_cache_for(table_name)
464
+ end
465
+
466
+ def add_column(table_name, column_name, type, options = {})
467
+ super
468
+ remove_sqlserver_columns_cache_for(table_name)
469
+ end
470
+
471
+ def remove_column(table_name, *column_names)
472
+ column_names.flatten.each do |column_name|
473
+ remove_check_constraints(table_name, column_name)
474
+ remove_default_constraint(table_name, column_name)
475
+ remove_indexes(table_name, column_name)
476
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
477
+ end
478
+ remove_sqlserver_columns_cache_for(table_name)
479
+ end
480
+
481
+ def change_column(table_name, column_name, type, options = {})
482
+ sql_commands = []
483
+ 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])}"
484
+ change_column_sql << " NOT NULL" if options[:null] == false
485
+ sql_commands << change_column_sql
486
+ if options_include_default?(options)
487
+ remove_default_constraint(table_name, column_name)
488
+ 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)}"
489
+ end
490
+ sql_commands.each { |c| do_execute(c) }
491
+ remove_sqlserver_columns_cache_for(table_name)
492
+ end
493
+
494
+ def change_column_default(table_name, column_name, default)
495
+ remove_default_constraint(table_name, column_name)
496
+ 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)}"
497
+ remove_sqlserver_columns_cache_for(table_name)
498
+ end
499
+
500
+ def rename_column(table_name, column_name, new_column_name)
501
+ column_for(table_name,column_name)
502
+ do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
503
+ remove_sqlserver_columns_cache_for(table_name)
504
+ end
505
+
506
+ def remove_index(table_name, options = {})
507
+ do_execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
508
+ end
509
+
510
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
511
+ limit = nil unless self.class.type_limitable?(type)
512
+ if type.to_s == 'integer'
513
+ case limit
514
+ when 1..2 then 'smallint'
515
+ when 3..4, nil then 'integer'
516
+ when 5..8 then 'bigint'
517
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
518
+ end
519
+ else
520
+ super
521
+ end
522
+ end
523
+
524
+ def add_order_by_for_association_limiting!(sql, options)
525
+ # Disertation http://gist.github.com/24073
526
+ # Information http://weblogs.sqlteam.com/jeffs/archive/2007/12/13/select-distinct-order-by-error.aspx
527
+ return sql if options[:order].blank?
528
+ columns = sql.match(/SELECT\s+DISTINCT(.*)FROM/)[1].strip
529
+ sql.sub!(/SELECT\s+DISTINCT/,'SELECT')
530
+ sql << "GROUP BY #{columns} ORDER BY #{order_to_min_set(options[:order])}"
531
+ end
532
+
533
+ def change_column_null(table_name, column_name, null, default = nil)
534
+ column = column_for(table_name,column_name)
535
+ unless null || default.nil?
536
+ do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
537
+ end
538
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql column.type, column.limit, column.precision, column.scale}"
539
+ sql << " NOT NULL" unless null
540
+ do_execute sql
541
+ end
542
+
543
+ def pk_and_sequence_for(table_name)
544
+ idcol = identity_column(table_name)
545
+ idcol ? [idcol.name,nil] : nil
546
+ end
547
+
548
+ # RAKE UTILITY METHODS =====================================#
549
+
550
+ def recreate_database(name)
551
+ existing_database = current_database.to_s
552
+ if name.to_s == existing_database
553
+ do_execute 'USE master'
554
+ end
555
+ drop_database(name)
556
+ create_database(name)
557
+ ensure
558
+ do_execute "USE #{existing_database}" if name.to_s == existing_database
559
+ end
560
+
561
+ def drop_database(name)
562
+ retry_count = 0
563
+ max_retries = 1
564
+ begin
565
+ do_execute "DROP DATABASE #{name}"
566
+ rescue ActiveRecord::StatementInvalid => err
567
+ # Remove existing connections and rollback any transactions if we received the message
568
+ # 'Cannot drop the database 'test' because it is currently in use'
569
+ if err.message =~ /because it is currently in use/
570
+ raise if retry_count >= max_retries
571
+ retry_count += 1
572
+ remove_database_connections_and_rollback(name)
573
+ retry
574
+ else
575
+ raise
576
+ end
577
+ end
578
+ end
579
+
580
+ def create_database(name)
581
+ do_execute "CREATE DATABASE #{name}"
582
+ end
583
+
584
+ def current_database
585
+ select_value 'SELECT DB_NAME()'
586
+ end
587
+
588
+ def remove_database_connections_and_rollback(name)
589
+ # This should disconnect all other users and rollback any transactions for SQL 2000 and 2005
590
+ # http://sqlserver2000.databases.aspfaq.com/how-do-i-drop-a-sql-server-database.html
591
+ do_execute "ALTER DATABASE #{name} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
592
+ end
593
+
594
+
595
+
596
+ protected
597
+
598
+ # DATABASE STATEMENTS ======================================
599
+
600
+ def select(sql, name = nil, ignore_special_columns = false)
601
+ repair_special_columns(sql) unless ignore_special_columns
602
+ fields, rows = raw_select(sql,name)
603
+ rows.inject([]) do |results,row|
604
+ row_hash = {}
605
+ fields.each_with_index do |f, i|
606
+ row_hash[f] = row[i]
607
+ end
608
+ results << row_hash
609
+ end
610
+ end
611
+
612
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
613
+ set_utf8_values!(sql)
614
+ super || select_value("SELECT SCOPE_IDENTITY() AS Ident")
615
+ end
616
+
617
+ def update_sql(sql, name = nil)
618
+ set_utf8_values!(sql)
619
+ execute(sql, name)
620
+ select_value('SELECT @@ROWCOUNT AS AffectedRows')
621
+ end
622
+
623
+ def raw_execute(sql, name = nil, &block)
624
+ log(sql, name) do
625
+ if block_given?
626
+ raw_connection.execute(sql) { |handle| yield(handle) }
627
+ else
628
+ raw_connection.execute(sql)
629
+ end
630
+ end
631
+ end
632
+
633
+ def without_type_conversion
634
+ raw_connection.convert_types = false if raw_connection.respond_to?(:convert_types=)
635
+ yield
636
+ ensure
637
+ raw_connection.convert_types = true if raw_connection.respond_to?(:convert_types=)
638
+ end
639
+
640
+ def do_execute(sql,name=nil)
641
+ log(sql, name || 'EXECUTE') do
642
+ raw_connection.do(sql)
643
+ end
644
+ end
645
+
646
+ def raw_select(sql, name = nil)
647
+ handle = raw_execute(sql,name)
648
+ fields = handle.column_names
649
+ results = handle_as_array(handle)
650
+ rows = results.inject([]) do |rows,row|
651
+ row.each_with_index do |value, i|
652
+ # DEPRECATED in DBI 0.4.0 and above. Remove when 0.2.2 and lower is no longer supported.
653
+ if value.is_a? DBI::Timestamp
654
+ row[i] = value.to_sqlserver_string
655
+ end
656
+ end
657
+ rows << row
658
+ end
659
+ return fields, rows
660
+ end
661
+
662
+ def handle_as_array(handle)
663
+ array = handle.inject([]) do |rows,row|
664
+ rows << row.inject([]){ |values,value| values << value }
665
+ end
666
+ finish_statement_handle(handle)
667
+ array
668
+ end
669
+
670
+ def add_limit_offset_for_association_limiting!(sql, options)
671
+ sql.replace %|
672
+ SET NOCOUNT ON
673
+ DECLARE @row_number TABLE (row int identity(1,1), id int)
674
+ INSERT INTO @row_number (id)
675
+ #{sql}
676
+ SET NOCOUNT OFF
677
+ SELECT id FROM (
678
+ SELECT TOP #{options[:limit]} * FROM (
679
+ SELECT TOP #{options[:limit] + options[:offset]} * FROM @row_number ORDER BY row
680
+ ) AS tmp1 ORDER BY row DESC
681
+ ) AS tmp2 ORDER BY row
682
+ |.gsub(/[ \t\r\n]+/,' ')
683
+ end
684
+
685
+ # SCHEMA STATEMENTS ========================================#
686
+
687
+ def remove_check_constraints(table_name, column_name)
688
+ constraints = select_values("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{quote_string(table_name)}' and COLUMN_NAME = '#{quote_string(column_name)}'")
689
+ constraints.each do |constraint|
690
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
691
+ end
692
+ end
693
+
694
+ def remove_default_constraint(table_name, column_name)
695
+ constraints = select_values("SELECT def.name FROM sysobjects def, syscolumns col, sysobjects tab WHERE col.cdefault = def.id AND col.name = '#{quote_string(column_name)}' AND tab.name = '#{quote_string(table_name)}' AND col.id = tab.id")
696
+ constraints.each do |constraint|
697
+ do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(constraint)}"
698
+ end
699
+ end
700
+
701
+ def remove_indexes(table_name, column_name)
702
+ indexes(table_name).select{ |index| index.columns.include?(column_name.to_s) }.each do |index|
703
+ remove_index(table_name, {:name => index.name})
704
+ end
705
+ end
706
+
707
+ def default_name(table_name, column_name)
708
+ "DF_#{table_name}_#{column_name}"
709
+ end
710
+
711
+ # IDENTITY INSERTS =========================================#
712
+
713
+ def with_identity_insert_enabled(table_name, &block)
714
+ set_identity_insert(table_name, true)
715
+ yield
716
+ ensure
717
+ set_identity_insert(table_name, false)
718
+ end
719
+
720
+ def set_identity_insert(table_name, enable = true)
721
+ sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
722
+ do_execute(sql,'IDENTITY_INSERT')
723
+ rescue Exception => e
724
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
725
+ end
726
+
727
+ def query_requires_identity_insert?(sql)
728
+ if insert_sql?(sql)
729
+ table_name = get_table_name(sql)
730
+ id_column = identity_column(table_name)
731
+ id_column && sql =~ /INSERT[^(]+\([^)]*\[#{id_column.name}\][^)]*\)/i ? table_name : false
732
+ else
733
+ false
734
+ end
735
+ end
736
+
737
+ def identity_column(table_name)
738
+ columns(table_name).detect(&:is_identity?)
739
+ end
740
+
741
+ # HELPER METHODS ===========================================#
742
+
743
+ def insert_sql?(sql)
744
+ !(sql =~ /^\s*INSERT/i).nil?
745
+ end
746
+
747
+ def unqualify_table_name(table_name)
748
+ table_name.to_s.split('.').last.gsub(/[\[\]]/,'')
749
+ end
750
+
751
+ def unqualify_db_name(table_name)
752
+ table_names = table_name.to_s.split('.')
753
+ table_names.length == 3 ? table_names.first.tr('[]','') : nil
754
+ end
755
+
756
+ def get_table_name(sql)
757
+ if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
758
+ $1 || $2
759
+ elsif sql =~ /from\s+([^\(\s]+)\s*/i
760
+ $1
761
+ else
762
+ nil
763
+ end
764
+ end
765
+
766
+ def orders_and_dirs_set(order)
767
+ orders = order.sub('ORDER BY','').split(',').map(&:strip).reject(&:blank?)
768
+ orders_dirs = orders.map do |ord|
769
+ dir = nil
770
+ if match_data = ord.match(/\b(asc|desc)$/i)
771
+ dir = match_data[1]
772
+ ord.sub!(dir,'').strip!
773
+ dir.upcase!
774
+ end
775
+ [ord,dir]
776
+ end
777
+ end
778
+
779
+ def order_to_min_set(order)
780
+ orders_dirs = orders_and_dirs_set(order)
781
+ orders_dirs.map do |o,d|
782
+ "MIN(#{o}) #{d}".strip
783
+ end.join(', ')
784
+ end
785
+
786
+ def sql_for_association_limiting?(sql)
787
+ if md = sql.match(/^\s*SELECT(.*)FROM.*GROUP BY.*ORDER BY.*/im)
788
+ select_froms = md[1].split(',')
789
+ select_froms.size == 1 && !select_froms.first.include?('*')
790
+ end
791
+ end
792
+
793
+ def remove_sqlserver_columns_cache_for(table_name)
794
+ cache_key = unqualify_table_name(table_name)
795
+ @sqlserver_columns_cache[cache_key] = nil
796
+ end
797
+
798
+ def column_definitions(table_name)
799
+ db_name = unqualify_db_name(table_name)
800
+ table_name = unqualify_table_name(table_name)
801
+ # COL_LENGTH returns values that do not reflect how much data can be stored in certain data types.
802
+ # COL_LENGTH returns -1 for varchar(max), nvarchar(max), and varbinary(max)
803
+ # COL_LENGTH returns 16 for ntext, text, image types
804
+ sql = %{
805
+ SELECT
806
+ columns.COLUMN_NAME as name,
807
+ columns.DATA_TYPE as type,
808
+ CASE
809
+ WHEN columns.COLUMN_DEFAULT = '(null)' OR columns.COLUMN_DEFAULT = '(NULL)' THEN NULL
810
+ ELSE columns.COLUMN_DEFAULT
811
+ END as default_value,
812
+ columns.NUMERIC_SCALE as numeric_scale,
813
+ columns.NUMERIC_PRECISION as numeric_precision,
814
+ CASE
815
+ WHEN columns.DATA_TYPE IN ('nvarchar') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 1073741823
816
+ WHEN columns.DATA_TYPE IN ('varchar', 'varbinary') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 2147483647
817
+ WHEN columns.DATA_TYPE IN ('ntext') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 1073741823
818
+ WHEN columns.DATA_TYPE IN ('text', 'image') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 2147483647
819
+ ELSE COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME)
820
+ END as length,
821
+ CASE
822
+ WHEN columns.IS_NULLABLE = 'YES' THEN 1
823
+ ELSE NULL
824
+ end as is_nullable,
825
+ CASE
826
+ WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
827
+ ELSE 1
828
+ END as is_identity
829
+ FROM #{db_name}INFORMATION_SCHEMA.COLUMNS columns
830
+ WHERE columns.TABLE_NAME = '#{table_name}'
831
+ ORDER BY columns.ordinal_position
832
+ }.gsub(/[ \t\r\n]+/,' ')
833
+ results = without_type_conversion { select(sql,nil,true) }
834
+ results.collect do |ci|
835
+ ci.symbolize_keys!
836
+ ci[:type] = if ci[:type] =~ /numeric|decimal/i
837
+ "#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
838
+ else
839
+ "#{ci[:type]}(#{ci[:length]})"
840
+ end
841
+ ci[:table_name] = table_name
842
+ ci[:default_value] = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/)[1] if ci[:default_value]
843
+ ci[:null] = ci[:is_nullable].to_i == 1 ; ci.delete(:is_nullable)
844
+ ci
845
+ end
846
+ end
847
+
848
+ def column_for(table_name, column_name)
849
+ unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
850
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
851
+ end
852
+ column
853
+ end
854
+
855
+
856
+
857
+ def change_order_direction(order)
858
+ order.split(",").collect {|fragment|
859
+ case fragment
860
+ when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
861
+ when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
862
+ else String.new(fragment).split(',').join(' DESC,') + ' DESC'
863
+ end
864
+ }.join(",")
865
+ end
866
+
867
+ def special_columns(table_name)
868
+ columns(table_name).select(&:is_special?).map(&:name)
869
+ end
870
+
871
+ def repair_special_columns(sql)
872
+ special_cols = special_columns(get_table_name(sql))
873
+ for col in special_cols.to_a
874
+ sql.gsub!(/((\.|\s|\()\[?#{col.to_s}\]?)\s?=\s?/, '\1 LIKE ')
875
+ sql.gsub!(/ORDER BY #{col.to_s}/i, '')
876
+ end
877
+ sql
878
+ end
879
+
880
+ def utf8_columns(table_name)
881
+ columns(table_name).select(&:is_utf8?).map(&:name)
882
+ end
883
+
884
+ def set_utf8_values!(sql)
885
+ utf8_cols = utf8_columns(get_table_name(sql))
886
+ if sql =~ /^\s*UPDATE/i
887
+ utf8_cols.each do |col|
888
+ sql.gsub!("[#{col.to_s}] = '", "[#{col.to_s}] = N'")
889
+ end
890
+ elsif sql =~ /^\s*INSERT(?!.*DEFAULT VALUES\s*$)/i
891
+ # TODO This code should be simplified
892
+ # Get columns and values, split them into arrays, and store the original_values for when we need to replace them
893
+ columns_and_values = sql.scan(/\((.*?)\)/m).flatten
894
+ columns = columns_and_values.first.split(',')
895
+ values = columns_and_values[1].split(',')
896
+ original_values = values.dup
897
+ # Iterate columns that should be UTF8, and append an N to the value, if the value is not NULL
898
+ utf8_cols.each do |col|
899
+ columns.each_with_index do |column, idx|
900
+ values[idx] = " N#{values[idx].gsub(/^ /, '')}" if column =~ /\[#{col}\]/ and values[idx] !~ /^NULL$/
901
+ end
902
+ end
903
+ # Replace (in place) the SQL
904
+ sql.gsub!(original_values.join(','), values.join(','))
905
+ end
906
+ end
907
+
908
+ end #class SQLServerAdapter < AbstractAdapter
909
+
910
+ end #module ConnectionAdapters
911
+
912
+ end #module ActiveRecord
913
+