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, #reset_runtime, #supports_count_distinct?, #verify!

Methods included from Quoting

#quoted_date

Methods included from DatabaseStatements

#add_limit!, #default_sequence_name, #reset_sequence!, #select_value, #select_values, #transaction

Methods included from SchemaStatements

#add_column, #add_column_options!, #add_index, #change_column_default, #create_table, #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.



178
179
180
181
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 178

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)


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

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

#adapter_nameObject



199
200
201
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 199

def adapter_name
  'SQLServer'
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
# 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\b/i, "SELECT 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/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT 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
        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



312
313
314
315
316
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 312

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

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

:nodoc:



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

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])}"]
  if options[:default]
    remove_default_constraint(table_name, column_name)
    sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
  end
  sql_commands.each {|c|
    execute(c)
  }
end

#columns(table_name, name = nil) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 242

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?
  sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, IS_NULLABLE As IsNullable, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE 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]
    type = "#{field[:ColType]}(#{field[:Length]})"
    is_identity = field[:IsIdentity] == 1
    is_nullable = field[:IsNullable] == 'YES'
    columns << ColumnWithIdentity.new(field[:ColName], default, type, is_identity, is_nullable, field[:Scale])
  end
  columns
end

#commit_db_transactionObject



318
319
320
321
322
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 318

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

#create_database(name) ⇒ Object



405
406
407
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 405

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

#current_databaseObject



409
410
411
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 409

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

#disconnect!Object

Disconnects from the database



228
229
230
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 228

def disconnect!
  @connection.disconnect rescue nil
end

#drop_database(name) ⇒ Object



401
402
403
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 401

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

#execute(sql, name = nil) ⇒ Object



294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 294

def execute(sql, name = nil)
  if sql =~ /^\s*INSERT/i
    insert(sql, name)
  elsif sql =~ /^\s*UPDATE|^\s*DELETE/i
    log(sql, name) do
      @connection.execute(sql)
      retVal = select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
    end
  else
    log(sql, name) { @connection.execute(sql) }
  end
end

#indexes(table_name, name = nil) ⇒ Object



421
422
423
424
425
426
427
428
429
430
431
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 421

def indexes(table_name, name = nil)
  indexes = []
  execute("EXEC sp_helpindex #{table_name}", name).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
  indexes
end

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



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
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 263

def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
  begin
    table_name = get_table_name(sql)
    col = get_identity_column(table_name)
    ii_enabled = false

    if col != nil
      if query_contains_identity_column(sql, col)
        begin
          execute enable_identity_insert(table_name, true)
          ii_enabled = true
        rescue Exception => e
          raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
        end
      end
    end
    log(sql, name) do
      @connection.execute(sql)
      id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
    end
  ensure
    if ii_enabled
      begin
        execute enable_identity_insert(table_name, false)
      rescue Exception => e
        raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
      end
    end
  end
end

#native_database_typesObject



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 183

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 },
    :datetime    => { :name => "datetime" },
    :timestamp   => { :name => "datetime" },
    :time        => { :name => "datetime" },
    :date        => { :name => "datetime" },
    :binary      => { :name => "image"},
    :boolean     => { :name => "bit"}
  }
end

#quote(value, column = nil) ⇒ Object



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 330

def quote(value, column = nil)
  case value
    when String                
      if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
        "'#{quote_string(column.class.string_to_binary(value))}'"
      else
        "'#{quote_string(value)}'"
      end
    when NilClass              then "NULL"
    when TrueClass             then '1'
    when FalseClass            then '0'
    when Float, Fixnum, Bignum then value.to_s
    when Date                  then "'#{value.to_s}'" 
    when Time, DateTime        then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
    else                            "'#{quote_string(value.to_yaml)}'"
  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



348
349
350
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 348

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

#quoted_falseObject



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

def quoted_false
  "0"
end

#quoted_trueObject



352
353
354
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 352

def quoted_true
  "1"
end

#reconnect!Object

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



218
219
220
221
222
223
224
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 218

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



396
397
398
399
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 396

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

#remove_column(table_name, column_name) ⇒ Object



437
438
439
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 437

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

#remove_default_constraint(table_name, column_name) ⇒ Object



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

def remove_default_constraint(table_name, column_name)
  defaults = 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"
  defaults.each {|constraint|
    execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
  }
end

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



468
469
470
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 468

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

#rename_column(table, column, new_column_name) ⇒ Object



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

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

#rename_table(name, new_name) ⇒ Object



433
434
435
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 433

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

#rollback_db_transactionObject



324
325
326
327
328
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 324

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

#select_all(sql, name = nil) ⇒ Object



232
233
234
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 232

def select_all(sql, name = nil)
  select(sql, name)
end

#select_one(sql, name = nil) ⇒ Object



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

def select_one(sql, name = nil)
  add_limit!(sql, :limit => 1)
  result = select(sql, name)
  result.nil? ? nil : result.first
end

#supports_migrations?Boolean

:nodoc:

Returns:

  • (Boolean)


203
204
205
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 203

def supports_migrations? #:nodoc:
  true
end

#tables(name = nil) ⇒ Object



413
414
415
416
417
418
419
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 413

def tables(name = nil)
  execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).inject([]) do |tables, field|
    table_name = field[0]
    tables << table_name unless table_name == 'dtproperties'
    tables
  end
end

#type_to_sql(type, limit = nil) ⇒ Object

:nodoc:



472
473
474
475
476
477
478
479
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 472

def type_to_sql(type, limit = nil) #:nodoc:
  native = native_database_types[type]
  # if there's no :limit in the default type definition, assume that type doesn't support limits
  limit = limit || native[:limit]
  column_type_sql = native[:name]
  column_type_sql << "(#{limit})" if limit
  column_type_sql
end

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



307
308
309
# File 'lib/active_record/connection_adapters/sqlserver_adapter.rb', line 307

def update(sql, name = nil)
  execute(sql, name)
end