mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Refactored the AbstractAdapter to be a lot less scary. Cleaned up the docs and style for the OSS adapters
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2339 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
ea65465422
commit
b3df95985a
11 changed files with 799 additions and 717 deletions
|
@ -0,0 +1,126 @@
|
|||
module ActiveRecord
|
||||
class Base
|
||||
class ConnectionSpecification #:nodoc:
|
||||
attr_reader :config, :adapter_method
|
||||
def initialize (config, adapter_method)
|
||||
@config, @adapter_method = config, adapter_method
|
||||
end
|
||||
end
|
||||
|
||||
# The class -> [adapter_method, config] map
|
||||
@@defined_connections = {}
|
||||
|
||||
# Establishes the connection to the database. Accepts a hash as input where
|
||||
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
||||
# example for regular databases (MySQL, Postgresql, etc):
|
||||
#
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# :adapter => "mysql",
|
||||
# :host => "localhost",
|
||||
# :username => "myuser",
|
||||
# :password => "mypass",
|
||||
# :database => "somedatabase"
|
||||
# )
|
||||
#
|
||||
# Example for SQLite database:
|
||||
#
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# :adapter => "sqlite",
|
||||
# :dbfile => "path/to/dbfile"
|
||||
# )
|
||||
#
|
||||
# Also accepts keys as strings (for parsing from yaml for example):
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# "adapter" => "sqlite",
|
||||
# "dbfile" => "path/to/dbfile"
|
||||
# )
|
||||
#
|
||||
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
||||
# may be returned on an error.
|
||||
def self.establish_connection(spec = nil)
|
||||
case spec
|
||||
when nil
|
||||
raise AdapterNotSpecified unless defined? RAILS_ENV
|
||||
establish_connection(RAILS_ENV)
|
||||
when ConnectionSpecification
|
||||
@@defined_connections[self] = spec
|
||||
when Symbol, String
|
||||
if configuration = configurations[spec.to_s]
|
||||
establish_connection(configuration)
|
||||
else
|
||||
raise AdapterNotSpecified, "#{spec} database is not configured"
|
||||
end
|
||||
else
|
||||
spec = spec.symbolize_keys
|
||||
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
||||
adapter_method = "#{spec[:adapter]}_connection"
|
||||
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
|
||||
remove_connection
|
||||
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
||||
end
|
||||
end
|
||||
|
||||
def self.active_connections #:nodoc:
|
||||
if threaded_connections
|
||||
Thread.current['active_connections'] ||= {}
|
||||
else
|
||||
@@active_connections ||= {}
|
||||
end
|
||||
end
|
||||
|
||||
# Locate the connection of the nearest super class. This can be an
|
||||
# active or defined connections: if it is the latter, it will be
|
||||
# opened and set as the active connection for the class it was defined
|
||||
# for (not necessarily the current class).
|
||||
def self.retrieve_connection #:nodoc:
|
||||
klass = self
|
||||
ar_super = ActiveRecord::Base.superclass
|
||||
until klass == ar_super
|
||||
if conn = active_connections[klass]
|
||||
return conn
|
||||
elsif conn = @@defined_connections[klass]
|
||||
klass.connection = conn
|
||||
return self.connection
|
||||
end
|
||||
klass = klass.superclass
|
||||
end
|
||||
raise ConnectionNotEstablished
|
||||
end
|
||||
|
||||
# Returns true if a connection that's accessible to this class have already been opened.
|
||||
def self.connected?
|
||||
klass = self
|
||||
until klass == ActiveRecord::Base.superclass
|
||||
if active_connections[klass]
|
||||
return true
|
||||
else
|
||||
klass = klass.superclass
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
# Remove the connection for this class. This will close the active
|
||||
# connection and the defined connection (if they exist). The result
|
||||
# can be used as argument for establish_connection, for easy
|
||||
# re-establishing of the connection.
|
||||
def self.remove_connection(klass=self)
|
||||
conn = @@defined_connections[klass]
|
||||
@@defined_connections.delete(klass)
|
||||
active_connections[klass] = nil
|
||||
conn.config if conn
|
||||
end
|
||||
|
||||
# Set the connection for the class.
|
||||
def self.connection=(spec)
|
||||
raise ConnectionNotEstablished unless spec
|
||||
conn = self.send(spec.adapter_method, spec.config)
|
||||
active_connections[self] = conn
|
||||
end
|
||||
|
||||
# Converts all strings in a hash to symbols.
|
||||
def self.symbolize_strings_in_hash(hash) #:nodoc:
|
||||
hash.symbolize_keys
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
# TODO: Document me!
|
||||
module DatabaseStatements
|
||||
# Returns an array of record hashes with the column names as a keys and fields as values.
|
||||
def select_all(sql, name = nil) end
|
||||
|
||||
# Returns a record hash with the column names as a keys and fields as values.
|
||||
def select_one(sql, name = nil) end
|
||||
|
||||
# Returns a single value from a record
|
||||
def select_value(sql, name = nil)
|
||||
result = select_one(sql, name)
|
||||
result.nil? ? nil : result.values.first
|
||||
end
|
||||
|
||||
# Returns an array of the values of the first column in a select:
|
||||
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
||||
def select_values(sql, name = nil)
|
||||
result = select_all(sql, name)
|
||||
result.map{ |v| v.values.first }
|
||||
end
|
||||
|
||||
# Executes the statement
|
||||
def execute(sql, name = nil) end
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) end
|
||||
|
||||
# Executes the update statement and returns the number of rows affected.
|
||||
def update(sql, name = nil) end
|
||||
|
||||
# Executes the delete statement and returns the number of rows affected.
|
||||
def delete(sql, name = nil) end
|
||||
|
||||
# Wrap a block in a transaction. Returns result of block.
|
||||
def transaction(start_db_transaction = true)
|
||||
begin
|
||||
if block_given?
|
||||
begin_db_transaction if start_db_transaction
|
||||
result = yield
|
||||
commit_db_transaction if start_db_transaction
|
||||
result
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
rollback_db_transaction if start_db_transaction
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# Begins the transaction (and turns off auto-committing).
|
||||
def begin_db_transaction() end
|
||||
|
||||
# Commits the transaction (and turns on auto-committing).
|
||||
def commit_db_transaction() end
|
||||
|
||||
# Rolls back the transaction (and turns on auto-committing). Must be done if the transaction block
|
||||
# raises an exception or returns false.
|
||||
def rollback_db_transaction() end
|
||||
|
||||
def add_limit!(sql, options) #:nodoc:
|
||||
return unless options
|
||||
add_limit_offset!(sql, options)
|
||||
end
|
||||
|
||||
def add_limit_offset!(sql, options) #:nodoc:
|
||||
return if options[:limit].nil?
|
||||
sql << " LIMIT #{options[:limit]}"
|
||||
sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
# TODO: Document me!
|
||||
module Quoting
|
||||
def quote(value, column = nil)
|
||||
case value
|
||||
when String
|
||||
if column && column.type == :binary
|
||||
"'#{quote_string(column.string_to_binary(value))}'" # ' (for ruby-mode)
|
||||
elsif column && [:integer, :float].include?(column.type)
|
||||
value.to_s
|
||||
else
|
||||
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
||||
end
|
||||
when NilClass then "NULL"
|
||||
when TrueClass then (column && column.type == :boolean ? quoted_true : "1")
|
||||
when FalseClass then (column && column.type == :boolean ? quoted_false : "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
|
||||
|
||||
def quote_string(s)
|
||||
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
||||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
name
|
||||
end
|
||||
|
||||
def quoted_true
|
||||
"'t'"
|
||||
end
|
||||
|
||||
def quoted_false
|
||||
"'f'"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,168 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters #:nodoc:
|
||||
class Column #:nodoc:
|
||||
attr_reader :name, :default, :type, :limit, :null
|
||||
# The name should contain the name of the column, such as "name" in "name varchar(250)"
|
||||
# The default should contain the type-casted default of the column, such as 1 in "count int(11) DEFAULT 1"
|
||||
# The type parameter should either contain :integer, :float, :datetime, :date, :text, or :string
|
||||
# The sql_type is just used for extracting the limit, such as 10 in "varchar(10)"
|
||||
def initialize(name, default, sql_type = nil, null = true)
|
||||
@name, @type, @null = name, simplified_type(sql_type), null
|
||||
# have to do this one separately because type_cast depends on #type
|
||||
@default = type_cast(default)
|
||||
@limit = extract_limit(sql_type) unless sql_type.nil?
|
||||
end
|
||||
|
||||
def klass
|
||||
case type
|
||||
when :integer then Fixnum
|
||||
when :float then Float
|
||||
when :datetime then Time
|
||||
when :date then Date
|
||||
when :timestamp then Time
|
||||
when :time then Time
|
||||
when :text, :string then String
|
||||
when :binary then String
|
||||
when :boolean then Object
|
||||
end
|
||||
end
|
||||
|
||||
def type_cast(value)
|
||||
if value.nil? then return nil end
|
||||
case type
|
||||
when :string then value
|
||||
when :text then value
|
||||
when :integer then value.to_i rescue value ? 1 : 0
|
||||
when :float then value.to_f
|
||||
when :datetime then string_to_time(value)
|
||||
when :timestamp then string_to_time(value)
|
||||
when :time then string_to_dummy_time(value)
|
||||
when :date then string_to_date(value)
|
||||
when :binary then binary_to_string(value)
|
||||
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
|
||||
else value
|
||||
end
|
||||
end
|
||||
|
||||
def human_name
|
||||
Base.human_attribute_name(@name)
|
||||
end
|
||||
|
||||
def string_to_binary(value)
|
||||
value
|
||||
end
|
||||
|
||||
def binary_to_string(value)
|
||||
value
|
||||
end
|
||||
|
||||
private
|
||||
def string_to_date(string)
|
||||
return string unless string.is_a?(String)
|
||||
date_array = ParseDate.parsedate(string.to_s)
|
||||
# treat 0000-00-00 as nil
|
||||
Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
|
||||
end
|
||||
|
||||
def string_to_time(string)
|
||||
return string unless string.is_a?(String)
|
||||
time_array = ParseDate.parsedate(string.to_s).compact
|
||||
# treat 0000-00-00 00:00:00 as nil
|
||||
Time.send(Base.default_timezone, *time_array) rescue nil
|
||||
end
|
||||
|
||||
def string_to_dummy_time(string)
|
||||
return string unless string.is_a?(String)
|
||||
time_array = ParseDate.parsedate(string.to_s)
|
||||
# pad the resulting array with dummy date information
|
||||
time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
|
||||
Time.send(Base.default_timezone, *time_array) rescue nil
|
||||
end
|
||||
|
||||
def extract_limit(sql_type)
|
||||
$1.to_i if sql_type =~ /\((.*)\)/
|
||||
end
|
||||
|
||||
def simplified_type(field_type)
|
||||
case field_type
|
||||
when /int/i
|
||||
:integer
|
||||
when /float|double|decimal|numeric/i
|
||||
:float
|
||||
when /datetime/i
|
||||
:datetime
|
||||
when /timestamp/i
|
||||
:timestamp
|
||||
when /time/i
|
||||
:time
|
||||
when /date/i
|
||||
:date
|
||||
when /clob/i, /text/i
|
||||
:text
|
||||
when /blob/i, /binary/i
|
||||
:binary
|
||||
when /char/i, /string/i
|
||||
:string
|
||||
when /boolean/i
|
||||
:boolean
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
|
||||
end
|
||||
|
||||
class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null) #:nodoc:
|
||||
def to_sql
|
||||
column_sql = "#{name} #{type_to_sql(type.to_sym, limit)}"
|
||||
add_column_options!(column_sql, :null => null, :default => default)
|
||||
column_sql
|
||||
end
|
||||
alias to_s :to_sql
|
||||
|
||||
private
|
||||
def type_to_sql(name, limit)
|
||||
base.type_to_sql(name, limit) rescue name
|
||||
end
|
||||
|
||||
def add_column_options!(sql, options)
|
||||
base.add_column_options!(sql, options.merge(:column => self))
|
||||
end
|
||||
end
|
||||
|
||||
class TableDefinition #:nodoc:
|
||||
attr_accessor :columns
|
||||
|
||||
def initialize(base)
|
||||
@columns = []
|
||||
@base = base
|
||||
end
|
||||
|
||||
def primary_key(name)
|
||||
column(name, native[:primary_key])
|
||||
end
|
||||
|
||||
def [](name)
|
||||
@columns.find {|column| column.name == name}
|
||||
end
|
||||
|
||||
def column(name, type, options = {})
|
||||
column = self[name] || ColumnDefinition.new(@base, name, type)
|
||||
column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
|
||||
column.default = options[:default]
|
||||
column.null = options[:null]
|
||||
@columns << column unless @columns.include? column
|
||||
self
|
||||
end
|
||||
|
||||
def to_sql
|
||||
@columns * ', '
|
||||
end
|
||||
|
||||
private
|
||||
def native
|
||||
@base.native_database_types
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,137 @@
|
|||
module ActiveRecord
|
||||
module ConnectionAdapters # :nodoc:
|
||||
# TODO: Document me!
|
||||
module SchemaStatements
|
||||
def native_database_types #:nodoc:
|
||||
{}
|
||||
end
|
||||
|
||||
# def tables(name = nil) end
|
||||
|
||||
# Returns an array of indexes for the given table.
|
||||
# def indexes(table_name, name = nil) end
|
||||
|
||||
# Returns an array of column objects for the table specified by +table_name+.
|
||||
def columns(table_name, name = nil) end
|
||||
|
||||
|
||||
def create_table(name, options = {})
|
||||
table_definition = TableDefinition.new(self)
|
||||
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
|
||||
|
||||
yield table_definition
|
||||
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
||||
create_sql << "#{name} ("
|
||||
create_sql << table_definition.to_sql
|
||||
create_sql << ") #{options[:options]}"
|
||||
execute create_sql
|
||||
end
|
||||
|
||||
def drop_table(name)
|
||||
execute "DROP TABLE #{name}"
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {})
|
||||
add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
|
||||
add_column_options!(add_column_sql, options)
|
||||
execute(add_column_sql)
|
||||
end
|
||||
|
||||
def remove_column(table_name, column_name)
|
||||
execute "ALTER TABLE #{table_name} DROP #{column_name}"
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {})
|
||||
raise NotImplementedError, "change_column is not implemented"
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default)
|
||||
raise NotImplementedError, "change_column_default is not implemented"
|
||||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name)
|
||||
raise NotImplementedError, "rename_column is not implemented"
|
||||
end
|
||||
|
||||
# Create a new index on the given table. By default, it will be named
|
||||
# <code>"#{table_name}_#{column_name.to_a.first}_index"</code>, but you
|
||||
# can explicitly name the index by passing <code>:name => "..."</code>
|
||||
# as the last parameter. Unique indexes may be created by passing
|
||||
# <code>:unique => true</code>.
|
||||
def add_index(table_name, column_name, options = {})
|
||||
index_name = "#{table_name}_#{column_name.to_a.first}_index"
|
||||
|
||||
if Hash === options # legacy support, since this param was a string
|
||||
index_type = options[:unique] ? "UNIQUE" : ""
|
||||
index_name = options[:name] || index_name
|
||||
else
|
||||
index_type = options
|
||||
end
|
||||
|
||||
execute "CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{column_name.to_a.join(", ")})"
|
||||
end
|
||||
|
||||
# Remove the given index from the table.
|
||||
#
|
||||
# remove_index :my_table, :column => :foo
|
||||
# remove_index :my_table, :name => :my_index_on_foo
|
||||
#
|
||||
# The first version will remove the index named
|
||||
# <code>"#{my_table}_#{column}_index"</code> from the table. The
|
||||
# second removes the named column from the table.
|
||||
def remove_index(table_name, options = {})
|
||||
if Hash === options # legacy support
|
||||
if options[:column]
|
||||
index_name = "#{table_name}_#{options[:column]}_index"
|
||||
elsif options[:name]
|
||||
index_name = options[:name]
|
||||
else
|
||||
raise ArgumentError, "You must specify the index name"
|
||||
end
|
||||
else
|
||||
index_name = "#{table_name}_#{options}_index"
|
||||
end
|
||||
|
||||
execute "DROP INDEX #{index_name} ON #{table_name}"
|
||||
end
|
||||
|
||||
|
||||
# Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database.
|
||||
def structure_dump #:nodoc:
|
||||
end
|
||||
|
||||
def initialize_schema_information #:nodoc:
|
||||
begin
|
||||
execute "CREATE TABLE schema_info (version #{type_to_sql(:integer)})"
|
||||
execute "INSERT INTO schema_info (version) VALUES(0)"
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# Schema has been intialized
|
||||
end
|
||||
end
|
||||
|
||||
def dump_schema_information #:nodoc:
|
||||
begin
|
||||
if (current_schema = ActiveRecord::Migrator.current_version) > 0
|
||||
return "INSERT INTO schema_info (version) VALUES (#{current_schema});"
|
||||
end
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# No Schema Info
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def type_to_sql(type, limit = nil) #:nodoc:
|
||||
native = native_database_types[type]
|
||||
limit ||= native[:limit]
|
||||
column_type_sql = native[:name]
|
||||
column_type_sql << "(#{limit})" if limit
|
||||
column_type_sql
|
||||
end
|
||||
|
||||
def add_column_options!(sql, options) #:nodoc:
|
||||
sql << " NOT NULL" if options[:null] == false
|
||||
sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,12 @@
|
|||
require 'benchmark'
|
||||
require 'date'
|
||||
|
||||
require 'active_record/connection_adapters/abstract/schema_definitions'
|
||||
require 'active_record/connection_adapters/abstract/schema_statements'
|
||||
require 'active_record/connection_adapters/abstract/database_statements'
|
||||
require 'active_record/connection_adapters/abstract/quoting'
|
||||
require 'active_record/connection_adapters/abstract/connection_specification'
|
||||
|
||||
# Method that requires a library, ensuring that rubygems is loaded
|
||||
# This is used in the database adaptors to require DB drivers. Reasons:
|
||||
# (1) database drivers are the only third-party library that Rails depend upon
|
||||
|
@ -25,245 +31,12 @@ def require_library_or_gem(library_name)
|
|||
end
|
||||
|
||||
module ActiveRecord
|
||||
class Base
|
||||
class ConnectionSpecification #:nodoc:
|
||||
attr_reader :config, :adapter_method
|
||||
def initialize (config, adapter_method)
|
||||
@config, @adapter_method = config, adapter_method
|
||||
end
|
||||
end
|
||||
|
||||
# The class -> [adapter_method, config] map
|
||||
@@defined_connections = {}
|
||||
|
||||
# Establishes the connection to the database. Accepts a hash as input where
|
||||
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
||||
# example for regular databases (MySQL, Postgresql, etc):
|
||||
#
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# :adapter => "mysql",
|
||||
# :host => "localhost",
|
||||
# :username => "myuser",
|
||||
# :password => "mypass",
|
||||
# :database => "somedatabase"
|
||||
# )
|
||||
#
|
||||
# Example for SQLite database:
|
||||
#
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# :adapter => "sqlite",
|
||||
# :dbfile => "path/to/dbfile"
|
||||
# )
|
||||
#
|
||||
# Also accepts keys as strings (for parsing from yaml for example):
|
||||
# ActiveRecord::Base.establish_connection(
|
||||
# "adapter" => "sqlite",
|
||||
# "dbfile" => "path/to/dbfile"
|
||||
# )
|
||||
#
|
||||
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
||||
# may be returned on an error.
|
||||
def self.establish_connection(spec = nil)
|
||||
case spec
|
||||
when nil
|
||||
raise AdapterNotSpecified unless defined? RAILS_ENV
|
||||
establish_connection(RAILS_ENV)
|
||||
when ConnectionSpecification
|
||||
@@defined_connections[self] = spec
|
||||
when Symbol, String
|
||||
if configuration = configurations[spec.to_s]
|
||||
establish_connection(configuration)
|
||||
else
|
||||
raise AdapterNotSpecified, "#{spec} database is not configured"
|
||||
end
|
||||
else
|
||||
spec = spec.symbolize_keys
|
||||
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
|
||||
adapter_method = "#{spec[:adapter]}_connection"
|
||||
unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
|
||||
remove_connection
|
||||
establish_connection(ConnectionSpecification.new(spec, adapter_method))
|
||||
end
|
||||
end
|
||||
|
||||
def self.active_connections #:nodoc:
|
||||
if threaded_connections
|
||||
Thread.current['active_connections'] ||= {}
|
||||
else
|
||||
@@active_connections ||= {}
|
||||
end
|
||||
end
|
||||
|
||||
# Locate the connection of the nearest super class. This can be an
|
||||
# active or defined connections: if it is the latter, it will be
|
||||
# opened and set as the active connection for the class it was defined
|
||||
# for (not necessarily the current class).
|
||||
def self.retrieve_connection #:nodoc:
|
||||
klass = self
|
||||
ar_super = ActiveRecord::Base.superclass
|
||||
until klass == ar_super
|
||||
if conn = active_connections[klass]
|
||||
return conn
|
||||
elsif conn = @@defined_connections[klass]
|
||||
klass.connection = conn
|
||||
return self.connection
|
||||
end
|
||||
klass = klass.superclass
|
||||
end
|
||||
raise ConnectionNotEstablished
|
||||
end
|
||||
|
||||
# Returns true if a connection that's accessible to this class have already been opened.
|
||||
def self.connected?
|
||||
klass = self
|
||||
until klass == ActiveRecord::Base.superclass
|
||||
if active_connections[klass]
|
||||
return true
|
||||
else
|
||||
klass = klass.superclass
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
# Remove the connection for this class. This will close the active
|
||||
# connection and the defined connection (if they exist). The result
|
||||
# can be used as argument for establish_connection, for easy
|
||||
# re-establishing of the connection.
|
||||
def self.remove_connection(klass=self)
|
||||
conn = @@defined_connections[klass]
|
||||
@@defined_connections.delete(klass)
|
||||
active_connections[klass] = nil
|
||||
conn.config if conn
|
||||
end
|
||||
|
||||
# Set the connection for the class.
|
||||
def self.connection=(spec)
|
||||
raise ConnectionNotEstablished unless spec
|
||||
conn = self.send(spec.adapter_method, spec.config)
|
||||
active_connections[self] = conn
|
||||
end
|
||||
|
||||
# Converts all strings in a hash to symbols.
|
||||
def self.symbolize_strings_in_hash(hash) #:nodoc:
|
||||
hash.symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
module ConnectionAdapters # :nodoc:
|
||||
class Column # :nodoc:
|
||||
attr_reader :name, :default, :type, :limit, :null
|
||||
# The name should contain the name of the column, such as "name" in "name varchar(250)"
|
||||
# The default should contain the type-casted default of the column, such as 1 in "count int(11) DEFAULT 1"
|
||||
# The type parameter should either contain :integer, :float, :datetime, :date, :text, or :string
|
||||
# The sql_type is just used for extracting the limit, such as 10 in "varchar(10)"
|
||||
def initialize(name, default, sql_type = nil, null = true)
|
||||
@name, @type, @null = name, simplified_type(sql_type), null
|
||||
# have to do this one separately because type_cast depends on #type
|
||||
@default = type_cast(default)
|
||||
@limit = extract_limit(sql_type) unless sql_type.nil?
|
||||
end
|
||||
|
||||
def klass
|
||||
case type
|
||||
when :integer then Fixnum
|
||||
when :float then Float
|
||||
when :datetime then Time
|
||||
when :date then Date
|
||||
when :timestamp then Time
|
||||
when :time then Time
|
||||
when :text, :string then String
|
||||
when :binary then String
|
||||
when :boolean then Object
|
||||
end
|
||||
end
|
||||
|
||||
def type_cast(value)
|
||||
if value.nil? then return nil end
|
||||
case type
|
||||
when :string then value
|
||||
when :text then value
|
||||
when :integer then value.to_i rescue value ? 1 : 0
|
||||
when :float then value.to_f
|
||||
when :datetime then string_to_time(value)
|
||||
when :timestamp then string_to_time(value)
|
||||
when :time then string_to_dummy_time(value)
|
||||
when :date then string_to_date(value)
|
||||
when :binary then binary_to_string(value)
|
||||
when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
|
||||
else value
|
||||
end
|
||||
end
|
||||
|
||||
def human_name
|
||||
Base.human_attribute_name(@name)
|
||||
end
|
||||
|
||||
def string_to_binary(value)
|
||||
value
|
||||
end
|
||||
|
||||
def binary_to_string(value)
|
||||
value
|
||||
end
|
||||
|
||||
private
|
||||
def string_to_date(string)
|
||||
return string unless string.is_a?(String)
|
||||
date_array = ParseDate.parsedate(string.to_s)
|
||||
# treat 0000-00-00 as nil
|
||||
Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
|
||||
end
|
||||
|
||||
def string_to_time(string)
|
||||
return string unless string.is_a?(String)
|
||||
time_array = ParseDate.parsedate(string.to_s).compact
|
||||
# treat 0000-00-00 00:00:00 as nil
|
||||
Time.send(Base.default_timezone, *time_array) rescue nil
|
||||
end
|
||||
|
||||
def string_to_dummy_time(string)
|
||||
return string unless string.is_a?(String)
|
||||
time_array = ParseDate.parsedate(string.to_s)
|
||||
# pad the resulting array with dummy date information
|
||||
time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
|
||||
Time.send(Base.default_timezone, *time_array) rescue nil
|
||||
end
|
||||
|
||||
def extract_limit(sql_type)
|
||||
$1.to_i if sql_type =~ /\((.*)\)/
|
||||
end
|
||||
|
||||
def simplified_type(field_type)
|
||||
case field_type
|
||||
when /int/i
|
||||
:integer
|
||||
when /float|double|decimal|numeric/i
|
||||
:float
|
||||
when /datetime/i
|
||||
:datetime
|
||||
when /timestamp/i
|
||||
:timestamp
|
||||
when /time/i
|
||||
:time
|
||||
when /date/i
|
||||
:date
|
||||
when /clob/i, /text/i
|
||||
:text
|
||||
when /blob/i, /binary/i
|
||||
:binary
|
||||
when /char/i, /string/i
|
||||
:string
|
||||
when /boolean/i
|
||||
:boolean
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# All the concrete database adapters follow the interface laid down in this class.
|
||||
# You can use this interface directly by borrowing the database connection from the Base with
|
||||
# Base.connection.
|
||||
class AbstractAdapter
|
||||
include Quoting, DatabaseStatements, SchemaStatements
|
||||
@@row_even = true
|
||||
|
||||
def initialize(connection, logger = nil) #:nodoc:
|
||||
|
@ -271,245 +44,22 @@ module ActiveRecord
|
|||
@runtime = 0
|
||||
end
|
||||
|
||||
# Returns an array of record hashes with the column names as a keys and fields as values.
|
||||
def select_all(sql, name = nil) end
|
||||
|
||||
# Returns a record hash with the column names as a keys and fields as values.
|
||||
def select_one(sql, name = nil) end
|
||||
|
||||
# Returns a single value from a record
|
||||
def select_value(sql, name = nil)
|
||||
result = select_one(sql, name)
|
||||
result.nil? ? nil : result.values.first
|
||||
# Returns the human-readable name of the adapter. Use mixed case - one can always use downcase if needed.
|
||||
def adapter_name
|
||||
'Abstract'
|
||||
end
|
||||
|
||||
# Returns an array of the values of the first column in a select:
|
||||
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
||||
def select_values(sql, name = nil)
|
||||
result = select_all(sql, name)
|
||||
result.map{ |v| v.values.first }
|
||||
# Returns true for database adapters that has implemented the schema statements.
|
||||
def supports_migrations?
|
||||
false
|
||||
end
|
||||
|
||||
# Returns an array of table names for the current database.
|
||||
# def tables(name = nil) end
|
||||
|
||||
# Returns an array of indexes for the given table.
|
||||
# def indexes(table_name, name = nil) end
|
||||
|
||||
# Returns an array of column objects for the table specified by +table_name+.
|
||||
def columns(table_name, name = nil) end
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) end
|
||||
|
||||
# Executes the update statement and returns the number of rows affected.
|
||||
def update(sql, name = nil) end
|
||||
|
||||
# Executes the delete statement and returns the number of rows affected.
|
||||
def delete(sql, name = nil) end
|
||||
|
||||
def reset_runtime #:nodoc:
|
||||
rt = @runtime
|
||||
@runtime = 0
|
||||
return rt
|
||||
end
|
||||
|
||||
# Wrap a block in a transaction. Returns result of block.
|
||||
def transaction(start_db_transaction = true)
|
||||
begin
|
||||
if block_given?
|
||||
begin_db_transaction if start_db_transaction
|
||||
result = yield
|
||||
commit_db_transaction if start_db_transaction
|
||||
result
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
rollback_db_transaction if start_db_transaction
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# Begins the transaction (and turns off auto-committing).
|
||||
def begin_db_transaction() end
|
||||
|
||||
# Commits the transaction (and turns on auto-committing).
|
||||
def commit_db_transaction() end
|
||||
|
||||
# Rolls back the transaction (and turns on auto-committing). Must be done if the transaction block
|
||||
# raises an exception or returns false.
|
||||
def rollback_db_transaction() end
|
||||
|
||||
def quoted_true() "'t'" end
|
||||
def quoted_false() "'f'" end
|
||||
|
||||
def quote(value, column = nil)
|
||||
case value
|
||||
when String
|
||||
if column && column.type == :binary
|
||||
"'#{quote_string(column.string_to_binary(value))}'" # ' (for ruby-mode)
|
||||
elsif column && [:integer, :float].include?(column.type)
|
||||
value.to_s
|
||||
else
|
||||
"'#{quote_string(value)}'" # ' (for ruby-mode)
|
||||
end
|
||||
when NilClass then "NULL"
|
||||
when TrueClass then (column && column.type == :boolean ? quoted_true : "1")
|
||||
when FalseClass then (column && column.type == :boolean ? quoted_false : "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
|
||||
|
||||
def quote_string(s)
|
||||
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
||||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
name
|
||||
end
|
||||
|
||||
# Returns the human-readable name of the adapter. Use mixed case - one can always use downcase if needed.
|
||||
def adapter_name()
|
||||
'Abstract'
|
||||
end
|
||||
|
||||
# Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database.
|
||||
def structure_dump() end
|
||||
|
||||
def add_limit!(sql, options)
|
||||
return unless options
|
||||
add_limit_offset!(sql, options)
|
||||
end
|
||||
|
||||
def add_limit_offset!(sql, options)
|
||||
return if options[:limit].nil?
|
||||
sql << " LIMIT #{options[:limit]}"
|
||||
sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil?
|
||||
end
|
||||
|
||||
|
||||
def initialize_schema_information
|
||||
begin
|
||||
execute "CREATE TABLE schema_info (version #{type_to_sql(:integer)})"
|
||||
execute "INSERT INTO schema_info (version) VALUES(0)"
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# Schema has been intialized
|
||||
end
|
||||
end
|
||||
|
||||
def dump_schema_information
|
||||
begin
|
||||
if (current_schema = ActiveRecord::Migrator.current_version) > 0
|
||||
return "INSERT INTO schema_info (version) VALUES (#{current_schema});"
|
||||
end
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# No Schema Info
|
||||
end
|
||||
end
|
||||
|
||||
def create_table(name, options = {})
|
||||
table_definition = TableDefinition.new(self)
|
||||
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
|
||||
|
||||
yield table_definition
|
||||
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
|
||||
create_sql << "#{name} ("
|
||||
create_sql << table_definition.to_sql
|
||||
create_sql << ") #{options[:options]}"
|
||||
execute create_sql
|
||||
end
|
||||
|
||||
def drop_table(name)
|
||||
execute "DROP TABLE #{name}"
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {})
|
||||
add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
|
||||
add_column_options!(add_column_sql, options)
|
||||
execute(add_column_sql)
|
||||
end
|
||||
|
||||
def remove_column(table_name, column_name)
|
||||
execute "ALTER TABLE #{table_name} DROP #{column_name}"
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {})
|
||||
raise NotImplementedError, "change_column is not implemented"
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default)
|
||||
raise NotImplementedError, "change_column_default is not implemented"
|
||||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name)
|
||||
raise NotImplementedError, "rename_column is not implemented"
|
||||
end
|
||||
|
||||
# Create a new index on the given table. By default, it will be named
|
||||
# <code>"#{table_name}_#{column_name.to_a.first}_index"</code>, but you
|
||||
# can explicitly name the index by passing <code>:name => "..."</code>
|
||||
# as the last parameter. Unique indexes may be created by passing
|
||||
# <code>:unique => true</code>.
|
||||
def add_index(table_name, column_name, options = {})
|
||||
index_name = "#{table_name}_#{column_name.to_a.first}_index"
|
||||
|
||||
if Hash === options # legacy support, since this param was a string
|
||||
index_type = options[:unique] ? "UNIQUE" : ""
|
||||
index_name = options[:name] || index_name
|
||||
else
|
||||
index_type = options
|
||||
end
|
||||
|
||||
execute "CREATE #{index_type} INDEX #{index_name} ON #{table_name} (#{column_name.to_a.join(", ")})"
|
||||
end
|
||||
|
||||
# Remove the given index from the table.
|
||||
#
|
||||
# remove_index :my_table, :column => :foo
|
||||
# remove_index :my_table, :name => :my_index_on_foo
|
||||
#
|
||||
# The first version will remove the index named
|
||||
# <code>"#{my_table}_#{column}_index"</code> from the table. The
|
||||
# second removes the named column from the table.
|
||||
def remove_index(table_name, options = {})
|
||||
if Hash === options # legacy support
|
||||
if options[:column]
|
||||
index_name = "#{table_name}_#{options[:column]}_index"
|
||||
elsif options[:name]
|
||||
index_name = options[:name]
|
||||
else
|
||||
raise ArgumentError, "You must specify the index name"
|
||||
end
|
||||
else
|
||||
index_name = "#{table_name}_#{options}_index"
|
||||
end
|
||||
|
||||
execute "DROP INDEX #{index_name} ON #{table_name}"
|
||||
end
|
||||
|
||||
def supports_migrations?
|
||||
false
|
||||
end
|
||||
|
||||
def native_database_types
|
||||
{}
|
||||
end
|
||||
|
||||
def type_to_sql(type, limit = nil)
|
||||
native = native_database_types[type]
|
||||
limit ||= native[:limit]
|
||||
column_type_sql = native[:name]
|
||||
column_type_sql << "(#{limit})" if limit
|
||||
column_type_sql
|
||||
end
|
||||
|
||||
def add_column_options!(sql, options)
|
||||
sql << " NOT NULL" if options[:null] == false
|
||||
sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
|
||||
end
|
||||
|
||||
protected
|
||||
def log(sql, name)
|
||||
begin
|
||||
|
@ -561,61 +111,5 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns)
|
||||
end
|
||||
|
||||
class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null)
|
||||
def to_sql
|
||||
column_sql = "#{name} #{type_to_sql(type.to_sym, limit)}"
|
||||
add_column_options!(column_sql, :null => null, :default => default)
|
||||
column_sql
|
||||
end
|
||||
alias to_s :to_sql
|
||||
|
||||
private
|
||||
def type_to_sql(name, limit)
|
||||
base.type_to_sql(name, limit) rescue name
|
||||
end
|
||||
|
||||
def add_column_options!(sql, options)
|
||||
base.add_column_options!(sql, options.merge(:column => self))
|
||||
end
|
||||
end
|
||||
|
||||
class TableDefinition
|
||||
attr_accessor :columns
|
||||
|
||||
def initialize(base)
|
||||
@columns = []
|
||||
@base = base
|
||||
end
|
||||
|
||||
def primary_key(name)
|
||||
column(name, native[:primary_key])
|
||||
end
|
||||
|
||||
def [](name)
|
||||
@columns.find {|column| column.name == name}
|
||||
end
|
||||
|
||||
def column(name, type, options = {})
|
||||
column = self[name] || ColumnDefinition.new(@base, name, type)
|
||||
column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
|
||||
column.default = options[:default]
|
||||
column.null = options[:null]
|
||||
@columns << column unless @columns.include? column
|
||||
self
|
||||
end
|
||||
|
||||
def to_sql
|
||||
@columns * ', '
|
||||
end
|
||||
|
||||
private
|
||||
def native
|
||||
@base.native_database_types
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,11 +82,20 @@ module ActiveRecord
|
|||
"MySQL server has gone away"
|
||||
]
|
||||
|
||||
def supports_migrations?
|
||||
def initialize(connection, logger, connection_options=nil)
|
||||
super(connection, logger)
|
||||
@connection_options = connection_options
|
||||
end
|
||||
|
||||
def adapter_name #:nodoc:
|
||||
'MySQL'
|
||||
end
|
||||
|
||||
def supports_migrations? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def native_database_types
|
||||
def native_database_types #:nodoc
|
||||
{
|
||||
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
||||
:string => { :name => "varchar", :limit => 255 },
|
||||
|
@ -102,58 +111,42 @@ module ActiveRecord
|
|||
}
|
||||
end
|
||||
|
||||
def initialize(connection, logger, connection_options=nil)
|
||||
super(connection, logger)
|
||||
@connection_options = connection_options
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
def quote_column_name(name) #:nodoc:
|
||||
"`#{name}`"
|
||||
end
|
||||
|
||||
def adapter_name
|
||||
'MySQL'
|
||||
def quote_string(string) #:nodoc:
|
||||
Mysql::quote(string)
|
||||
end
|
||||
|
||||
def select_all(sql, name = nil)
|
||||
def quoted_true
|
||||
"1"
|
||||
end
|
||||
|
||||
def quoted_false
|
||||
"0"
|
||||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
"`#{name}`"
|
||||
end
|
||||
|
||||
|
||||
# DATABASE STATEMENTS ======================================
|
||||
|
||||
def select_all(sql, name = nil) #:nodoc:
|
||||
select(sql, name)
|
||||
end
|
||||
|
||||
def select_one(sql, name = nil)
|
||||
def select_one(sql, name = nil) #:nodoc:
|
||||
result = select(sql, name)
|
||||
result.nil? ? nil : result.first
|
||||
end
|
||||
|
||||
def tables(name = nil)
|
||||
tables = []
|
||||
execute("SHOW TABLES", name).each { |field| tables << field[0] }
|
||||
tables
|
||||
end
|
||||
|
||||
def indexes(table_name, name = nil)
|
||||
indexes = []
|
||||
current_index = nil
|
||||
execute("SHOW KEYS FROM #{table_name}", name).each do |row|
|
||||
if current_index != row[2]
|
||||
next if row[2] == "PRIMARY" # skip the primary key
|
||||
current_index = row[2]
|
||||
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
|
||||
end
|
||||
|
||||
indexes.last.columns << row[4]
|
||||
end
|
||||
indexes
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)
|
||||
sql = "SHOW FIELDS FROM #{table_name}"
|
||||
columns = []
|
||||
execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
||||
columns
|
||||
end
|
||||
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
execute(sql, name = nil)
|
||||
id_value || @connection.insert_id
|
||||
end
|
||||
|
||||
def execute(sql, name = nil, retries = 2)
|
||||
def execute(sql, name = nil, retries = 2) #:nodoc:
|
||||
unless @logger
|
||||
@connection.query(sql)
|
||||
else
|
||||
|
@ -175,52 +168,39 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def update(sql, name = nil)
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
||||
execute(sql, name = nil)
|
||||
id_value || @connection.insert_id
|
||||
end
|
||||
|
||||
def update(sql, name = nil) #:nodoc:
|
||||
execute(sql, name)
|
||||
@connection.affected_rows
|
||||
end
|
||||
|
||||
alias_method :delete, :update
|
||||
alias_method :delete, :update #:nodoc:
|
||||
|
||||
|
||||
def begin_db_transaction
|
||||
def begin_db_transaction #:nodoc:
|
||||
execute "BEGIN"
|
||||
rescue Exception
|
||||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
def commit_db_transaction
|
||||
def commit_db_transaction #:nodoc:
|
||||
execute "COMMIT"
|
||||
rescue Exception
|
||||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
def rollback_db_transaction
|
||||
def rollback_db_transaction #:nodoc:
|
||||
execute "ROLLBACK"
|
||||
rescue Exception
|
||||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
|
||||
def quoted_true() "1" end
|
||||
def quoted_false() "0" end
|
||||
|
||||
def quote_column_name(name)
|
||||
"`#{name}`"
|
||||
end
|
||||
|
||||
def quote_string(string)
|
||||
Mysql::quote(string)
|
||||
end
|
||||
|
||||
|
||||
def structure_dump
|
||||
select_all("SHOW TABLES").inject("") do |structure, table|
|
||||
structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
|
||||
end
|
||||
end
|
||||
|
||||
def add_limit_offset!(sql, options)
|
||||
def add_limit_offset!(sql, options) #:nodoc
|
||||
if options[:limit]
|
||||
if options[:offset].blank?
|
||||
sql << " LIMIT #{options[:limit]}"
|
||||
|
@ -230,26 +210,68 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def recreate_database(name)
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
def structure_dump #:nodoc:
|
||||
select_all("SHOW TABLES").inject("") do |structure, table|
|
||||
structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
|
||||
end
|
||||
end
|
||||
|
||||
def recreate_database(name) #:nodoc:
|
||||
drop_database(name)
|
||||
create_database(name)
|
||||
end
|
||||
|
||||
def drop_database(name)
|
||||
execute "DROP DATABASE IF EXISTS #{name}"
|
||||
end
|
||||
|
||||
def create_database(name)
|
||||
def create_database(name) #:nodoc:
|
||||
execute "CREATE DATABASE #{name}"
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default)
|
||||
def drop_database(name) #:nodoc:
|
||||
execute "DROP DATABASE IF EXISTS #{name}"
|
||||
end
|
||||
|
||||
|
||||
def tables(name = nil) #:nodoc:
|
||||
tables = []
|
||||
execute("SHOW TABLES", name).each { |field| tables << field[0] }
|
||||
tables
|
||||
end
|
||||
|
||||
def indexes(table_name, name = nil)#:nodoc:
|
||||
indexes = []
|
||||
current_index = nil
|
||||
execute("SHOW KEYS FROM #{table_name}", name).each do |row|
|
||||
if current_index != row[2]
|
||||
next if row[2] == "PRIMARY" # skip the primary key
|
||||
current_index = row[2]
|
||||
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
|
||||
end
|
||||
|
||||
indexes.last.columns << row[4]
|
||||
end
|
||||
indexes
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)#:nodoc:
|
||||
sql = "SHOW FIELDS FROM #{table_name}"
|
||||
columns = []
|
||||
execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
||||
columns
|
||||
end
|
||||
|
||||
def create_table(name, options = {}) #:nodoc:
|
||||
super(name, {:options => "ENGINE=InnoDB"}.merge(options))
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default) #:nodoc:
|
||||
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
||||
|
||||
change_column(table_name, column_name, current_type, { :default => default })
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {})
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
options[:default] ||= select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
|
||||
|
||||
change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
|
||||
|
@ -257,14 +279,11 @@ module ActiveRecord
|
|||
execute(change_column_sql)
|
||||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name)
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
|
||||
execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
|
||||
end
|
||||
|
||||
def create_table(name, options = {})
|
||||
super(name, {:options => "ENGINE=InnoDB"}.merge(options))
|
||||
end
|
||||
|
||||
private
|
||||
def select(sql, name = nil)
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
|
||||
# postgresql_adaptor.rb
|
||||
# author: Luke Holden <lholden@cablelan.net>
|
||||
# notes: Currently this adaptor does not pass the test_zero_date_fields
|
||||
# and test_zero_datetime_fields unit tests in the BasicsTest test
|
||||
# group.
|
||||
#
|
||||
# This is due to the fact that, in postgresql you can not have a
|
||||
# totally zero timestamp. Instead null/nil should be used to
|
||||
# represent no value.
|
||||
#
|
||||
# Author: Luke Holden <lholden@cablelan.net>
|
||||
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
require 'parsedate'
|
||||
|
@ -60,6 +50,10 @@ module ActiveRecord
|
|||
# * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
|
||||
# * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
|
||||
class PostgreSQLAdapter < AbstractAdapter
|
||||
def adapter_name
|
||||
'PostgreSQL'
|
||||
end
|
||||
|
||||
def native_database_types
|
||||
{
|
||||
:primary_key => "serial primary key",
|
||||
|
@ -80,17 +74,71 @@ module ActiveRecord
|
|||
true
|
||||
end
|
||||
|
||||
def select_all(sql, name = nil)
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
def quote(value, column = nil)
|
||||
if value.class == String && column && column.type == :binary
|
||||
quote_bytea(value)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
%("#{name}")
|
||||
end
|
||||
|
||||
|
||||
# DATABASE STATEMENTS ======================================
|
||||
|
||||
def select_all(sql, name = nil) #:nodoc:
|
||||
select(sql, name)
|
||||
end
|
||||
|
||||
def select_one(sql, name = nil)
|
||||
def select_one(sql, name = nil) #:nodoc:
|
||||
result = select(sql, name)
|
||||
result.nil? ? nil : result.first
|
||||
end
|
||||
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
||||
execute(sql, name)
|
||||
table = sql.split(" ", 4)[2]
|
||||
return id_value || last_insert_id(table, pk)
|
||||
end
|
||||
|
||||
def query(sql, name = nil) #:nodoc:
|
||||
log(sql, name) { @connection.query(sql) }
|
||||
end
|
||||
|
||||
def execute(sql, name = nil) #:nodoc:
|
||||
log(sql, name) { @connection.exec(sql) }
|
||||
end
|
||||
|
||||
def update(sql, name = nil) #:nodoc:
|
||||
execute(sql, name).cmdtuples
|
||||
end
|
||||
|
||||
alias_method :delete, :update #:nodoc:
|
||||
|
||||
|
||||
def begin_db_transaction #:nodoc:
|
||||
execute "BEGIN"
|
||||
end
|
||||
|
||||
def commit_db_transaction #:nodoc:
|
||||
execute "COMMIT"
|
||||
end
|
||||
|
||||
def rollback_db_transaction #:nodoc:
|
||||
execute "ROLLBACK"
|
||||
end
|
||||
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
# Return the list of all tables in the schema search path.
|
||||
def tables(name = nil)
|
||||
def tables(name = nil) #:nodoc:
|
||||
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
||||
query(<<-SQL, name).map { |row| row[0] }
|
||||
SELECT tablename
|
||||
|
@ -99,7 +147,7 @@ module ActiveRecord
|
|||
SQL
|
||||
end
|
||||
|
||||
def indexes(table_name, name = nil)
|
||||
def indexes(table_name, name = nil) #:nodoc:
|
||||
result = query(<<-SQL, name)
|
||||
SELECT i.relname, d.indisunique, a.attname
|
||||
FROM pg_class t, pg_class i, pg_index d, pg_attribute a
|
||||
|
@ -132,33 +180,27 @@ module ActiveRecord
|
|||
indexes
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)
|
||||
def columns(table_name, name = nil) #:nodoc:
|
||||
column_definitions(table_name).collect do |name, type, default, notnull|
|
||||
Column.new(name, default_value(default), translate_field_type(type),
|
||||
notnull == "f")
|
||||
end
|
||||
end
|
||||
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
execute(sql, name)
|
||||
table = sql.split(" ", 4)[2]
|
||||
return id_value || last_insert_id(table, pk)
|
||||
# Set the schema search path to a string of comma-separated schema names.
|
||||
# Names beginning with $ are quoted (e.g. $user => '$user')
|
||||
# See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
|
||||
def schema_search_path=(schema_csv) #:nodoc:
|
||||
if schema_csv
|
||||
execute "SET search_path TO #{schema_csv}"
|
||||
@schema_search_path = nil
|
||||
end
|
||||
end
|
||||
|
||||
def query(sql, name = nil)
|
||||
log(sql, name) { @connection.query(sql) }
|
||||
def schema_search_path #:nodoc:
|
||||
@schema_search_path ||= query('SHOW search_path')[0][0]
|
||||
end
|
||||
|
||||
def execute(sql, name = nil)
|
||||
log(sql, name) { @connection.exec(sql) }
|
||||
end
|
||||
|
||||
def update(sql, name = nil)
|
||||
execute(sql, name).cmdtuples
|
||||
end
|
||||
|
||||
alias_method :delete, :update
|
||||
|
||||
def add_column(table_name, column_name, type, options = {})
|
||||
native_type = native_database_types[type]
|
||||
sql_commands = ["ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"]
|
||||
|
@ -171,56 +213,20 @@ module ActiveRecord
|
|||
sql_commands.each { |cmd| execute(cmd) }
|
||||
end
|
||||
|
||||
|
||||
def begin_db_transaction() execute "BEGIN" end
|
||||
def commit_db_transaction() execute "COMMIT" end
|
||||
def rollback_db_transaction() execute "ROLLBACK" end
|
||||
|
||||
def quote(value, column = nil)
|
||||
if value.class == String && column && column.type == :binary
|
||||
quote_bytea(value)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
%("#{name}")
|
||||
end
|
||||
|
||||
def adapter_name
|
||||
'PostgreSQL'
|
||||
end
|
||||
|
||||
|
||||
# Set the schema search path to a string of comma-separated schema names.
|
||||
# Names beginning with $ are quoted (e.g. $user => '$user')
|
||||
# See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
|
||||
def schema_search_path=(schema_csv)
|
||||
if schema_csv
|
||||
execute "SET search_path TO #{schema_csv}"
|
||||
@schema_search_path = nil
|
||||
end
|
||||
end
|
||||
|
||||
def schema_search_path
|
||||
@schema_search_path ||= query('SHOW search_path')[0][0]
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {})
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
execute = "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type}"
|
||||
change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default)
|
||||
def change_column_default(table_name, column_name, default) #:nodoc:
|
||||
execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT '#{default}'"
|
||||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name)
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
|
||||
end
|
||||
|
||||
def remove_index(table_name, options)
|
||||
def remove_index(table_name, options) #:nodoc:
|
||||
if Hash === options
|
||||
index_name = options[:name]
|
||||
else
|
||||
|
@ -230,6 +236,7 @@ module ActiveRecord
|
|||
execute "DROP INDEX #{index_name}"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
BYTEA_COLUMN_TYPE_OID = 17
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# sqlite_adapter.rb
|
||||
# author: Luke Holden <lholden@cablelan.net>
|
||||
# updated for SQLite3: Jamis Buck <jamis_buck@byu.edu>
|
||||
# Author: Luke Holden <lholden@cablelan.net>
|
||||
# Updated for SQLite3: Jamis Buck <jamis_buck@byu.edu>
|
||||
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
||||
|
@ -87,7 +86,15 @@ module ActiveRecord
|
|||
#
|
||||
# * <tt>:dbfile</tt> -- Path to the database file.
|
||||
class SQLiteAdapter < AbstractAdapter
|
||||
def native_database_types
|
||||
def adapter_name #:nodoc:
|
||||
'SQLite'
|
||||
end
|
||||
|
||||
def supports_migrations? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def native_database_types #:nodoc:
|
||||
{
|
||||
:primary_key => "INTEGER PRIMARY KEY NOT NULL",
|
||||
:string => { :name => "varchar", :limit => 255 },
|
||||
|
@ -103,32 +110,41 @@ module ActiveRecord
|
|||
}
|
||||
end
|
||||
|
||||
def supports_migrations?
|
||||
true
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
def quote_string(s) #:nodoc:
|
||||
@connection.class.quote(s)
|
||||
end
|
||||
|
||||
def execute(sql, name = nil)
|
||||
#log(sql, name, @connection) { |connection| connection.execute(sql) }
|
||||
def quote_column_name(name) #:nodoc:
|
||||
"'#{name}'"
|
||||
end
|
||||
|
||||
|
||||
# DATABASE STATEMENTS ======================================
|
||||
|
||||
def execute(sql, name = nil) #:nodoc:
|
||||
log(sql, name) { @connection.execute(sql) }
|
||||
end
|
||||
|
||||
def update(sql, name = nil)
|
||||
def update(sql, name = nil) #:nodoc:
|
||||
execute(sql, name)
|
||||
@connection.changes
|
||||
end
|
||||
|
||||
def delete(sql, name = nil)
|
||||
def delete(sql, name = nil) #:nodoc:
|
||||
sql += " WHERE 1=1" unless sql =~ /WHERE/i
|
||||
execute(sql, name)
|
||||
@connection.changes
|
||||
end
|
||||
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
||||
execute(sql, name = nil)
|
||||
id_value || @connection.last_insert_row_id
|
||||
end
|
||||
|
||||
def select_all(sql, name = nil)
|
||||
def select_all(sql, name = nil) #:nodoc:
|
||||
execute(sql, name).map do |row|
|
||||
record = {}
|
||||
row.each_key do |key|
|
||||
|
@ -140,29 +156,40 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def select_one(sql, name = nil)
|
||||
def select_one(sql, name = nil) #:nodoc:
|
||||
result = select_all(sql, name)
|
||||
result.nil? ? nil : result.first
|
||||
end
|
||||
|
||||
|
||||
def begin_db_transaction() @connection.transaction end
|
||||
def commit_db_transaction() @connection.commit end
|
||||
def rollback_db_transaction() @connection.rollback end
|
||||
def begin_db_transaction #:nodoc:
|
||||
@connection.transaction
|
||||
end
|
||||
|
||||
def tables(name = nil)
|
||||
def commit_db_transaction #:nodoc:
|
||||
@connection.commit
|
||||
end
|
||||
|
||||
def rollback_db_transaction #:nodoc:
|
||||
@connection.rollback
|
||||
end
|
||||
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
def tables(name = nil) #:nodoc:
|
||||
execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
|
||||
row[0]
|
||||
end
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)
|
||||
def columns(table_name, name = nil) #:nodoc:
|
||||
table_structure(table_name).map { |field|
|
||||
SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
|
||||
}
|
||||
end
|
||||
|
||||
def indexes(table_name, name = nil)
|
||||
def indexes(table_name, name = nil) #:nodoc:
|
||||
execute("PRAGMA index_list(#{table_name})", name).map do |row|
|
||||
index = IndexDefinition.new(table_name, row['name'])
|
||||
index.unique = row['unique'] != '0'
|
||||
|
@ -171,24 +198,12 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def primary_key(table_name)
|
||||
def primary_key(table_name) #:nodoc:
|
||||
column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
|
||||
column ? column['name'] : nil
|
||||
end
|
||||
|
||||
def quote_string(s)
|
||||
@connection.class.quote(s)
|
||||
end
|
||||
|
||||
def quote_column_name(name)
|
||||
"'#{name}'"
|
||||
end
|
||||
|
||||
def adapter_name()
|
||||
'SQLite'
|
||||
end
|
||||
|
||||
def remove_index(table_name, options={})
|
||||
def remove_index(table_name, options={}) #:nodoc:
|
||||
if Hash === options
|
||||
index_name = options[:name]
|
||||
else
|
||||
|
@ -198,25 +213,25 @@ module ActiveRecord
|
|||
execute "DROP INDEX #{index_name}"
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {})
|
||||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition.column(column_name, type, options)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_column(table_name, column_name)
|
||||
def remove_column(table_name, column_name) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition.columns.delete(definition[column_name])
|
||||
end
|
||||
end
|
||||
|
||||
def change_column_default(table_name, column_name, default)
|
||||
def change_column_default(table_name, column_name, default) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition[column_name].default = default
|
||||
end
|
||||
end
|
||||
|
||||
def change_column(table_name, column_name, type, options = {})
|
||||
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition[column_name].instance_eval do
|
||||
self.type = type
|
||||
|
@ -226,7 +241,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def rename_column(table_name, column_name, new_column_name)
|
||||
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
||||
alter_table(table_name, :rename => {column_name => new_column_name})
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module ActiveRecord
|
||||
class Schema < Migration #:nodoc:
|
||||
# TODO: Document me!
|
||||
class Schema < Migration
|
||||
private_class_method :new
|
||||
|
||||
def self.define(info={}, &block)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module ActiveRecord
|
||||
# This class is used to dump the database schema for some connection to some
|
||||
# output format (i.e., ActiveRecord::Schema).
|
||||
class SchemaDumper
|
||||
class SchemaDumper #:nodoc:
|
||||
private_class_method :new
|
||||
|
||||
def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
|
||||
|
|
Loading…
Reference in a new issue