Skip to content

Commit d50fc22

Browse files
authored
Merge pull request rails#51744 from Shopify/result-rows
Index Result rows rather than to convert them into hashes
2 parents e2ef1d6 + 10dfdc5 commit d50fc22

File tree

2 files changed

+70
-7
lines changed

2 files changed

+70
-7
lines changed

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,8 @@ def table_structure_with_collation(table_name, basic_structure)
691691
end
692692

693693
basic_structure.map do |column|
694+
column = column.to_h
695+
694696
column_name = column["name"]
695697

696698
if collation_hash.has_key? column_name

activerecord/lib/active_record/result.rb

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,59 @@ module ActiveRecord
3636
class Result
3737
include Enumerable
3838

39+
class IndexedRow
40+
def initialize(column_indexes, row)
41+
@column_indexes = column_indexes
42+
@row = row
43+
end
44+
45+
def size
46+
@column_indexes.size
47+
end
48+
alias_method :length, :size
49+
50+
def each_key(&block)
51+
@column_indexes.each_key(&block)
52+
end
53+
54+
def keys
55+
@column_indexes.keys
56+
end
57+
58+
def ==(other)
59+
if other.is_a?(Hash)
60+
to_hash == other
61+
else
62+
super
63+
end
64+
end
65+
66+
def key?(column)
67+
@column_indexes.key?(column)
68+
end
69+
70+
def fetch(column)
71+
if index = @column_indexes[column]
72+
@row[index]
73+
elsif block_given?
74+
yield
75+
else
76+
raise KeyError, "key not found: #{column.inspect}"
77+
end
78+
end
79+
80+
def [](column)
81+
if index = @column_indexes[column]
82+
@row[index]
83+
end
84+
end
85+
86+
def to_h
87+
@column_indexes.transform_values { |index| @row[index] }
88+
end
89+
alias_method :to_hash, :to_h
90+
end
91+
3992
attr_reader :columns, :rows, :column_types
4093

4194
def self.empty(async: false) # :nodoc:
@@ -67,14 +120,16 @@ def length
67120
end
68121

69122
# Calls the given block once for each element in row collection, passing
70-
# row as parameter.
123+
# row as parameter. Each row is a Hash-like, read only object.
124+
#
125+
# To get real hashes, use +.to_a.each+.
71126
#
72127
# Returns an +Enumerator+ if no block is given.
73128
def each(&block)
74129
if block_given?
75-
hash_rows.each(&block)
130+
indexed_rows.each(&block)
76131
else
77-
hash_rows.to_enum { @rows.size }
132+
indexed_rows.to_enum { @rows.size }
78133
end
79134
end
80135

@@ -134,14 +189,13 @@ def cast_values(type_overrides = {}) # :nodoc:
134189
end
135190

136191
def initialize_copy(other)
137-
@columns = columns
138-
@rows = rows.dup
192+
@rows = rows.dup
139193
@column_types = column_types.dup
140-
@hash_rows = nil
141194
end
142195

143196
def freeze # :nodoc:
144197
hash_rows.freeze
198+
indexed_rows.freeze
145199
super
146200
end
147201

@@ -154,7 +208,7 @@ def column_indexes # :nodoc:
154208
hash[columns[index]] = index
155209
index += 1
156210
end
157-
hash
211+
hash.freeze
158212
end
159213
end
160214

@@ -167,6 +221,13 @@ def column_type(name, index, type_overrides)
167221
end
168222
end
169223

224+
def indexed_rows
225+
@indexed_rows ||= begin
226+
columns = column_indexes
227+
@rows.map { |row| IndexedRow.new(columns, row) }.freeze
228+
end
229+
end
230+
170231
def hash_rows
171232
# We use transform_values to rows.
172233
# This is faster because we avoid any reallocs and avoid hashing entirely.

0 commit comments

Comments
 (0)