From b3df95985a449fd155868b4ec04a556530a03e6c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 25 Sep 2005 17:56:03 +0000 Subject: [PATCH] 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 --- .../abstract/connection_specification.rb | 126 ++++ .../abstract/database_statements.rb | 73 +++ .../connection_adapters/abstract/quoting.rb | 42 ++ .../abstract/schema_definitions.rb | 168 ++++++ .../abstract/schema_statements.rb | 137 +++++ .../connection_adapters/abstract_adapter.rb | 540 +----------------- .../connection_adapters/mysql_adapter.rb | 181 +++--- .../connection_adapters/postgresql_adapter.rb | 153 ++--- .../connection_adapters/sqlite_adapter.rb | 91 +-- activerecord/lib/active_record/schema.rb | 3 +- .../lib/active_record/schema_dumper.rb | 2 +- 11 files changed, 799 insertions(+), 717 deletions(-) create mode 100644 activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb create mode 100644 activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb create mode 100644 activerecord/lib/active_record/connection_adapters/abstract/quoting.rb create mode 100644 activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb create mode 100644 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb new file mode 100644 index 0000000000..8b94d68717 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -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 \ No newline at end of file diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb new file mode 100644 index 0000000000..1e8dd045f6 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -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 \ No newline at end of file diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb new file mode 100644 index 0000000000..7f7cc03c7a --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -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 \ No newline at end of file diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb new file mode 100644 index 0000000000..f90ac0266a --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -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 \ No newline at end of file diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb new file mode 100644 index 0000000000..2ce1380f96 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -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 + # "#{table_name}_#{column_name.to_a.first}_index", but you + # can explicitly name the index by passing :name => "..." + # as the last parameter. Unique indexes may be created by passing + # :unique => true. + 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 + # "#{my_table}_#{column}_index" 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 \ No newline at end of file diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index b9c06a21cb..f6dfc4bfd4 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -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,491 +31,35 @@ 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: + def initialize(connection, logger = nil) #:nodoc: @connection, @logger = connection, logger @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 true for database adapters that has implemented the schema statements. + def supports_migrations? + false 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 - - # 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: + 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 - # "#{table_name}_#{column_name.to_a.first}_index", but you - # can explicitly name the index by passing :name => "..." - # as the last parameter. Unique indexes may be created by passing - # :unique => true. - 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 - # "#{my_table}_#{column}_index" 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 diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index eea47817f6..98ac1fae67 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -81,12 +81,21 @@ module ActiveRecord "Lost connection to MySQL server during query", "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) @@ -278,4 +297,4 @@ module ActiveRecord end end end -end +end \ No newline at end of file diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 0fe2517657..254b1a04a8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,14 +1,4 @@ - -# postgresql_adaptor.rb -# author: Luke Holden -# 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 require 'active_record/connection_adapters/abstract_adapter' require 'parsedate' @@ -60,6 +50,10 @@ module ActiveRecord # * :encoding -- An optional client encoding that is using in a SET client_encoding TO call on connection. # * :min_messages -- An optional client min messages that is using in a SET client_min_messages TO 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])}"] @@ -170,57 +212,21 @@ module ActiveRecord end 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 @@ -229,6 +235,7 @@ module ActiveRecord execute "DROP INDEX #{index_name}" end + private BYTEA_COLUMN_TYPE_OID = 17 diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ab42490020..d1929f74e4 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -1,6 +1,5 @@ -# sqlite_adapter.rb -# author: Luke Holden -# updated for SQLite3: Jamis Buck +# Author: Luke Holden +# Updated for SQLite3: Jamis Buck require 'active_record/connection_adapters/abstract_adapter' @@ -87,7 +86,15 @@ module ActiveRecord # # * :dbfile -- 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 commit_db_transaction #:nodoc: + @connection.commit + end - def tables(name = nil) + 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 diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index e25385bf06..8527d54697 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -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) diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 592b2a7068..0f51de58af 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -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)