aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb123
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb63
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb41
7 files changed, 262 insertions, 42 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 77a5fe1efb..0ec0576795 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -123,14 +123,14 @@ module ActiveRecord
# A cached lookup for table existence.
def table_exists?(name)
- return true if @tables.key? name
+ return @tables[name] if @tables.key? name
with_connection do |conn|
conn.tables.each { |table| @tables[table] = true }
- @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name)
+ @tables[name] = conn.table_exists?(name) if !@tables.key?(name)
end
- @tables.key? name
+ @tables[name]
end
# Clears out internal caches:
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 989a4fcbca..6f135b56b5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -6,7 +6,7 @@ require 'bigdecimal/util'
module ActiveRecord
module ConnectionAdapters #:nodoc:
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
@@ -46,13 +46,13 @@ module ActiveRecord
# +change_table+ is actually of this type:
#
# class SomeMigration < ActiveRecord::Migration
- # def self.up
+ # def up
# create_table :foo do |t|
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
# end
# end
#
- # def self.down
+ # def down
# ...
# end
# end
@@ -479,4 +479,3 @@ module ActiveRecord
end
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 910ef3efce..11da84e245 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -154,11 +154,17 @@ module ActiveRecord
# )
#
# See also TableDefinition#column for details on how to create columns.
- def create_table(table_name, options = {})
+ def create_table(table_name, options = {}, &blk)
td = table_definition
td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
- yield td if block_given?
+ if block_given?
+ if blk.arity == 1
+ yield td
+ else
+ td.instance_eval(&blk)
+ end
+ end
if options[:force] && table_exists?(table_name)
drop_table(table_name)
@@ -235,14 +241,19 @@ module ActiveRecord
#
# See also Table for details on
# all of the various column transformation
- def change_table(table_name, options = {})
- if supports_bulk_alter? && options[:bulk]
- recorder = ActiveRecord::Migration::CommandRecorder.new(self)
- yield Table.new(table_name, recorder)
- bulk_change_table(table_name, recorder.commands)
- else
- yield Table.new(table_name, self)
+ def change_table(table_name, options = {}, &blk)
+ bulk_change = supports_bulk_alter? && options[:bulk]
+ recorder = bulk_change ? ActiveRecord::Migration::CommandRecorder.new(self) : self
+ table = Table.new(table_name, recorder)
+
+ if block_given?
+ if blk.arity == 1
+ yield table
+ else
+ table.instance_eval(&blk)
+ end
end
+ bulk_change_table(table_name, recorder.commands) if bulk_change
end
# Renames a table.
@@ -339,6 +350,14 @@ 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, :part_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)
+ #
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})"
@@ -520,9 +539,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)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4c3a8f7233..c47bcfc406 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -130,6 +130,11 @@ module ActiveRecord
false
end
+ # Does this adapter support index sort order?
+ def supports_index_sort_order?
+ false
+ end
+
# QUOTING ==================================================
# Override to return the quoted table name. Defaults to column quoting.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index dd573ba569..baf4c043c4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -155,6 +155,12 @@ module ActiveRecord
true
end
+ # Technically MySQL allows to create indexes with the sort order syntax
+ # but at the moment (5.5) it doesn't yet implement them
+ def supports_index_sort_order?
+ true
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -222,6 +228,80 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
+ def explain(arel)
+ sql = "EXPLAIN #{to_sql(arel)}"
+ start = Time.now
+ result = exec_query(sql, 'EXPLAIN')
+ elapsed = Time.now - start
+
+ ExplainPrettyPrinter.new.pp(result, elapsed)
+ end
+
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
+ # MySQL shell:
+ #
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # 2 rows in set (0.00 sec)
+ #
+ # This is an exercise in Ruby hyperrealism :).
+ def pp(result, elapsed)
+ widths = compute_column_widths(result)
+ separator = build_separator(widths)
+
+ pp = []
+
+ pp << separator
+ pp << build_cells(result.columns, widths)
+ pp << separator
+
+ result.rows.each do |row|
+ pp << build_cells(row, widths)
+ end
+
+ pp << separator
+ pp << build_footer(result.rows.length, elapsed)
+
+ pp.join("\n") + "\n"
+ end
+
+ private
+
+ def compute_column_widths(result)
+ [].tap do |widths|
+ result.columns.each_with_index do |column, i|
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
+ widths << cells_in_column.map(&:length).max
+ end
+ end
+ end
+
+ def build_separator(widths)
+ padding = 1
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
+ end
+
+ def build_cells(items, widths)
+ cells = []
+ items.each_with_index do |item, i|
+ item = 'NULL' if item.nil?
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
+ cells << item.to_s.send(justifier, widths[i])
+ end
+ '| ' + cells.join(' | ') + ' |'
+ end
+
+ def build_footer(nrows, elapsed)
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
+ end
+ end
+
# Executes the SQL statement in the context of this connection.
def execute(sql, name = nil)
if name == :skip_logging
@@ -496,8 +576,17 @@ module ActiveRecord
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table)
- execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result|
- keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] }
+ sql = <<-SQL
+ SELECT t.constraint_type, k.column_name
+ FROM information_schema.table_constraints t
+ JOIN information_schema.key_column_usage k
+ USING (constraint_name, table_schema, table_name)
+ WHERE t.table_schema = DATABASE()
+ AND t.table_name = '#{table}'
+ SQL
+
+ execute_and_free(sql, 'SCHEMA') do |result|
+ keys = each_hash(result).select { |row| row[:constraint_type] == 'PRIMARY KEY' }.map { |row| row[:column_name] }
keys.length == 1 ? [keys.first, nil] : nil
end
end
@@ -526,17 +615,29 @@ module ActiveRecord
protected
+ def add_index_length(option_strings, column_names, options = {})
+ if options.is_a?(Hash) && length = options[:length]
+ case length
+ when Hash
+ column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)}
+ when Fixnum
+ column_names.each {|name| option_strings[name] += "(#{length})"}
+ end
+ end
+
+ return option_strings
+ end
+
def quoted_columns_for_index(column_names, options = {})
- length = options[:length] if options.is_a?(Hash)
+ option_strings = Hash[column_names.map {|name| [name, '']}]
- 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
+ # add index length
+ option_strings = add_index_length(option_strings, column_names, options)
+
+ # add index sort order
+ option_strings = add_index_sort_order(option_strings, column_names, options)
+
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
end
def translate_exception(exception, message)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e8a43e7bce..b7918c7f07 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -247,6 +247,10 @@ module ActiveRecord
true
end
+ def supports_index_sort_order?
+ true
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -513,6 +517,48 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
+ def explain(arel)
+ sql = "EXPLAIN #{to_sql(arel)}"
+ ExplainPrettyPrinter.new.pp(exec_query(sql))
+ end
+
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
+ # PostgreSQL shell:
+ #
+ # QUERY PLAN
+ # ------------------------------------------------------------------------------
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
+ # Join Filter: (posts.user_id = users.id)
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
+ # Index Cond: (id = 1)
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
+ # Filter: (posts.user_id = 1)
+ # (6 rows)
+ #
+ def pp(result)
+ header = result.columns.first
+ lines = result.rows.map(&:first)
+
+ # We add 2 because there's one char of padding at both sides, note
+ # the extra hyphens in the example above.
+ width = [header, *lines].map(&:length).max + 2
+
+ pp = []
+
+ pp << header.center(width).rstrip
+ pp << '-' * width
+
+ pp += lines.map {|line| " #{line}"}
+
+ nrows = result.rows.length
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ pp << "(#{nrows} #{rows_label})"
+
+ pp.join("\n") + "\n"
+ end
+ end
+
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
def select_rows(sql, name = nil)
@@ -754,16 +800,15 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
- schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
result = query(<<-SQL, name)
- SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
WHERE i.relkind = 'i'
AND d.indisprimary = 'f'
AND t.relname = '#{table_name}'
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
ORDER BY i.relname
SQL
@@ -772,7 +817,8 @@ module ActiveRecord
index_name = row[0]
unique = row[1] == 't'
indkey = row[2].split(" ")
- oid = row[3]
+ inddef = row[3]
+ oid = row[4]
columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
SELECT a.attnum, a.attname
@@ -782,7 +828,12 @@ module ActiveRecord
SQL
column_names = columns.values_at(*indkey).compact
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
+
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
end.compact
end
@@ -826,7 +877,7 @@ module ActiveRecord
# Returns the active schema search path.
def schema_search_path
- @schema_search_path ||= query('SHOW search_path')[0][0]
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
end
# Returns the current client message level.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index e0e957a12c..35df0a1542 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -157,6 +157,10 @@ module ActiveRecord
sqlite_version >= '3.1.0'
end
+ def supports_index_sort_order?
+ sqlite_version >= '3.3.0'
+ end
+
def native_database_types #:nodoc:
{
:primary_key => default_primary_key_type,
@@ -218,6 +222,25 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
+ def explain(arel)
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel)}"
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN'))
+ end
+
+ class ExplainPrettyPrinter
+ # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
+ # the output of the SQLite shell:
+ #
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
+ #
+ def pp(result) # :nodoc:
+ result.rows.map do |row|
+ row.join('|')
+ end.join("\n") + "\n"
+ end
+ end
+
def exec_query(sql, name = nil, binds = [])
log(sql, name, binds) do
@@ -457,28 +480,30 @@ module ActiveRecord
drop_table(from)
end
- def copy_table(from, to, options = {}) #:nodoc:
- options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
+ def copy_table(from, to, options = {}, &block) #:nodoc:
+ from_columns, from_primary_key = columns(from), primary_key(from)
+ options = options.merge(:id => (!from_columns.detect {|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
+ table_definition = nil
create_table(to, options) do |definition|
- @definition = definition
- columns(from).each do |column|
+ table_definition = definition
+ from_columns.each do |column|
column_name = options[:rename] ?
(options[:rename][column.name] ||
options[:rename][column.name.to_sym] ||
column.name) : column.name
- @definition.column(column_name, column.type,
+ table_definition.column(column_name, column.type,
:limit => column.limit, :default => column.default,
:precision => column.precision, :scale => column.scale,
:null => column.null)
end
- @definition.primary_key(primary_key(from)) if primary_key(from)
- yield @definition if block_given?
+ table_definition.primary_key from_primary_key if from_primary_key
+ table_definition.instance_eval(&block) if block
end
copy_table_indexes(from, to, options[:rename] || {})
copy_table_contents(from, to,
- @definition.columns.map {|column| column.name},
+ table_definition.columns.map {|column| column.name},
options[:rename] || {})
end