mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
8fee923888
We introduced a performance hit by adding an additional iteration through a model's attributes on creation. We don't actually need the values from `Result` to be a hash, we can separate the columns and values and zip them up ourself during the iteration that we have to do.
140 lines
3.4 KiB
Ruby
140 lines
3.4 KiB
Ruby
module ActiveRecord
|
|
###
|
|
# This class encapsulates a Result returned from calling +exec_query+ on any
|
|
# database connection adapter. For example:
|
|
#
|
|
# result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
|
|
# result # => #<ActiveRecord::Result:0xdeadbeef>
|
|
#
|
|
# # Get the column names of the result:
|
|
# result.columns
|
|
# # => ["id", "title", "body"]
|
|
#
|
|
# # Get the record values of the result:
|
|
# result.rows
|
|
# # => [[1, "title_1", "body_1"],
|
|
# [2, "title_2", "body_2"],
|
|
# ...
|
|
# ]
|
|
#
|
|
# # Get an array of hashes representing the result (column => value):
|
|
# result.to_hash
|
|
# # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
|
|
# {"id" => 2, "title" => "title_2", "body" => "body_2"},
|
|
# ...
|
|
# ]
|
|
#
|
|
# # ActiveRecord::Result also includes Enumerable.
|
|
# result.each do |row|
|
|
# puts row['title'] + " " + row['body']
|
|
# end
|
|
class Result
|
|
include Enumerable
|
|
|
|
IDENTITY_TYPE = Type::Value.new # :nodoc:
|
|
|
|
attr_reader :columns, :rows, :column_types
|
|
|
|
def initialize(columns, rows, column_types = {})
|
|
@columns = columns
|
|
@rows = rows
|
|
@hash_rows = nil
|
|
@column_types = column_types
|
|
end
|
|
|
|
def length
|
|
@rows.length
|
|
end
|
|
|
|
def each
|
|
if block_given?
|
|
hash_rows.each { |row| yield row }
|
|
else
|
|
hash_rows.to_enum { @rows.size }
|
|
end
|
|
end
|
|
|
|
def each_pair
|
|
return to_enum(__method__) unless block_given?
|
|
|
|
columns = @columns.map { |c| c.dup.freeze }
|
|
@rows.each do |row|
|
|
yield columns, row
|
|
end
|
|
end
|
|
|
|
def to_hash
|
|
hash_rows
|
|
end
|
|
|
|
alias :map! :map
|
|
alias :collect! :map
|
|
|
|
# Returns true if there are no records.
|
|
def empty?
|
|
rows.empty?
|
|
end
|
|
|
|
def to_ary
|
|
hash_rows
|
|
end
|
|
|
|
def [](idx)
|
|
hash_rows[idx]
|
|
end
|
|
|
|
def last
|
|
hash_rows.last
|
|
end
|
|
|
|
def cast_values(type_overrides = {}) # :nodoc:
|
|
types = columns.map { |name| column_type(name, type_overrides) }
|
|
result = rows.map do |values|
|
|
types.zip(values).map { |type, value| type.type_cast_from_database(value) }
|
|
end
|
|
|
|
columns.one? ? result.map!(&:first) : result
|
|
end
|
|
|
|
def initialize_copy(other)
|
|
@columns = columns.dup
|
|
@rows = rows.dup
|
|
@column_types = column_types.dup
|
|
@hash_rows = nil
|
|
end
|
|
|
|
private
|
|
|
|
def column_type(name, type_overrides = {})
|
|
type_overrides.fetch(name) do
|
|
column_types.fetch(name, IDENTITY_TYPE)
|
|
end
|
|
end
|
|
|
|
def hash_rows
|
|
@hash_rows ||=
|
|
begin
|
|
# We freeze the strings to prevent them getting duped when
|
|
# used as keys in ActiveRecord::Base's @attributes hash
|
|
columns = @columns.map { |c| c.dup.freeze }
|
|
@rows.map { |row|
|
|
# In the past we used Hash[columns.zip(row)]
|
|
# though elegant, the verbose way is much more efficient
|
|
# both time and memory wise cause it avoids a big array allocation
|
|
# this method is called a lot and needs to be micro optimised
|
|
hash = {}
|
|
|
|
index = 0
|
|
length = columns.length
|
|
|
|
while index < length
|
|
hash[columns[index]] = row[index]
|
|
index += 1
|
|
end
|
|
|
|
hash
|
|
}
|
|
end
|
|
end
|
|
end
|
|
end
|