Navigation Menu

Skip to content

Commit

Permalink
Merge pull request #13313 from ccutrer/temp-tables
Browse files Browse the repository at this point in the history
support creating temporary tables from queries

Conflicts:
	activerecord/CHANGELOG.md
  • Loading branch information
rafaelfranca committed Dec 15, 2013
2 parents d150387 + 75a2e4a commit a09659d
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 17 deletions.
10 changes: 10 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,13 @@
* Respect temporary option when dropping tables with MySQL.

Normal DROP TABLE also works, but commits the transaction.

*Cody Cutrer*

* Add option to create tables from a query.

*Cody Cutrer*

* `db:test:clone` and `db:test:prepare` must load Rails environment.

`db:test:clone` and `db:test:prepare` use `ActiveRecord::Base`. configurations,
Expand Down
Expand Up @@ -34,9 +34,10 @@ def visit_ColumnDefinition(o)

def visit_TableDefinition(o)
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
create_sql << "#{quote_table_name(o.name)} ("
create_sql << o.columns.map { |c| accept c }.join(', ')
create_sql << ") #{o.options}"
create_sql << "#{quote_table_name(o.name)} "
create_sql << "(#{o.columns.map { |c| accept c }.join(', ')}) " unless o.as
create_sql << "#{o.options}"
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
end

Expand Down
Expand Up @@ -49,14 +49,15 @@ class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
attr_reader :name, :temporary, :options
attr_reader :name, :temporary, :options, :as

def initialize(types, name, temporary, options)
def initialize(types, name, temporary, options, as = nil)
@columns_hash = {}
@indexes = {}
@native = types
@temporary = temporary
@options = options
@as = as
@name = name
end

Expand Down
Expand Up @@ -131,6 +131,9 @@ def column_exists?(table_name, column_name, type = nil, options = {})
# [<tt>:force</tt>]
# Set to true to drop the table before creating it.
# Defaults to false.
# [<tt>:as]
# SQL to use to generate the table. When this option is used, the block is
# ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
#
# ====== Add a backend specific option to the generated SQL (MySQL)
#
Expand Down Expand Up @@ -169,19 +172,31 @@ def column_exists?(table_name, column_name, type = nil, options = {})
# supplier_id int
# )
#
# ====== Create a temporary table based on a query
#
# create_table(:long_query, temporary: true,
# as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
#
# generates:
#
# CREATE TEMPORARY TABLE long_query AS
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
td = create_table_definition table_name, options[:temporary], options[:options]
td = create_table_definition table_name, options[:temporary], options[:options], options[:as]

unless options[:id] == false
pk = options.fetch(:primary_key) {
Base.get_primary_key table_name.to_s.singularize
}
if !options[:as]
unless options[:id] == false
pk = options.fetch(:primary_key) {
Base.get_primary_key table_name.to_s.singularize
}

td.primary_key pk, options.fetch(:id, :primary_key), options
end
td.primary_key pk, options.fetch(:id, :primary_key), options
end

yield td if block_given?
yield td if block_given?
end

if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
Expand Down Expand Up @@ -826,8 +841,8 @@ def rename_column_indexes(table_name, column_name, new_column_name)
end

private
def create_table_definition(name, temporary, options)
TableDefinition.new native_database_types, name, temporary, options
def create_table_definition(name, temporary, options, as = nil)
TableDefinition.new native_database_types, name, temporary, options, as
end

def create_alter_table(name)
Expand Down
Expand Up @@ -492,6 +492,10 @@ def rename_table(table_name, new_name)
rename_table_indexes(table_name, new_name)
end

def drop_table(table_name, options = {})
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
end

def rename_index(table_name, old_name, new_name)
if (version[0] == 5 && version[1] >= 7) || version[0] >= 6
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
Expand Down
Expand Up @@ -973,8 +973,8 @@ def extract_table_ref_from_insert_sql(sql)
$1.strip if $1
end

def create_table_definition(name, temporary, options)
TableDefinition.new native_database_types, name, temporary, options
def create_table_definition(name, temporary, options, as = nil)
TableDefinition.new native_database_types, name, temporary, options, as
end

def update_table_definition(table_name, base)
Expand Down
9 changes: 9 additions & 0 deletions activerecord/test/cases/adapters/mysql2/schema_test.rb
Expand Up @@ -65,6 +65,15 @@ def test_dump_indexes
assert_nil index_c.using
assert_equal :fulltext, index_c.type
end

def test_drop_temporary_table
@connection.transaction do
@connection.create_table(:temp_table, temporary: true)
# if it doesn't properly say DROP TEMPORARY TABLE, the transaction commit
# will complain that no transaction is active
@connection.drop_table(:temp_table, temporary: true)
end
end
end
end
end
26 changes: 26 additions & 0 deletions activerecord/test/cases/migration_test.rb
Expand Up @@ -443,6 +443,32 @@ def test_create_table_with_binary_column
Person.connection.drop_table :binary_testings rescue nil
end

def test_create_table_with_query
Person.connection.drop_table :table_from_query_testings rescue nil
Person.connection.create_table(:person, force: true)

Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"

columns = Person.connection.columns(:table_from_query_testings)
assert_equal 1, columns.length
assert_equal "id", columns.first.name

Person.connection.drop_table :table_from_query_testings rescue nil
end

def test_create_table_with_query_from_relation
Person.connection.drop_table :table_from_query_testings rescue nil
Person.connection.create_table(:person, force: true)

Person.connection.create_table :table_from_query_testings, as: Person.select(:id)

columns = Person.connection.columns(:table_from_query_testings)
assert_equal 1, columns.length
assert_equal "id", columns.first.name

Person.connection.drop_table :table_from_query_testings rescue nil
end

if current_adapter? :OracleAdapter
def test_create_table_with_custom_sequence_name
# table name is 29 chars, the standard sequence name will
Expand Down

0 comments on commit a09659d

Please sign in to comment.