Skip to content

Commit 5179851

Browse files
committed
Make the schema cache forward compatible with the changes in Column
Since rails#54333 The Column class added a new attribute `cast_type` that might not be in the schema cache dump made by a Rails 8.0 application. In that case, we should fallback to the previous behavior and fetch the type from the connection. Of course this is not ideal since it will still require a connection to get the cast type, but for schema cache dumps made by Rails 8.1 we will not need to connect to the database anymore. This code can be removed after we release Rails 8.1.
1 parent 31f8a6b commit 5179851

File tree

13 files changed

+211
-32
lines changed

13 files changed

+211
-32
lines changed

activerecord/lib/active_record/attributes.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,11 @@ def define_attribute(
241241

242242
def _default_attributes # :nodoc:
243243
@default_attributes ||= begin
244-
attributes_hash = columns_hash.transform_values do |column|
245-
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(column))
244+
# TODO: Remove the need for a connection after we release 8.1.
245+
attributes_hash = with_connection do |connection|
246+
columns_hash.transform_values do |column|
247+
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
248+
end
246249
end
247250

248251
attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
@@ -297,7 +300,8 @@ def resolve_type_name(name, **options)
297300
Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
298301
end
299302

300-
def type_for_column(column)
303+
def type_for_column(connection, column)
304+
# TODO: Remove the need for a connection after we release 8.1.
301305
hook_attribute_type(column.name, super)
302306
end
303307
end

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,8 @@ def build_fixture_sql(fixtures, table_name)
624624

625625
columns.map do |name, column|
626626
if fixture.key?(name)
627-
with_yaml_fallback(column.cast_type.serialize(fixture[name]))
627+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
628+
with_yaml_fallback(column.fetch_cast_type(self).serialize(fixture[name]))
628629
else
629630
default_insert_value(column)
630631
end

activerecord/lib/active_record/connection_adapters/abstract/quoting.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ def quote_default_expression(value, column) # :nodoc:
146146
if value.is_a?(Proc)
147147
value.call
148148
else
149-
cast_type = column.respond_to?(:cast_type) ? column.cast_type : lookup_cast_type(column.sql_type)
149+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
150+
cast_type = column.respond_to?(:fetch_cast_type) ? column.fetch_cast_type(self) : lookup_cast_type(column.sql_type)
150151
value = cast_type.serialize(value)
151152
quote(value)
152153
end
@@ -209,6 +210,11 @@ def sanitize_as_sql_comment(value) # :nodoc:
209210
comment
210211
end
211212

213+
def lookup_cast_type(sql_type) # :nodoc:
214+
# TODO: Make this method private after we release 8.1.
215+
type_map.lookup(sql_type)
216+
end
217+
212218
private
213219
def type_casted_binds(binds)
214220
binds&.map do |value|
@@ -219,12 +225,6 @@ def type_casted_binds(binds)
219225
end
220226
end
221227
end
222-
223-
# In some adapters this executes queries; it should not be used in any
224-
# hot paths. Instead prefer using `column.cast_type`.
225-
def lookup_cast_type(sql_type)
226-
type_map.lookup(sql_type)
227-
end
228228
end
229229
end
230230
end

activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ def schema_scale(column)
8585

8686
def schema_default(column)
8787
return unless column.has_default?
88-
type = column.cast_type
88+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
89+
type = column.fetch_cast_type(@connection)
8990
default = type.deserialize(column.default)
9091
if default.nil?
9192
schema_expression(column)

activerecord/lib/active_record/connection_adapters/column.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module ConnectionAdapters
77
class Column
88
include Deduplicable
99

10-
attr_reader :name, :cast_type, :default, :sql_type_metadata, :null, :default_function, :collation, :comment
10+
attr_reader :name, :default, :sql_type_metadata, :null, :default_function, :collation, :comment
1111

1212
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
1313

@@ -28,6 +28,11 @@ def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, d
2828
@comment = comment
2929
end
3030

31+
def fetch_cast_type(connection) # :nodoc:
32+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
33+
@cast_type || connection.lookup_cast_type(sql_type)
34+
end
35+
3136
def has_default?
3237
!default.nil? || default_function
3338
end
@@ -105,6 +110,9 @@ def virtual?
105110
false
106111
end
107112

113+
protected
114+
attr_reader :cast_type
115+
108116
private
109117
def deduplicated
110118
@name = -name

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ def quote_default_expression(value, column) # :nodoc:
160160
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
161161
value # Does not quote function default values for UUID columns
162162
elsif column.respond_to?(:array?)
163-
quote(column.cast_type.serialize(value))
163+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
164+
quote(column.fetch_cast_type(self).serialize(value))
164165
else
165166
super
166167
end
@@ -186,11 +187,12 @@ def type_cast(value) # :nodoc:
186187
end
187188
end
188189

189-
private
190-
def lookup_cast_type(sql_type)
191-
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
192-
end
190+
# TODO: Make this method private after we release 8.1.
191+
def lookup_cast_type(sql_type) # :nodoc:
192+
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
193+
end
193194

195+
private
194196
def encode_array(array_data)
195197
encoder = array_data.encoder
196198
values = type_cast_array(array_data.values)

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,8 @@ def copy_table(from, to, options = {})
654654
column_options[:stored] = column.virtual_stored?
655655
column_options[:type] = column.type
656656
elsif column.has_default?
657-
default = column.cast_type.deserialize(column.default)
657+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
658+
default = column.fetch_cast_type(self).deserialize(column.default)
658659
default = -> { column.default_function } if default.nil?
659660

660661
unless column.auto_increment?

activerecord/lib/active_record/model_schema.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,8 +619,9 @@ def compute_table_name
619619
end
620620
end
621621

622-
def type_for_column(column)
623-
type = column.cast_type
622+
def type_for_column(connection, column)
623+
# TODO: Remove the need for a connection after we release 8.1.
624+
type = column.fetch_cast_type(connection)
624625

625626
if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
626627
type = type.to_immutable_string

activerecord/lib/active_record/type_caster/connection.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def type_for_attribute(attr_name)
1919
if schema_cache.data_source_exists?(table_name)
2020
column = schema_cache.columns_hash(table_name)[attr_name.to_s]
2121
if column
22-
type = column.cast_type
22+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
23+
type = column.fetch_cast_type(@klass.lease_connection)
2324
end
2425
end
2526

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
--- !ruby/object:ActiveRecord::ConnectionAdapters::SchemaCache
2+
columns:
3+
colleges:
4+
- &1 !ruby/object:ActiveRecord::ConnectionAdapters::Column
5+
auto_increment: true
6+
name: id
7+
sql_type_metadata: &4 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
8+
sql_type: INTEGER
9+
type: :integer
10+
limit:
11+
precision:
12+
scale:
13+
'null': false
14+
default:
15+
default_function:
16+
collation:
17+
comment:
18+
- &2 !ruby/object:ActiveRecord::ConnectionAdapters::Column
19+
auto_increment:
20+
name: name
21+
sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
22+
sql_type: varchar
23+
type: :string
24+
limit:
25+
precision:
26+
scale:
27+
'null': false
28+
default:
29+
default_function:
30+
collation:
31+
comment:
32+
courses:
33+
- *1
34+
- *2
35+
- !ruby/object:ActiveRecord::ConnectionAdapters::Column
36+
auto_increment:
37+
name: college_id
38+
sql_type_metadata: *4
39+
'null': true
40+
default:
41+
default_function:
42+
collation:
43+
comment:
44+
courses_professors:
45+
- !ruby/object:ActiveRecord::ConnectionAdapters::Column
46+
auto_increment:
47+
name: course_id
48+
sql_type_metadata: *4
49+
'null': true
50+
default:
51+
default_function:
52+
collation:
53+
comment:
54+
- !ruby/object:ActiveRecord::ConnectionAdapters::Column
55+
auto_increment:
56+
name: professor_id
57+
sql_type_metadata: *4
58+
'null': true
59+
default:
60+
default_function:
61+
collation:
62+
comment:
63+
dogs:
64+
- *1
65+
professors:
66+
- *1
67+
- *2
68+
primary_keys:
69+
colleges: id
70+
courses: id
71+
courses_professors:
72+
dogs: id
73+
professors: id
74+
data_sources:
75+
colleges: true
76+
courses: true
77+
courses_professors: true
78+
dogs: true
79+
professors: true
80+
indexes:
81+
colleges: []
82+
courses:
83+
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
84+
table: courses
85+
name: index_courses_on_college_id
86+
unique: false
87+
columns:
88+
- college_id
89+
lengths: {}
90+
orders: {}
91+
opclasses: {}
92+
where:
93+
type:
94+
using:
95+
include:
96+
nulls_not_distinct:
97+
comment:
98+
valid: true
99+
courses_professors:
100+
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
101+
table: courses_professors
102+
name: index_courses_professors_on_professor_id
103+
unique: false
104+
columns:
105+
- professor_id
106+
lengths: {}
107+
orders: {}
108+
opclasses: {}
109+
where:
110+
type:
111+
using:
112+
include:
113+
nulls_not_distinct:
114+
comment:
115+
valid: true
116+
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
117+
table: courses_professors
118+
name: index_courses_professors_on_course_id
119+
unique: false
120+
columns:
121+
- course_id
122+
lengths: {}
123+
orders: {}
124+
opclasses: {}
125+
where:
126+
type:
127+
using:
128+
include:
129+
nulls_not_distinct:
130+
comment:
131+
valid: true
132+
dogs: []
133+
professors: []
134+
version: 0

0 commit comments

Comments
 (0)