Skip to content

Commit f37a6dd

Browse files
committed
Lazily build ActiveRecord::Result#column_types
Given this attribute is very rarely accessed, delaying as many computations as possible is preferable.
1 parent 6d9f72b commit f37a6dd

File tree

4 files changed

+43
-27
lines changed

4 files changed

+43
-27
lines changed

activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,14 @@ def cast_result(result)
175175
return ActiveRecord::Result.empty
176176
end
177177

178-
types = {}
179178
fields = result.fields
180-
fields.each_with_index do |fname, i|
181-
ftype = result.ftype i
182-
fmod = result.fmod i
183-
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
179+
types = Array.new(fields.size)
180+
fields.size.times do |index|
181+
ftype = result.ftype(index)
182+
fmod = result.fmod(index)
183+
types[index] = get_oid_type(ftype, fmod, fields[index])
184184
end
185+
185186
ar_result = ActiveRecord::Result.new(fields, result.values, types.freeze)
186187
result.clear
187188
ar_result

activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,7 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
8787
stmt.step
8888
ActiveRecord::Result.empty
8989
else
90-
types = {}
91-
stmt.columns.zip(stmt.types).each_with_index do |(c, t), i|
92-
types[c] = types[i] = type_map.lookup(t)
93-
end
94-
ActiveRecord::Result.new(stmt.columns, stmt.to_a, types.freeze)
90+
ActiveRecord::Result.new(stmt.columns, stmt.to_a, stmt.types.map { |t| type_map.lookup(t) })
9591
end
9692
else
9793
# Don't cache statements if they are not prepared.
@@ -104,11 +100,7 @@ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notif
104100
stmt.step
105101
ActiveRecord::Result.empty
106102
else
107-
types = {}
108-
stmt.columns.zip(stmt.types).each_with_index do |(c, t), i|
109-
types[c] = types[i] = type_map.lookup(t)
110-
end
111-
ActiveRecord::Result.new(stmt.columns, stmt.to_a, types.freeze)
103+
ActiveRecord::Result.new(stmt.columns, stmt.to_a, stmt.types.map { |t| type_map.lookup(t) })
112104
end
113105
ensure
114106
stmt.close

activerecord/lib/active_record/result.rb

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def to_h
8989
alias_method :to_hash, :to_h
9090
end
9191

92-
attr_reader :columns, :rows, :column_types
92+
attr_reader :columns, :rows
9393

9494
def self.empty(async: false) # :nodoc:
9595
if async
@@ -105,7 +105,8 @@ def initialize(columns, rows, column_types = nil)
105105
@columns = columns.each(&:-@).freeze
106106
@rows = rows
107107
@hash_rows = nil
108-
@column_types = column_types || EMPTY_HASH
108+
@column_types = column_types.freeze
109+
@types_hash = nil
109110
@column_indexes = nil
110111
end
111112

@@ -154,6 +155,23 @@ def last(n = nil)
154155
n ? hash_rows.last(n) : hash_rows.last
155156
end
156157

158+
# Returns the +ActiveRecord::Type+ type of all columns.
159+
# Note that not all database adapters return the result types,
160+
# so the hash may be empty.
161+
def column_types
162+
if @column_types
163+
@types_hash ||= begin
164+
types = {}
165+
@columns.each_with_index do |name, index|
166+
types[name] = types[index] = @column_types[index]
167+
end
168+
types.freeze
169+
end
170+
else
171+
EMPTY_HASH
172+
end
173+
end
174+
157175
def result # :nodoc:
158176
self
159177
end
@@ -162,7 +180,7 @@ def cancel # :nodoc:
162180
self
163181
end
164182

165-
def cast_values(type_overrides = {}) # :nodoc:
183+
def cast_values(type_overrides = nil) # :nodoc:
166184
if columns.one?
167185
# Separated to avoid allocating an array per row
168186

@@ -196,15 +214,16 @@ def initialize_copy(other)
196214

197215
def freeze # :nodoc:
198216
hash_rows.freeze
199-
indexed_rows.freeze
217+
indexed_rows
218+
column_types
200219
super
201220
end
202221

203222
def column_indexes # :nodoc:
204223
@column_indexes ||= begin
205224
index = 0
206225
hash = {}
207-
length = columns.length
226+
length = columns.length
208227
while index < length
209228
hash[columns[index]] = index
210229
index += 1
@@ -222,10 +241,14 @@ def indexed_rows # :nodoc:
222241

223242
private
224243
def column_type(name, index, type_overrides)
225-
type_overrides.fetch(name) do
226-
column_types.fetch(index) do
227-
column_types.fetch(name, Type.default_value)
244+
if type_overrides
245+
type_overrides.fetch(name) do
246+
column_type(name, index, nil)
228247
end
248+
elsif @column_types
249+
@column_types[index] || Type.default_value
250+
else
251+
Type.default_value
229252
end
230253
end
231254

activerecord/test/cases/result_test.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def result
8383
test "cast_values returns rows after type casting" do
8484
values = [["1.1", "2.2"], ["3.3", "4.4"]]
8585
columns = ["col1", "col2"]
86-
types = { "col1" => Type::Integer.new, "col2" => Type::Float.new }
86+
types = [Type::Integer.new, Type::Float.new]
8787
result = Result.new(columns, values, types)
8888

8989
assert_equal [[1, 2.2], [3, 4.4]], result.cast_values
@@ -92,7 +92,7 @@ def result
9292
test "cast_values uses identity type for unknown types" do
9393
values = [["1.1", "2.2"], ["3.3", "4.4"]]
9494
columns = ["col1", "col2"]
95-
types = { "col1" => Type::Integer.new }
95+
types = [Type::Integer.new]
9696
result = Result.new(columns, values, types)
9797

9898
assert_equal [[1, "2.2"], [3, "4.4"]], result.cast_values
@@ -101,7 +101,7 @@ def result
101101
test "cast_values returns single dimensional array if single column" do
102102
values = [["1.1"], ["3.3"]]
103103
columns = ["col1"]
104-
types = { "col1" => Type::Integer.new }
104+
types = [Type::Integer.new]
105105
result = Result.new(columns, values, types)
106106

107107
assert_equal [1, 3], result.cast_values
@@ -110,7 +110,7 @@ def result
110110
test "cast_values can receive types to use instead" do
111111
values = [["1.1", "2.2"], ["3.3", "4.4"]]
112112
columns = ["col1", "col2"]
113-
types = { "col1" => Type::Integer.new, "col2" => Type::Float.new }
113+
types = [Type::Integer.new, Type::Float.new]
114114
result = Result.new(columns, values, types)
115115

116116
assert_equal [[1.1, 2.2], [3.3, 4.4]], result.cast_values("col1" => Type::Float.new)

0 commit comments

Comments
 (0)