diff options
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb')
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb | 176 |
1 files changed, 111 insertions, 65 deletions
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 b4a9e29ef1..62b0f51bb2 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,12 @@ -require 'active_support/core_ext/array/wrap' +require 'active_support/deprecation/reporting' +require 'active_record/schema_migration' +require 'active_record/migration/join_table' module ActiveRecord module ConnectionAdapters # :nodoc: module SchemaStatements + include ActiveRecord::Migration::JoinTable + # 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. @@ -12,14 +16,11 @@ module ActiveRecord # Truncates a table alias according to the limits of the current adapter. def table_alias_for(table_name) - table_name[0...table_alias_length].gsub(/\./, '_') + table_name[0...table_alias_length].tr('.', '_') end - # def tables(name = nil) end - # Checks to see if the table +table_name+ exists on the database. # - # === Example # table_exists?(:developers) def table_exists?(table_name) tables.include?(table_name.to_s) @@ -30,7 +31,6 @@ module ActiveRecord # Checks to see if an index exists on a table for a given index definition. # - # === Examples # # Check an index exists # index_exists?(:suppliers, :company_id) # @@ -43,7 +43,7 @@ module ActiveRecord # # Check an index with a custom name exists # index_exists?(:suppliers, :company_id, :name => "idx_company_id" def index_exists?(table_name, column_name, options = {}) - column_names = Array.wrap(column_name) + column_names = Array(column_name) index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names) if options[:unique] indexes(table_name).any?{ |i| i.unique && i.name == index_name } @@ -54,7 +54,7 @@ module ActiveRecord # 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 + def columns(table_name) end # Checks to see if a column exists in a given table. # @@ -113,7 +113,7 @@ module ActiveRecord # Defaults to +id+. If <tt>:id</tt> is false this option is ignored. # # Also note that this just sets the primary key in the table. You additionally - # need to configure the primary key in the model via the +set_primary_key+ macro. + # need to configure the primary key in the model via +self.primary_key=+. # Models do NOT auto-detect the primary key from their table definition. # # [<tt>:options</tt>] @@ -124,7 +124,6 @@ module ActiveRecord # Set to true to drop the table before creating it. # Defaults to false. # - # ===== Examples # ====== Add a backend specific option to the generated SQL (MySQL) # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8') # generates: @@ -161,7 +160,7 @@ module ActiveRecord yield td if block_given? if options[:force] && table_exists?(table_name) - drop_table(table_name) + drop_table(table_name, options) end create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " @@ -169,11 +168,49 @@ module ActiveRecord create_sql << td.to_sql create_sql << ") #{options[:options]}" execute create_sql + td.indexes.each_pair { |c,o| add_index table_name, c, o } + end + + # Creates a new join table with the name created using the lexical order of the first two + # arguments. These arguments can be a String or a Symbol. + # + # # Creates a table called 'assemblies_parts' with no id. + # create_join_table(:assemblies, :parts) + # + # You can pass a +options+ hash can include the following keys: + # [<tt>:table_name</tt>] + # Sets the table name overriding the default + # [<tt>:column_options</tt>] + # Any extra options you want appended to the columns definition. + # [<tt>:options</tt>] + # Any extra options you want appended to the table definition. + # [<tt>:temporary</tt>] + # Make a temporary table. + # [<tt>:force</tt>] + # Set to true to drop the table before creating it. + # Defaults to false. + # + # ====== Add a backend specific option to the generated SQL (MySQL) + # create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # generates: + # CREATE TABLE assemblies_parts ( + # assembly_id int NOT NULL, + # part_id int NOT NULL, + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + def create_join_table(table_1, table_2, options = {}) + join_table_name = find_join_table_name(table_1, table_2, options) + + column_options = options.delete(:column_options) || {} + column_options.reverse_merge!({:null => false}) + + create_table(join_table_name, options.merge!(:id => false)) do |td| + td.integer :"#{table_1.to_s.singularize}_id", column_options + td.integer :"#{table_2.to_s.singularize}_id", column_options + end end # A block for changing columns in +table+. # - # === Example # # change_table() yields a Table instance # change_table(:suppliers) do |t| # t.column :name, :string, :limit => 60 @@ -187,7 +224,6 @@ module ActiveRecord # # Defaults to false. # - # ===== Examples # ====== Add a column # change_table(:suppliers) do |t| # t.column :name, :string, :limit => 60 @@ -246,14 +282,14 @@ module ActiveRecord end # Renames a table. - # ===== Example + # # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) raise NotImplementedError, "rename_table is not implemented" end # Drops a table from the database. - def drop_table(table_name) + def drop_table(table_name, options = {}) execute "DROP TABLE #{quote_table_name(table_name)}" end @@ -266,7 +302,7 @@ module ActiveRecord end # Removes the column(s) from the table definition. - # ===== Examples + # # remove_column(:suppliers, :qualification) # remove_columns(:suppliers, :qualification, :experience) def remove_column(table_name, *column_names) @@ -276,7 +312,7 @@ module ActiveRecord # 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 = {}) @@ -284,7 +320,7 @@ module ActiveRecord end # Sets a new default value for a column. - # ===== Examples + # # change_column_default(:suppliers, :qualification, 'new') # change_column_default(:accounts, :authorized, 1) # change_column_default(:users, :email, nil) @@ -293,7 +329,7 @@ module ActiveRecord 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" @@ -302,17 +338,8 @@ module ActiveRecord # 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 name, - # unless you pass <tt>:name</tt> as an option. - # - # When creating an index on multiple columns, the first column is used as a name - # for the index. For example, when you specify an index on two columns - # [<tt>:first</tt>, <tt>:last</tt>], the DBMS creates an index for both columns as well as an - # index for the first column <tt>:first</tt>. Using just the first name for this index - # makes sense, because you will never have to create a singular index with this - # name. - # - # ===== Examples + # The index will be named after the table and the column name(s), unless + # you pass <tt>:name</tt> as an option. # # ====== Creating a simple index # add_index(:suppliers, :name) @@ -339,9 +366,24 @@ module ActiveRecord # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) # # Note: SQLite doesn't support index length + # + # ====== Creating an index with a sort order (desc or asc, asc is the default) + # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :party_id => :asc}) + # generates + # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) + # + # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it) + # + # ====== Creating a partial index + # add_index(:accounts, [:branch_id, :party_id], :unique => true, :where => "active") + # generates + # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active + # + # Note: only supported by PostgreSQL + # def add_index(table_name, column_name, options = {}) - index_name, index_type, index_columns = add_index_options(table_name, column_name, options) - execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})" + index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options) + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" end # Remove the given index from the table. @@ -377,7 +419,7 @@ module ActiveRecord def index_name(table_name, options) #:nodoc: if Hash === options # legacy support if options[:column] - "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}" + "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}" elsif options[:name] options[:name] else @@ -405,37 +447,20 @@ module ActiveRecord def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name - migrated = select_values("SELECT version FROM #{sm_table}") - migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n") + + ActiveRecord::SchemaMigration.order('version').all.map { |sm| + "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');" + }.join "\n\n" end # Should not be called normally, but this operation is non-destructive. # The migrations module handles this automatically. def initialize_schema_migrations_table - sm_table = ActiveRecord::Migrator.schema_migrations_table_name - - unless table_exists?(sm_table) - create_table(sm_table, :id => false) do |schema_migrations_table| - schema_migrations_table.column :version, :string, :null => false - end - add_index sm_table, :version, :unique => true, - :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" - - # Backwards-compatibility: if we find schema_info, assume we've - # migrated up to that point: - si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix - - if table_exists?(si_table) - - old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i - assume_migrated_upto_version(old_version) - drop_table(si_table) - end - end + ActiveRecord::SchemaMigration.create_table end def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths) - migrations_paths = Array.wrap(migrations_paths) + migrations_paths = Array(migrations_paths) version = version.to_i sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) @@ -504,15 +529,15 @@ module ActiveRecord end # Adds timestamps (created_at and updated_at) columns to the named table. - # ===== Examples + # # add_timestamps(:suppliers) def add_timestamps(table_name) - add_column table_name, :created_at, :datetime, :null => false - add_column table_name, :updated_at, :datetime, :null => false + add_column table_name, :created_at, :datetime + add_column table_name, :updated_at, :datetime end # Removes the timestamp columns (created_at and updated_at) from the table definition. - # ===== Examples + # # remove_timestamps(:suppliers) def remove_timestamps(table_name) remove_column table_name, :updated_at @@ -520,9 +545,29 @@ module ActiveRecord end protected + def add_index_sort_order(option_strings, column_names, options = {}) + if options.is_a?(Hash) && order = options[:order] + case order + when Hash + column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)} + when String + column_names.each {|name| option_strings[name] += " #{order.upcase}"} + end + end + + return option_strings + end + # Overridden by the mysql adapter for supporting index lengths def quoted_columns_for_index(column_names, options = {}) - column_names.map {|name| quote_column_name(name) } + option_strings = Hash[column_names.map {|name| [name, '']}] + + # add index sort order if supported + if supports_index_sort_order? + option_strings = add_index_sort_order(option_strings, column_names, options) + end + + column_names.map {|name| quote_column_name(name) + option_strings[name]} end def options_include_default?(options) @@ -530,12 +575,15 @@ module ActiveRecord end def add_index_options(table_name, column_name, options = {}) - column_names = Array.wrap(column_name) + column_names = Array(column_name) index_name = index_name(table_name, :column => column_names) if Hash === options # legacy support, since this param was a string index_type = options[:unique] ? "UNIQUE" : "" index_name = options[:name].to_s if options.key?(:name) + if supports_partial_index? + index_options = options[:where] ? " WHERE #{options[:where]}" : "" + end else index_type = options end @@ -548,7 +596,7 @@ module ActiveRecord end index_columns = quoted_columns_for_index(column_names, options).join(", ") - [index_name, index_type, index_columns] + [index_name, index_type, index_columns, index_options] end def index_name_for_remove(table_name, options = {}) @@ -562,8 +610,6 @@ module ActiveRecord end def columns_for_remove(table_name, *column_names) - column_names = column_names.flatten - raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank? column_names.map {|column_name| quote_column_name(column_name) } end |