Class: ActiveRecord::ConnectionAdapters::SQLServerAdapter

Inherits:
AbstractAdapter
  • Object
show all
Defined in:
lib/active_record/connection_adapters/sqlserver_adapter.rb

Overview

In ADO mode, this adapter will ONLY work on Windows systems, since it relies on Win32OLE, which, to my knowledge, is only available on Windows.

This mode also relies on the ADO support in the DBI module. If you are using the one-click installer of Ruby, then you already have DBI installed, but the ADO module is NOT installed. You will need to get the latest source distribution of Ruby-DBI from ruby-dbi.sourceforge.net/ unzip it, and copy the file src/lib/dbd_ado/ADO.rb to X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb (you will more than likely need to create the ADO directory). Once you’ve installed that file, you are ready to go.

In ODBC mode, the adapter requires the ODBC support in the DBI module which requires the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing, and it is available at www.ch-werner.de/rubyodbc/

Options:

  • :mode – ADO or ODBC. Defaults to ADO.

  • :username – Defaults to sa.

  • :password – Defaults to empty string.

ADO specific options:

  • :host – Defaults to localhost.

  • :database – The name of the database. No default, must be provided.

ODBC specific options:

  • :dsn – Defaults to nothing.

ADO code tested on Windows 2000 and higher systems, running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.

ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63, unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.

Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux

Instance Method Summary collapse

Methods inherited from AbstractAdapter

#prefetch_primary_key?, #raw_connection, #requires_reloading?, #reset_runtime, #supports_count_distinct?, #verify!

Methods included from Quoting

#quoted_date, #quoted_false, #quoted_true

Methods included from SchemaStatements

#add_column_options!, #add_index, #add_order_by_for_association_limiting!, #change_column_default, #create_table, #distinct, #drop_table, #dump_schema_information, #index_name, #initialize_schema_information, #structure_dump, #table_alias_for, #table_alias_length

Constructor Details

#initialize(connection, logger, connection_options = nil) ⇒ SQLServerAdapter

Returns a new instance of SQLServerAdapter.



189
190
191
192
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 189

def initialize(connection, logger, connection_options=nil)
  super(connection, logger)
  @connection_options = connection_options
end

Instance Method Details

#active?Boolean

Returns true if the connection is active.

Returns:

  • (Boolean)


234
235
236
237
238
239
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 234

def active?
  @connection.execute("SELECT 1").finish
  true
rescue DBI::DatabaseError, DBI::InterfaceError
  false
end

#adapter_nameObject



211
212
213
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 211

def adapter_name
  'SQLServer'
end

#add_column(table_name, column_name, type, options = {}) ⇒ Object

Adds a new column to the named table. See TableDefinition#column for details of the options you can use.



448
449
450
451
452
453
454
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 448

def add_column(table_name, column_name, type, options = {})
  add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
  add_column_options!(add_column_sql, options)
  # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
  # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date       
  execute(add_column_sql)
end

#add_limit_offset!(sql, options) ⇒ Object



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 364

def add_limit_offset!(sql, options)
  if options[:limit] and options[:offset]
    total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally")[0][:TotalRows].to_i
    if (options[:limit] + options[:offset]) >= total_rows
      options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
    end
    sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
    sql << ") AS tmp1"
    if options[:order]
      options[:order] = options[:order].split(',').map do |field|
        parts = field.split(" ")
        tc = parts[0]
        if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
          tc.gsub!(/\./, '\\.\\[')
          tc << '\\]'
        end
        if sql =~ /#{tc} AS (t\d_r\d\d?)/
          parts[0] = $1
        elsif parts[0] =~ /\w+\.(\w+)/
          parts[0] = $1
        end
        parts.join(' ')
      end.join(', ')
      sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
    else
      sql << " ) AS tmp2"
    end
  elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
    sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
      "SELECT#{$1} TOP #{options[:limit]}"
    end unless options[:limit].nil?
  end
end

#begin_db_transactionObject



326
327
328
329
330
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 326

def begin_db_transaction
  @connection["AutoCommit"] = false
rescue Exception => e
  @connection["AutoCommit"] = true
end

#change_column(table_name, column_name, type, options = {}) ⇒ Object

:nodoc:



460
461
462
463
464
465
466
467
468
469
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 460

def change_column(table_name, column_name, type, options = {}) #:nodoc:
  sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
  if options_include_default?(options)
    remove_default_constraint(table_name, column_name)
    sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
  end
  sql_commands.each {|c|
    execute(c)
  }
end

#columns(table_name, name = nil) ⇒ Object



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 256

def columns(table_name, name = nil)
  return [] if table_name.blank?
  table_name = table_name.to_s if table_name.is_a?(Symbol)
  table_name = table_name.split('.')[-1] unless table_name.nil?
  table_name = table_name.gsub(/[\[\]]/, '')
  sql = %Q{
    SELECT 
      cols.COLUMN_NAME as ColName,  
      cols.COLUMN_DEFAULT as DefaultValue,
      cols.NUMERIC_SCALE as numeric_scale,
      cols.NUMERIC_PRECISION as numeric_precision, 
      cols.DATA_TYPE as ColType, 
      cols.IS_NULLABLE As IsNullable,  
      COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,  
      COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,  
      cols.NUMERIC_SCALE as Scale 
    FROM INFORMATION_SCHEMA.COLUMNS cols 
    WHERE cols.TABLE_NAME = '#{table_name}'   
  }
  # Comment out if you want to have the Columns select statment logged.
  # Personally, I think it adds unnecessary bloat to the log. 
  # If you do comment it out, make sure to un-comment the "result" line that follows
  result = log(sql, name) { @connection.select_all(sql) }
  #result = @connection.select_all(sql)
  columns = []
  result.each do |field|
    default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
    if field[:ColType] =~ /numeric|decimal/i
      type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
    else
      type = "#{field[:ColType]}(#{field[:Length]})"
    end
    is_identity = field[:IsIdentity] == 1
    is_nullable = field[:IsNullable] == 'YES'
    columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
  end
  columns
end

#commit_db_transactionObject



332
333
334
335
336
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 332

def commit_db_transaction
  @connection.commit
ensure
  @connection["AutoCommit"] = true
end

#create_database(name) ⇒ Object



407
408
409
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 407

def create_database(name)
  execute "CREATE DATABASE #{name}"
end

#current_databaseObject



411
412
413
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 411

def current_database
  @connection.select_one("select DB_NAME()")[0]
end

#disconnect!Object

Disconnects from the database



252
253
254
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 252

def disconnect!
  @connection.disconnect rescue nil
end

#drop_database(name) ⇒ Object



403
404
405
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 403

def drop_database(name)
  execute "DROP DATABASE #{name}"
end

#execute(sql, name = nil) ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 308

def execute(sql, name = nil)
  if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
    log(sql, name) do
      with_identity_insert_enabled(table_name) do 
        @connection.execute(sql) do |handle|
          yield(handle) if block_given?
        end
      end
    end
  else
    log(sql, name) do
      @connection.execute(sql) do |handle|
        yield(handle) if block_given?
      end
    end
  end
end

#indexes(table_name, name = nil) ⇒ Object



425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 425

def indexes(table_name, name = nil)
  ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = false
  indexes = []        
  execute("EXEC sp_helpindex '#{table_name}'", name) do |sth|
    sth.each do |index| 
      unique = index[1] =~ /unique/
      primary = index[1] =~ /primary key/
      if !primary
        indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
      end
    end
  end
  indexes
  ensure
    ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
end

#insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) ⇒ Object



295
296
297
298
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 295

def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  execute(sql, name)
  id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
end

#native_database_typesObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 194

def native_database_types
  {
    :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
    :string      => { :name => "varchar", :limit => 255  },
    :text        => { :name => "text" },
    :integer     => { :name => "int" },
    :float       => { :name => "float", :limit => 8 },
    :decimal     => { :name => "decimal" },
    :datetime    => { :name => "datetime" },
    :timestamp   => { :name => "datetime" },
    :time        => { :name => "datetime" },
    :date        => { :name => "datetime" },
    :binary      => { :name => "image"},
    :boolean     => { :name => "bit"}
  }
end

#quote(value, column = nil) ⇒ Object



344
345
346
347
348
349
350
351
352
353
354
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 344

def quote(value, column = nil)
  return value.quoted_id if value.respond_to?(:quoted_id)

  case value
    when TrueClass             then '1'
    when FalseClass            then '0'
    when Time, DateTime        then "'#{value.strftime("%Y%m%d %H:%M:%S")}'"
    when Date                  then "'#{value.strftime("%Y%m%d")}'"
    else                       super
  end
end

#quote_column_name(name) ⇒ Object



360
361
362
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 360

def quote_column_name(name)
  "[#{name}]"
end

#quote_string(string) ⇒ Object



356
357
358
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 356

def quote_string(string)
  string.gsub(/\'/, "''")
end

#reconnect!Object

Reconnects to the database, returns false if no connection could be made.



242
243
244
245
246
247
248
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 242

def reconnect!
  disconnect!
  @connection = DBI.connect(*@connection_options)
rescue DBI::DatabaseError => e
  @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
  false
end

#recreate_database(name) ⇒ Object



398
399
400
401
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 398

def recreate_database(name)
  drop_database(name)
  create_database(name)
end

#remove_check_constraints(table_name, column_name) ⇒ Object



485
486
487
488
489
490
491
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 485

def remove_check_constraints(table_name, column_name)
  # TODO remove all constraints in single method
  constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
  constraints.each do |constraint|
    execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
  end
end

#remove_column(table_name, column_name) ⇒ Object



471
472
473
474
475
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 471

def remove_column(table_name, column_name)
  remove_check_constraints(table_name, column_name)
  remove_default_constraint(table_name, column_name)
  execute "ALTER TABLE [#{table_name}] DROP COLUMN [#{column_name}]"
end

#remove_default_constraint(table_name, column_name) ⇒ Object



477
478
479
480
481
482
483
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 477

def remove_default_constraint(table_name, column_name)
  constraints = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
  
  constraints.each do |constraint|
    execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
  end
end

#remove_index(table_name, options = {}) ⇒ Object



493
494
495
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 493

def remove_index(table_name, options = {})
  execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
end

#rename_column(table, column, new_column_name) ⇒ Object



456
457
458
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 456

def rename_column(table, column, new_column_name)
  execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
end

#rename_table(name, new_name) ⇒ Object



442
443
444
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 442

def rename_table(name, new_name)
  execute "EXEC sp_rename '#{name}', '#{new_name}'"
end

#rollback_db_transactionObject



338
339
340
341
342
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 338

def rollback_db_transaction
  @connection.rollback
ensure
  @connection["AutoCommit"] = true
end

#supports_migrations?Boolean

:nodoc:

Returns:

  • (Boolean)


215
216
217
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 215

def supports_migrations? #:nodoc:
  true
end

#tables(name = nil) ⇒ Object



415
416
417
418
419
420
421
422
423
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 415

def tables(name = nil)
  execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", name) do |sth|
    sth.inject([]) do |tables, field|
      table_name = field[0]
      tables << table_name unless table_name == 'dtproperties'
      tables
    end
  end
end

#type_to_sql(type, limit = nil, precision = nil, scale = nil) ⇒ Object

:nodoc:



219
220
221
222
223
224
225
226
227
228
229
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 219

def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
  return super unless type.to_s == 'integer'

  if limit.nil? || limit == 4
    'integer'
  elsif limit < 4
    'smallint'
  else
    'bigint'
  end
end

#update(sql, name = nil) ⇒ Object Also known as: delete



300
301
302
303
304
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 300

def update(sql, name = nil)
  execute(sql, name) do |handle|
    handle.rows
  end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]        
end