From 9b6207c3d0596599078b4171caa71b6e7c49ebc9 Mon Sep 17 00:00:00 2001
From: Jeremy Kemper <jeremy@bitsweat.net>
Date: Tue, 16 Oct 2007 05:06:33 +0000
Subject: Quote table names. Defaults to column quoting. Closes #4593.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7932 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
---
 activerecord/CHANGELOG                             |   2 +
 activerecord/lib/active_record/base.rb             |  26 ++--
 .../abstract/database_statements.rb                |   4 +-
 .../connection_adapters/abstract/quoting.rb        |  12 +-
 .../abstract/schema_statements.rb                  |  26 ++--
 .../connection_adapters/abstract_adapter.rb        |   6 +
 .../connection_adapters/mysql_adapter.rb           |  32 ++--
 activerecord/lib/active_record/fixtures.rb         |   2 +-
 activerecord/test/active_schema_test_mysql.rb      |   6 +-
 .../test/fixtures/reserved_words/distinct.yml      |   5 +
 .../fixtures/reserved_words/distincts_selects.yml  |  11 ++
 .../test/fixtures/reserved_words/group.yml         |  14 ++
 .../test/fixtures/reserved_words/select.yml        |   8 +
 .../test/fixtures/reserved_words/values.yml        |   7 +
 activerecord/test/reserved_word_test_mysql.rb      | 168 +++++++++++++++++++++
 15 files changed, 283 insertions(+), 46 deletions(-)
 create mode 100644 activerecord/test/fixtures/reserved_words/distinct.yml
 create mode 100644 activerecord/test/fixtures/reserved_words/distincts_selects.yml
 create mode 100644 activerecord/test/fixtures/reserved_words/group.yml
 create mode 100644 activerecord/test/fixtures/reserved_words/select.yml
 create mode 100644 activerecord/test/fixtures/reserved_words/values.yml
 create mode 100644 activerecord/test/reserved_word_test_mysql.rb

diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 1e2907c50b..f9a9c58b60 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
 *SVN*
 
+* Quote table names. Defaults to column quoting.  #4593 [Justin Lynn, gwcoffey, eadz, Dmitry V. Sabanin, Jeremy Kemper]
+
 * Alias association #build to #new so it behaves predictably.  #8787 [lifofifo]
 
 * Add notes to documentation regarding attr_readonly behavior with counter caches and polymorphic associations.  Closes #9835 [saimonmoore, rick]
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 67a0299490..4eb99551cd 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -531,7 +531,7 @@ module ActiveRecord #:nodoc:
       # calling the destroy method). Example:
       #   Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
       def delete_all(conditions = nil)
-        sql = "DELETE FROM #{table_name} "
+        sql = "DELETE FROM #{quoted_table_name} "
         add_conditions!(sql, conditions, scope(:find))
         connection.delete(sql, "#{name} Delete all")
       end
@@ -1033,7 +1033,7 @@ module ActiveRecord #:nodoc:
       
         def find_one(id, options)
           conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
-          options.update :conditions => "#{table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
+          options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
 
           # Use find_every(options).first since the primary key condition
           # already ensures we have a single record. Using find_initial adds
@@ -1048,7 +1048,7 @@ module ActiveRecord #:nodoc:
         def find_some(ids, options)
           conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
           ids_list   = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
-          options.update :conditions => "#{table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}"
+          options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}"
 
           result = find_every(options)
 
@@ -1126,8 +1126,8 @@ module ActiveRecord #:nodoc:
 
         def construct_finder_sql(options)
           scope = scope(:find)
-          sql  = "SELECT #{(scope && scope[:select]) || options[:select] || (options[:joins] && table_name + '.*') || '*'} "
-          sql << "FROM #{(scope && scope[:from]) || options[:from] || table_name} "
+          sql  = "SELECT #{(scope && scope[:select]) || options[:select] || (options[:joins] && quoted_table_name + '.*') || '*'} "
+          sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
 
           add_joins!(sql, options, scope)
           add_conditions!(sql, options[:conditions], scope)
@@ -1220,8 +1220,8 @@ module ActiveRecord #:nodoc:
 
         def type_condition
           quoted_inheritance_column = connection.quote_column_name(inheritance_column)
-          type_condition = subclasses.inject("#{table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
-            condition << "OR #{table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
+          type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
+            condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
           end
 
           " (#{type_condition}) "
@@ -1572,7 +1572,7 @@ module ActiveRecord #:nodoc:
         #     # => "age BETWEEN 13 AND 18"
         def sanitize_sql_hash_for_conditions(attrs)
           conditions = attrs.map do |attr, value|
-            "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
+            "#{quoted_table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
           end.join(' AND ')
 
           replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
@@ -1742,7 +1742,7 @@ module ActiveRecord #:nodoc:
       def destroy
         unless new_record?
           connection.delete <<-end_sql, "#{self.class.name} Destroy"
-            DELETE FROM #{self.class.table_name}
+            DELETE FROM #{self.class.quoted_table_name}
             WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}
           end_sql
         end
@@ -1986,7 +1986,7 @@ module ActiveRecord #:nodoc:
         quoted_attributes = attributes_with_quotes(false, false)
         return 0 if quoted_attributes.empty?
         connection.update(
-          "UPDATE #{self.class.table_name} " +
+          "UPDATE #{self.class.quoted_table_name} " +
           "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
           "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
           "#{self.class.name} Update"
@@ -2005,7 +2005,7 @@ module ActiveRecord #:nodoc:
         statement = if quoted_attributes.empty?
           connection.empty_insert_statement(self.class.table_name)
         else
-          "INSERT INTO #{self.class.table_name} " +
+          "INSERT INTO #{self.class.quoted_table_name} " +
           "(#{quoted_column_names.join(', ')}) " +
           "VALUES(#{quoted_attributes.values.join(', ')})"
         end
@@ -2180,6 +2180,10 @@ module ActiveRecord #:nodoc:
         end
       end
 
+      def self.quoted_table_name
+        self.connection.quote_table_name(self.table_name)
+      end
+
       def quote_columns(quoter, hash)
         hash.inject({}) do |quoted, (name, value)|
           quoted[quoter.quote_column_name(name)] = value
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 341b104f06..066baaba45 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -136,11 +136,11 @@ module ActiveRecord
       # Inserts the given fixture into the table. Overridden in adapters that require
       # something beyond a simple insert (eg. Oracle).
       def insert_fixture(fixture, table_name)
-        execute "INSERT INTO #{table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
+        execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
       end
 
       def empty_insert_statement(table_name)
-        "INSERT INTO #{table_name} VALUES(DEFAULT)"
+        "INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)"
       end
 
       protected
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index b3b3d70359..3a7bf35248 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -39,10 +39,14 @@ module ActiveRecord
         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
+      # Quotes the column name. Defaults to no quoting.
+      def quote_column_name(column_name)
+        column_name
+      end
+
+      # Quotes the table name. Defaults to column name quoting.
+      def quote_table_name(table_name)
+        quote_column_name(table_name)
       end
 
       def quoted_true
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 f27b2287f4..d4fcded32a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -87,39 +87,39 @@ module ActiveRecord
       #  )
       #
       # See also TableDefinition#column for details on how to create columns.
-      def create_table(name, options = {})
+      def create_table(table_name, options = {})
         table_definition = TableDefinition.new(self)
         table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
 
         yield table_definition
 
         if options[:force]
-          drop_table(name, options) rescue nil
+          drop_table(table_name, options) rescue nil
         end
 
         create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
-        create_sql << "#{name} ("
+        create_sql << "#{quote_table_name(table_name)} ("
         create_sql << table_definition.to_sql
         create_sql << ") #{options[:options]}"
         execute create_sql
       end
-      
+
       # Renames a table.
       # ===== Example
       #  rename_table('octopuses', 'octopi')
-      def rename_table(name, new_name)
+      def rename_table(table_name, new_name)
         raise NotImplementedError, "rename_table is not implemented"
       end
 
       # Drops a table from the database.
-      def drop_table(name, options = {})
-        execute "DROP TABLE #{name}"
+      def drop_table(table_name, options = {})
+        execute "DROP TABLE #{quote_table_name(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 #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+        add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
         add_column_options!(add_column_sql, options)
         execute(add_column_sql)
       end
@@ -128,7 +128,7 @@ module ActiveRecord
       # ===== Examples
       #  remove_column(:suppliers, :qualification)
       def remove_column(table_name, column_name)
-        execute "ALTER TABLE #{table_name} DROP #{quote_column_name(column_name)}"
+        execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
       end
 
       # Changes the column's definition according to the new options.
@@ -194,7 +194,7 @@ module ActiveRecord
           index_type = options
         end
         quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
-        execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names})"
+        execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
       end
 
       # Remove the given index from the table.
@@ -234,8 +234,8 @@ module ActiveRecord
       # 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)"
+          execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:integer)})"
+          execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(0)"
         rescue ActiveRecord::StatementInvalid
           # Schema has been initialized
         end
@@ -244,7 +244,7 @@ module ActiveRecord
       def dump_schema_information #:nodoc:
         begin
           if (current_schema = ActiveRecord::Migrator.current_version) > 0
-            return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema})" 
+            return "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})" 
           end
         rescue ActiveRecord::StatementInvalid 
           # No Schema Info
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 18de0e0739..0741a47cc2 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -63,6 +63,12 @@ module ActiveRecord
         rt
       end
 
+      # QUOTING ==================================================
+
+      # Override to return the quoted table name if the database needs it
+      def quote_table_name(name)
+        name
+      end
 
       # CONNECTION MANAGEMENT ====================================
 
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 62cfc345a3..6ef7bb5b1a 100755
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -208,6 +208,10 @@ module ActiveRecord
         "`#{name}`"
       end
 
+      def quote_table_name(name) #:nodoc:
+        quote_column_name(name)
+      end
+
       def quote_string(string) #:nodoc:
         @connection.quote(string)
       end
@@ -322,7 +326,7 @@ module ActiveRecord
 
         select_all(sql).inject("") do |structure, table|
           table.delete('Table_type')
-          structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
+          structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
         end
       end
 
@@ -370,10 +374,14 @@ module ActiveRecord
         tables
       end
 
+      def drop_table(table_name, options = {})
+        super(table_name, options)
+      end
+
       def indexes(table_name, name = nil)#:nodoc:
         indexes = []
         current_index = nil
-        execute("SHOW KEYS FROM #{table_name}", name).each do |row|
+        execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
           if current_index != row[2]
             next if row[2] == "PRIMARY" # skip the primary key
             current_index = row[2]
@@ -386,24 +394,24 @@ module ActiveRecord
       end
 
       def columns(table_name, name = nil)#:nodoc:
-        sql = "SHOW FIELDS FROM #{table_name}"
+        sql = "SHOW FIELDS FROM #{quote_table_name(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))
+      def create_table(table_name, options = {}) #:nodoc:
+        super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
       end
 
-      def rename_table(name, new_name)
-        execute "RENAME TABLE #{name} TO #{new_name}"
+      def rename_table(table_name, new_name)
+        execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
       end
 
       def change_column_default(table_name, column_name, default) #:nodoc:
-        current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
+        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
 
-        execute("ALTER TABLE #{table_name} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
+        execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
       end
 
       def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -415,14 +423,14 @@ module ActiveRecord
           end
         end
 
-        change_column_sql = "ALTER TABLE #{table_name} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+        change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
         add_column_options!(change_column_sql, options)
         execute(change_column_sql)
       end
 
       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 #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
+        current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+        execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
       end
 
 
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6013d6e156..b314340f0c 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -318,7 +318,7 @@ class Fixtures < YAML::Omap
   end
 
   def delete_existing_fixtures
-    @connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
+    @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
   end
 
   def insert_fixtures
diff --git a/activerecord/test/active_schema_test_mysql.rb b/activerecord/test/active_schema_test_mysql.rb
index d7a20f9338..aa1d712409 100644
--- a/activerecord/test/active_schema_test_mysql.rb
+++ b/activerecord/test/active_schema_test_mysql.rb
@@ -13,7 +13,7 @@ class ActiveSchemaTest < Test::Unit::TestCase
   end
 
   def test_drop_table
-    assert_equal "DROP TABLE people", drop_table(:people)
+    assert_equal "DROP TABLE `people`", drop_table(:people)
   end
 
   if current_adapter?(:MysqlAdapter)
@@ -25,11 +25,11 @@ class ActiveSchemaTest < Test::Unit::TestCase
   end
 
   def test_add_column
-    assert_equal "ALTER TABLE people ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
+    assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
   end
 
   def test_add_column_with_limit
-    assert_equal "ALTER TABLE people ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
+    assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
   end
 
   private
diff --git a/activerecord/test/fixtures/reserved_words/distinct.yml b/activerecord/test/fixtures/reserved_words/distinct.yml
new file mode 100644
index 0000000000..0988f89ca6
--- /dev/null
+++ b/activerecord/test/fixtures/reserved_words/distinct.yml
@@ -0,0 +1,5 @@
+distinct1:
+  id: 1
+
+distinct2:
+  id: 2
diff --git a/activerecord/test/fixtures/reserved_words/distincts_selects.yml b/activerecord/test/fixtures/reserved_words/distincts_selects.yml
new file mode 100644
index 0000000000..90e8c95fef
--- /dev/null
+++ b/activerecord/test/fixtures/reserved_words/distincts_selects.yml
@@ -0,0 +1,11 @@
+distincts_selects1:
+  distinct_id: 1
+  select_id: 1
+
+distincts_selects2:
+  distinct_id: 1
+  select_id: 2
+
+distincts_selects3:
+  distinct_id: 2
+  select_id: 3
diff --git a/activerecord/test/fixtures/reserved_words/group.yml b/activerecord/test/fixtures/reserved_words/group.yml
new file mode 100644
index 0000000000..39abea7abb
--- /dev/null
+++ b/activerecord/test/fixtures/reserved_words/group.yml
@@ -0,0 +1,14 @@
+group1:
+  id: 1
+  select_id: 1
+  order: x
+
+group2:
+  id: 2
+  select_id: 2
+  order: y
+
+group3:
+  id: 3
+  select_id: 2
+  order: z
diff --git a/activerecord/test/fixtures/reserved_words/select.yml b/activerecord/test/fixtures/reserved_words/select.yml
new file mode 100644
index 0000000000..a4c35a2b63
--- /dev/null
+++ b/activerecord/test/fixtures/reserved_words/select.yml
@@ -0,0 +1,8 @@
+select1:
+  id: 1
+
+select2:
+  id: 2
+
+select3:
+  id: 3
diff --git a/activerecord/test/fixtures/reserved_words/values.yml b/activerecord/test/fixtures/reserved_words/values.yml
new file mode 100644
index 0000000000..7d109609ab
--- /dev/null
+++ b/activerecord/test/fixtures/reserved_words/values.yml
@@ -0,0 +1,7 @@
+values1:
+  id: 1
+  group_id: 2
+
+values2:
+  id: 2
+  group_id: 1
diff --git a/activerecord/test/reserved_word_test_mysql.rb b/activerecord/test/reserved_word_test_mysql.rb
new file mode 100644
index 0000000000..c740a80e81
--- /dev/null
+++ b/activerecord/test/reserved_word_test_mysql.rb
@@ -0,0 +1,168 @@
+require "#{File.dirname(__FILE__)}/abstract_unit"
+
+class Group < ActiveRecord::Base
+  Group.table_name = 'group'
+  belongs_to :select, :class_name => 'Select'
+  has_one :values
+end
+
+class Select < ActiveRecord::Base
+  Select.table_name = 'select'
+  has_many :groups
+end
+
+class Values < ActiveRecord::Base
+  Values.table_name = 'values'
+end
+
+class Distinct < ActiveRecord::Base
+  Distinct.table_name = 'distinct'
+  has_and_belongs_to_many :selects
+  has_many :values, :through => :groups
+end
+
+# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
+# reserved word names (ie: group, order, values, etc...)
+class MysqlReservedWordTest < Test::Unit::TestCase
+  def setup
+    @connection = ActiveRecord::Base.connection
+
+    # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
+    # will fail with these table names if these test cases fail
+
+    create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int',
+      'select'=>'id int auto_increment primary key',
+      'values'=>'id int auto_increment primary key, group_id int',
+      'distinct'=>'id int auto_increment primary key',
+      'distincts_selects'=>'distinct_id int, select_id int'
+  end
+
+  def teardown
+    drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+  end
+
+  # create tables with reserved-word names and columns
+  def test_create_tables
+    assert_nothing_raised {
+      @connection.create_table :order do |t|
+        t.column :group, :string
+      end
+    }
+  end
+
+  # rename tables with reserved-word names
+  def test_rename_tables
+    assert_nothing_raised { @connection.rename_table(:group, :order) }
+  end
+
+  # alter column with a reserved-word name in a table with a reserved-word name
+  def test_change_columns
+    assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') }
+    #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
+    assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) }
+    assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+  end
+
+  # dump structure of table with reserved word name
+  def test_structure_dump
+    assert_nothing_raised { @connection.structure_dump  }
+  end
+
+  # introspect table with reserved word name
+  def test_introspect
+    assert_nothing_raised { @connection.columns(:group) }
+    assert_nothing_raised { @connection.indexes(:group) }
+  end
+
+  #fixtures
+  self.use_instantiated_fixtures = true
+  self.use_transactional_fixtures = false
+
+  #fixtures :group
+
+  def test_fixtures
+    f = create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+
+    assert_nothing_raised {
+      f.each do |x|
+        x.delete_existing_fixtures
+      end
+    }
+
+    assert_nothing_raised {
+      f.each do |x|
+        x.insert_fixtures
+      end
+    }
+  end
+
+  #activerecord model class with reserved-word table name
+  def test_activerecord_model
+    create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+    x = nil
+    assert_nothing_raised { x = Group.new }
+    x.order = 'x'
+    assert_nothing_raised { x.save }
+    x.order = 'y'
+    assert_nothing_raised { x.save }
+    assert_nothing_raised { y = Group.find_by_order('y') }
+    assert_nothing_raised { y = Group.find(1) }
+    x = Group.find(1)
+  end
+
+  # has_one association with reserved-word table name
+  def test_has_one_associations
+    create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+    v = nil
+    assert_nothing_raised { v = Group.find(1).values }
+    assert_equal v.id, 2
+  end
+
+  # belongs_to association with reserved-word table name
+  def test_belongs_to_associations
+    create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+    gs = nil
+    assert_nothing_raised { gs = Select.find(2).groups }
+    assert_equal gs.length, 2
+    assert(gs.collect{|x| x.id}.sort == [2, 3])
+  end
+
+  # has_and_belongs_to_many with reserved-word table name
+  def test_has_and_belongs_to_many
+    create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+    s = nil
+    assert_nothing_raised { s = Distinct.find(1).selects }
+    assert_equal s.length, 2
+    assert(s.collect{|x|x.id}.sort == [1, 2])
+  end
+
+  # activerecord model introspection with reserved-word table and column names
+  def test_activerecord_introspection
+    assert_nothing_raised { Group.table_exists? }
+    assert_nothing_raised { Group.columns }
+  end
+
+  #the following functions were added to DRY test cases
+
+  private
+  # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
+  def create_test_fixtures(*fixture_names)
+    fixture_path = "./test/fixtures/reserved_words"
+    Fixtures.create_fixtures(fixture_path, fixture_names)
+  end
+
+  # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
+  def drop_tables_directly(table_names, connection = @connection)
+    table_names.each do |name|
+      connection.execute("DROP TABLE IF EXISTS `#{name}`")
+    end
+  end
+
+  # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
+  def create_tables_directly (tables, connection = @connection)
+    tables.each do |table_name, column_properties|
+      connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
+    end
+  end
+
+end
-- 
cgit v1.2.3