From 8d2f6c16e381f5fff6d3f24f5c73a443577a1488 Mon Sep 17 00:00:00 2001
From: Pratik Naik <pratiknaik@gmail.com>
Date: Sun, 9 May 2010 12:42:48 +0100
Subject: Revert "Revert "Add index length support for MySQL [#1852
 state:open]""

This reverts commit 6626833db13a69786f9f6cd56b9f53c4017c3e39.
---
 activerecord/CHANGELOG                             | 10 ++++++++++
 .../abstract/schema_definitions.rb                 |  2 +-
 .../abstract/schema_statements.rb                  | 23 +++++++++++++++++++++-
 .../connection_adapters/mysql_adapter.rb           | 15 +++++++++++++-
 activerecord/lib/active_record/schema_dumper.rb    |  1 +
 .../test/cases/active_schema_test_mysql.rb         | 17 ++++++++++++++++
 activerecord/test/cases/migration_test.rb          |  8 ++++++++
 7 files changed, 73 insertions(+), 3 deletions(-)

(limited to 'activerecord')

diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 3f42fa34ef..d8f68d7c28 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,15 @@
 *Rails 3.0.0 [beta 4/release candidate] (unreleased)*
 
+* Add index length support for MySQL. #1852 [Emili Parreno, Pratik Naik]
+
+  Example:
+
+    add_index(:accounts, :name, :name => 'by_name', :length => 10)
+    => CREATE INDEX by_name ON accounts(name(10))
+
+    add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
+    => CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
+
 * find_or_create_by_attr(value, ...) works when attr is protected.  #4457 [Santiago Pastorino, Marc-André Lafortune]
 
 * New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save.  #2991 [Brian Durand]
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 6c477e48ce..e42cd99786 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -258,7 +258,7 @@ module ActiveRecord
         end
     end
 
-    class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
+    class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
     end
 
     # Abstract representation of a column definition. Instances of this type
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 e8cba1bd41..1255ef09be 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -256,18 +256,32 @@ module ActiveRecord
       # name.
       #
       # ===== 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_party_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)
+      #
+      # ====== Creating an index with specific key length
+      #  add_index(:accounts, :name, :name => 'by_name', :length => 10)
+      # generates
+      #  CREATE INDEX by_name ON accounts(name(10))
+      #
+      #  add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
+      # generates
+      #  CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
+      #
+      # Note: SQLite doesn't support index length
       def add_index(table_name, column_name, options = {})
         column_names = Array.wrap(column_name)
         index_name   = index_name(table_name, :column => column_names)
@@ -278,7 +292,9 @@ module ActiveRecord
         else
           index_type = options
         end
-        quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
+
+        quoted_column_names = quoted_columns_for_index(column_names, options).join(", ")
+
         execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
       end
 
@@ -430,6 +446,11 @@ module ActiveRecord
       end
 
       protected
+        # 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) }
+        end
+
         def options_include_default?(options)
           options.include?(:default) && !(options[:null] == false && options[:default].nil?)
         end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 55d9d20bb5..470ff8d767 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -461,10 +461,11 @@ module ActiveRecord
           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", [])
+            indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
           end
 
           indexes.last.columns << row[4]
+          indexes.last.lengths << row[7]
         end
         result.free
         indexes
@@ -594,6 +595,18 @@ module ActiveRecord
       end
 
       protected
+        def quoted_columns_for_index(column_names, options = {})
+          length = options[:length] if options.is_a?(Hash)
+
+          quoted_column_names = case length
+          when Hash
+            column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
+          when Fixnum
+            column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
+          else
+            column_names.map {|name| quote_column_name(name) }
+          end
+        end
 
         def translate_exception(exception, message)
           return super unless exception.respond_to?(:errno)
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index c8e1b4f53a..5cb639a300 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -177,6 +177,7 @@ HEADER
             statment_parts << index.columns.inspect
             statment_parts << (':name => ' + index.name.inspect)
             statment_parts << ':unique => true' if index.unique
+            statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index.lengths.compact.present?
 
             '  ' + statment_parts.join(', ')
           end
diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb
index 9aff538ce9..f4d123be15 100644
--- a/activerecord/test/cases/active_schema_test_mysql.rb
+++ b/activerecord/test/cases/active_schema_test_mysql.rb
@@ -15,6 +15,23 @@ class ActiveSchemaTest < ActiveRecord::TestCase
     end
   end
 
+  def test_add_index
+    expected = "CREATE  INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+    assert_equal expected, add_index(:people, :last_name, :length => nil)
+
+    expected = "CREATE  INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+    assert_equal expected, add_index(:people, :last_name, :length => 10)
+
+    expected = "CREATE  INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+    assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
+
+    expected = "CREATE  INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+    assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
+
+    expected = "CREATE  INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+    assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+  end
+
   def test_drop_table
     assert_equal "DROP TABLE `people`", drop_table(:people)
   end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index a3d1ceaa1f..f67344445a 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -92,6 +92,14 @@ if ActiveRecord::Base.connection.supports_migrations?
         assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
         assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
         assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
+        assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) }
+        assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
+        assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) }
+        assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) }
+        assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) }
+        assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
+        assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) }
+        assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
       end
 
       # quoting
-- 
cgit v1.2.3