1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/lib/active_record/result.rb

169 lines
4.4 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
2010-10-12 18:57:26 -04:00
module ActiveRecord
###
# This class encapsulates a result returned from calling
# {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
# on any database connection adapter. For example:
2010-10-12 18:57:26 -04:00
#
# 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_a
# # => [{"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
2010-10-12 18:57:26 -04:00
class Result
include Enumerable
attr_reader :columns, :rows, :column_types
2010-10-12 18:57:26 -04:00
def initialize(columns, rows, column_types = {})
@columns = columns
@rows = rows
@hash_rows = nil
@column_types = column_types
2010-10-12 18:57:26 -04:00
end
2018-06-26 14:53:59 -04:00
# Returns true if this result set includes the column named +name+
Speed up homogeneous AR lists / reduce allocations This commit speeds up allocating homogeneous lists of AR objects. We can know if the result set contains an STI column before initializing every AR object, so this change pulls the "does this result set contain an STI column?" test up, then uses a specialized instantiation function. This way we only have to check for an STI column once rather than N times. This change also introduces a new initialization function that is meant for use when allocating AR objects that come from the database. Doing this allows us to eliminate one hash allocation per AR instance. Here is a benchmark: ```ruby require 'active_record' require 'benchmark/ips' ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:" ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table :users, force: true do |t| t.string :name t.timestamps null: false end end class User < ActiveRecord::Base; end 2000.times do User.create!(name: "Gorby") end Benchmark.ips do |x| x.report("find") do User.limit(2000).to_a end end ``` Results: Before: ``` [aaron@TC activerecord (master)]$ be ruby -I lib:~/git/allocation_tracer/lib speed.rb Warming up -------------------------------------- find 5.000 i/100ms Calculating ------------------------------------- find 56.192 (± 3.6%) i/s - 285.000 in 5.080940s ``` After: ``` [aaron@TC activerecord (homogeneous-allocation)]$ be ruby -I lib:~/git/allocation_tracer/lib speed.rb Warming up -------------------------------------- find 7.000 i/100ms Calculating ------------------------------------- find 72.204 (± 2.8%) i/s - 364.000 in 5.044592s ```
2018-06-25 18:52:59 -04:00
def includes_column?(name)
@columns.include? name
end
# Returns the number of elements in the rows array.
2014-10-13 15:01:41 -04:00
def length
@rows.length
end
# Calls the given block once for each element in row collection, passing
# row as parameter.
#
# Returns an +Enumerator+ if no block is given.
2010-10-12 18:57:26 -04:00
def each
if block_given?
hash_rows.each { |row| yield row }
else
hash_rows.to_enum { @rows.size }
end
2010-10-12 18:57:26 -04:00
end
def to_hash
ActiveSupport::Deprecation.warn(<<-MSG.squish)
`ActiveRecord::Result#to_hash` has been renamed to `to_a`.
`to_hash` is deprecated and will be removed in Rails 6.1.
MSG
to_a
end
alias :map! :map
alias :collect! :map
# Returns true if there are no records, otherwise false.
def empty?
rows.empty?
end
# Returns an array of hashes representing each row record.
def to_ary
hash_rows
end
alias :to_a :to_ary
def [](idx)
hash_rows[idx]
end
# Returns the first record from the rows collection.
# If the rows collection is empty, returns +nil+.
def first
return nil if @rows.empty?
Hash[@columns.zip(@rows.first)]
end
# Returns the last record from the rows collection.
# If the rows collection is empty, returns +nil+.
def last
return nil if @rows.empty?
Hash[@columns.zip(@rows.last)]
end
def cast_values(type_overrides = {}) # :nodoc:
if columns.one?
# Separated to avoid allocating an array per row
type = column_type(columns.first, type_overrides)
rows.map do |(value)|
type.deserialize(value)
end
else
types = columns.map { |name| column_type(name, type_overrides) }
rows.map do |values|
Array.new(values.size) { |i| types[i].deserialize(values[i]) }
end
end
end
def initialize_copy(other)
@columns = columns.dup
@rows = rows.dup
@column_types = column_types.dup
@hash_rows = nil
end
2010-10-12 18:57:26 -04:00
private
def column_type(name, type_overrides = {})
type_overrides.fetch(name) do
column_types.fetch(name, Type.default_value)
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(&:-@)
length = columns.length
@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
while index < length
hash[columns[index]] = row[index]
index += 1
end
hash
}
end
end
2010-10-12 18:57:26 -04:00
end
end