1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
# frozen_string_literal: true
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:
#
# 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
attr_reader :columns, :rows, :column_types
def initialize(columns, rows, column_types = {})
@columns = columns
@rows = rows
@hash_rows = nil
@column_types = column_types
end
# Returns the number of elements in the rows array.
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.
def each
if block_given?
hash_rows.each { |row| yield row }
else
hash_rows.to_enum { @rows.size }
end
end
# Returns an array of hashes representing each row record.
def to_hash
hash_rows
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
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:
types = columns.map { |name| column_type(name, type_overrides) }
result = rows.map do |values|
types.zip(values).map { |type, value| type.deserialize(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, 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(&:-@)
@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
|