diff options
6 files changed, 210 insertions, 38 deletions
| diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 8b94d68717..be5be0fff4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -1,4 +1,5 @@  module ActiveRecord +  # The root class of all active record objects.    class Base      class ConnectionSpecification #:nodoc:        attr_reader :config, :adapter_method diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 1e8dd045f6..809424c078 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -1,11 +1,12 @@  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. +      # Returns an array of record hashes with the column names as keys and +      # column values as values.        def select_all(sql, name = nil) end -      # Returns a record hash with the column names as a keys and fields as values. +      # Returns a record hash with the column names as keys and column values +      # as values.        def select_one(sql, name = nil) end        # Returns a single value from a record @@ -21,8 +22,11 @@ module ActiveRecord          result.map{ |v| v.values.first }        end -      # Executes the statement -      def execute(sql, name = nil) end +      # Executes the SQL statement in the context of this connection. +      # This abstract method raises a NotImplementedError. +      def execute(sql, name = nil) +        raise NotImplementedError, "execute is an abstract method" +      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 @@ -54,16 +58,23 @@ module ActiveRecord        # 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. +      # 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: +      # Alias for #add_limit_offset!. +      def add_limit!(sql, options)          return unless options          add_limit_offset!(sql, options)        end -      def add_limit_offset!(sql, options) #:nodoc: +      # Appends +LIMIT+ and +OFFSET+ options to a SQL statement. +      # This method *modifies* the +sql+ parameter. +      # ===== Examples +      #  add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50}) +      # generates +      #  SELECT * FROM suppliers LIMIT 10 OFFSET 50 +      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? diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 7f7cc03c7a..c8088bd978 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -1,7 +1,8 @@  module ActiveRecord    module ConnectionAdapters # :nodoc: -    # TODO: Document me!      module Quoting +      # Quotes the column value to help prevent +      # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].        def quote(value, column = nil)          case value            when String @@ -22,10 +23,14 @@ module ActiveRecord          end        end +      # Quotes a string, escaping any ' (single quote) and \ (backslash) +      # characters.        def quote_string(s)          s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)        end +      # Returns a quoted form of the column name.  This is highly adapter +      # specific.        def quote_column_name(name)          name        end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index f90ac0266a..424a898db3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -1,11 +1,15 @@  module ActiveRecord    module ConnectionAdapters #:nodoc: -    class Column #:nodoc: +    # An abstract definition of a column in a table. +    class Column        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)" + +      # Instantiates a new column in the table. +      # +      # +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>. +      # +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>. +      # +sql_type+ is only used to extract the column's length, if necessary.  For example, <tt>company_name varchar(<b>60</b>)</tt>. +      # +null+ determines if this column allows +NULL+ values.        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 @@ -13,6 +17,7 @@ module ActiveRecord          @limit = extract_limit(sql_type) unless sql_type.nil?        end +      # Returns the Ruby class that corresponds to the abstract data type.        def klass          case type            when :integer       then Fixnum @@ -27,6 +32,7 @@ module ActiveRecord          end        end +      # Casts value (which is a String) to an appropriate instance.        def type_cast(value)          if value.nil? then return nil end          case type @@ -44,14 +50,20 @@ module ActiveRecord          end        end +      # Returns the human name of the column name. +      # +      # ===== Examples +      #  Column.new('sales_stage', ...).human_name #=> 'Sales stage'        def human_name          Base.human_attribute_name(@name)        end +      # Used to convert from Strings to BLOBs        def string_to_binary(value)          value        end +      # Used to convert from BLOBs to Strings        def binary_to_string(value)          value        end @@ -108,7 +120,7 @@ module ActiveRecord            end          end      end -     +      class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:      end @@ -130,7 +142,9 @@ module ActiveRecord          end      end -    class TableDefinition #:nodoc: +    # Represents a SQL table in an abstract way. +    # Columns are stored as ColumnDefinition in the #columns attribute. +    class TableDefinition        attr_accessor :columns        def initialize(base) @@ -138,14 +152,48 @@ module ActiveRecord          @base = base        end +      # Appends a primary key definition to the table definition. +      # Can be called multiple times, but this is probably not a good idea.        def primary_key(name)          column(name, native[:primary_key])        end -       + +      # Returns a ColumnDefinition for the column with name +name+.        def [](name)          @columns.find {|column| column.name == name}        end +      # Instantiates a new column for the table. +      # The +type+ parameter must be one of the following values: +      # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>, +      # <tt>:integer</tt>, <tt>:float</tt>, <tt>:datetime</tt>, +      # <tt>:timestamp</tt>, <tt>:time</tt>, <tt>:date</tt>, +      # <tt>:binary</tt>, <tt>:boolean</tt>. +      # +      # Available options are (none of these exists by default): +      # * <tt>:limit</tt>: +      #   Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>, +      #   <tt>:binary</tt> or <tt>:integer</tt> columns only) +      # * <tt>:default</tt>: +      #   The column's default value.  You cannot explicitely set the default +      #   value to +NULL+.  Simply leave off this option if you want a +NULL+ +      #   default value. +      # * <tt>:null</tt>: +      #   Allows or disallows +NULL+ values in the column.  This option could +      #   have been named <tt>:null_allowed</tt>. +      # +      # This method returns <tt>self</tt>. +      # +      # ===== Examples +      #  # Assuming def is an instance of TableDefinition +      #  def.column(:granted, :boolean) +      #    #=> granted BOOLEAN +      # +      #  def.column(:picture, :binary, :limit => 2.megabytes) +      #    #=> picture BLOB(2097152) +      # +      #  def.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false) +      #    #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL        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] @@ -155,6 +203,9 @@ module ActiveRecord          self        end +      # Returns a String whose contents are the column definitions +      # concatenated together.  This string can then be pre and appended to +      # to generate the final SQL to create the table.        def to_sql          @columns * ', '        end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index d7aefc9a82..ea9173039c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1,8 +1,10 @@  module ActiveRecord    module ConnectionAdapters # :nodoc: -    # TODO: Document me!      module SchemaStatements -      def native_database_types #:nodoc: +      # Returns a Hash of mappings from the abstract data types to the native +      # database types.  See TableDefinition#column for details on the recognized +      # abstract data types. +      def native_database_types          {}        end @@ -11,10 +13,67 @@ module ActiveRecord        # 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+. +      # Returns an array of Column objects for the table specified by +table_name+. +      # See the concrete implementation for details on the expected parameter values.        def columns(table_name, name = nil) end - +      # Creates a new table +      # There are two ways to work with #create_table.  You can use the block +      # form or the regular form, like this: +      # +      # === Block form +      #  # create_table() yields a TableDefinition instance +      #  create_table(:suppliers) do |t| +      #    t.column :name, :string, :limit => 60 +      #    # Other fields here +      #  end +      # +      # === Regular form +      #  create_table(:suppliers) +      #  add_column(:suppliers, :name, :string, {:limit => 60}) +      # +      # The +options+ hash can include the following keys: +      # [<tt>:id</tt>] +      #   Set to true or false to add/not add a primary key column +      #   automatically.  Defaults to true. +      # [<tt>:primary_key</tt>] +      #   The name of the primary key, if one is to be added automatically. +      #   Defaults to +id+. +      # [<tt>:options</tt>] +      #   Any extra options you want appended to the table definition. +      # [<tt>:temporary</tt>] +      #   Make a temporary table. +      # +      # ===== Examples +      # ====== Add a backend specific option to the generated SQL (MySQL) +      #  create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8') +      # generates: +      #  CREATE TABLE suppliers ( +      #    id int(11) DEFAULT NULL auto_increment PRIMARY KEY +      #  ) ENGINE=InnoDB DEFAULT CHARSET=utf8 +      # +      # ====== Rename the primary key column +      #  create_table(:objects, :primary_key => 'guid') do |t| +      #    t.column :name, :string, :limit => 80 +      #  end +      # generates: +      #  CREATE TABLE objects ( +      #    guid int(11) DEFAULT NULL auto_increment PRIMARY KEY, +      #    name varchar(80) +      #  ) +      # +      # ====== Do not add a primary key column +      #  create_table(:categories_suppliers, :id => false) do |t| +      #    t.column :category_id, :integer +      #    t.column :supplier_id, :integer +      #  end +      # generates: +      #  CREATE TABLE categories_suppliers_join ( +      #    category_id int, +      #    supplier_id int +      #  ) +      # +      # See also TableDefinition#column for details on how to create columns.        def create_table(name, options = {})          table_definition = TableDefinition.new(self)          table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false @@ -27,37 +86,71 @@ module ActiveRecord          execute create_sql        end +      # Drops a table from the database.        def drop_table(name)          execute "DROP TABLE #{name}"        end +      # Adds a new column to the named table. +      # See TableDefinition#column for details of the options you can use.        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 +      # Removes the column from the table definition. +      # ===== Examples +      #  remove_column(:suppliers, :qualification)        def remove_column(table_name, column_name)          execute "ALTER TABLE #{table_name} DROP #{column_name}"        end       +      # Changes the column's definition according to the new options. +      # See TableDefinition#column for details of the options you can use. +      # ===== Examples +      #  change_column(:suppliers, :name, :string, :limit => 80) +      #  change_column(:accounts, :description, :text)        def change_column(table_name, column_name, type, options = {})          raise NotImplementedError, "change_column is not implemented"        end +      # Sets a new default value for a column.  If you want to set the default +      # value to +NULL+, you are out of luck.  You need to +      # DatabaseStatements#execute the apppropriate SQL statement yourself. +      # ===== Examples +      #  change_column_default(:suppliers, :qualification, 'new') +      #  change_column_default(:accounts, :authorized, 1)        def change_column_default(table_name, column_name, default)          raise NotImplementedError, "change_column_default is not implemented"        end +      # Renames a column. +      # ===== Example +      #  rename_column(:suppliers, :description, :name)        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}_#{Array(column_name).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>. +      # Adds a new index to the table.  +column_name+ can be a single Symbol, or +      # an Array of Symbols. +      # +      # The index will be named after the table and the first column names, +      # unless you pass +:name+ as an option. +      # +      # ===== Examples +      # ====== Creating a simple index +      #  add_index(:suppliers, :name) +      # generates +      #  CREATE INDEX suppliers_name_index ON suppliers(name) +      # ====== Creating a unique index +      #  add_index(:accounts, [:branch_id, :party_id], :unique => true) +      # generates +      #  CREATE UNIQUE INDEX accounts_branch_id_index ON accounts(branch_id, party_id) +      # ====== Creating a named index +      #  add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party') +      # generates +      #  CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)        def add_index(table_name, column_name, options = {})          index_name = "#{table_name}_#{Array(column_name).first}_index" @@ -73,12 +166,12 @@ module ActiveRecord        # 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. +      # Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms). +      #   remove_index :suppliers, :name +      # Remove the index named accounts_branch_id in the accounts table. +      #   remove_index :accounts, :column => :branch_id +      # Remove the index named by_branch_party in the accounts table. +      #   remove_index :accounts, :name => :by_branch_party        def remove_index(table_name, options = {})          if Hash === options # legacy support            if options[:column] @@ -96,11 +189,14 @@ module ActiveRecord        end -      # Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database. -      def structure_dump #:nodoc: +      # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the +      # entire structure of the database. +      def structure_dump        end -      def initialize_schema_information #:nodoc: +      # Should not be called normally, but this operation is non-destructive. +      # The migrations module handles this automatically. +      def initialize_schema_information          begin            execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"            execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)" diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index f6dfc4bfd4..a028cdbc00 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -35,6 +35,12 @@ module ActiveRecord      # 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. +    # +    # Most of the methods in the adapter are useful during migrations.  Most +    # notably, SchemaStatements#create_table, SchemaStatements#drop_table, +    # SchemaStatements#add_index, SchemaStatements#remove_index, +    # SchemaStatements#add_column, SchemaStatements#change_column and +    # SchemaStatements#remove_column are very useful.      class AbstractAdapter        include Quoting, DatabaseStatements, SchemaStatements        @@row_even = true @@ -44,12 +50,14 @@ module ActiveRecord          @runtime = 0        end -      # Returns the human-readable name of the adapter.  Use mixed case - one can always use downcase if needed. +      # 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. +      # Does this adapter support migrations ?  Backend specific, as the +      # abstract adapter always returns +false+.        def supports_migrations?          false        end | 
