activerecord 8.0.2 → 8.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +459 -413
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +9 -1
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +3 -3
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attributes.rb +38 -24
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +13 -10
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +56 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +2 -2
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +27 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +31 -21
- data/lib/active_record/model_schema.rb +10 -7
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +7 -7
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +34 -5
- data/lib/active_record/railties/databases.rake +23 -19
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +26 -12
- data/lib/active_record/relation/calculations.rb +35 -25
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +41 -24
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +43 -33
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +7 -10
- data/lib/active_record/relation.rb +37 -15
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +46 -18
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +24 -35
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +11 -3
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +34 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +68 -5
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +5 -21
- data/lib/arel.rb +3 -1
- metadata +15 -11
- data/lib/active_record/normalization.rb +0 -163
@@ -55,12 +55,15 @@ module ActiveRecord
|
|
55
55
|
# can be used to query the database repeatedly.
|
56
56
|
def cacheable_query(klass, arel) # :nodoc:
|
57
57
|
if prepared_statements
|
58
|
+
collector = collector()
|
59
|
+
collector.retryable = true
|
58
60
|
sql, binds = visitor.compile(arel.ast, collector)
|
59
|
-
query = klass.query(sql)
|
61
|
+
query = klass.query(sql, retryable: collector.retryable)
|
60
62
|
else
|
61
63
|
collector = klass.partial_query_collector
|
64
|
+
collector.retryable = true
|
62
65
|
parts, binds = visitor.compile(arel.ast, collector)
|
63
|
-
query = klass.partial_query(parts)
|
66
|
+
query = klass.partial_query(parts, retryable: collector.retryable)
|
64
67
|
end
|
65
68
|
[query, binds]
|
66
69
|
end
|
@@ -110,8 +113,8 @@ module ActiveRecord
|
|
110
113
|
query(...).map(&:first)
|
111
114
|
end
|
112
115
|
|
113
|
-
def query(
|
114
|
-
internal_exec_query(
|
116
|
+
def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
|
117
|
+
internal_exec_query(sql, name, allow_retry:, materialize_transactions:).rows
|
115
118
|
end
|
116
119
|
|
117
120
|
# Determines whether the SQL statement is a write query.
|
@@ -351,7 +354,7 @@ module ActiveRecord
|
|
351
354
|
# :args: (requires_new: nil, isolation: nil, &block)
|
352
355
|
def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
|
353
356
|
if !requires_new && current_transaction.joinable?
|
354
|
-
if isolation
|
357
|
+
if isolation && current_transaction.isolation != isolation
|
355
358
|
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
356
359
|
end
|
357
360
|
yield current_transaction.user_transaction
|
@@ -499,20 +502,6 @@ module ActiveRecord
|
|
499
502
|
"DEFAULT VALUES"
|
500
503
|
end
|
501
504
|
|
502
|
-
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
|
503
|
-
#
|
504
|
-
# The +limit+ may be anything that can evaluate to a string via #to_s. It
|
505
|
-
# should look like an integer, or an Arel SQL literal.
|
506
|
-
#
|
507
|
-
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
|
508
|
-
def sanitize_limit(limit)
|
509
|
-
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
|
510
|
-
limit
|
511
|
-
else
|
512
|
-
Integer(limit)
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
505
|
# Fixture value is quoted by Arel, however scalar values
|
517
506
|
# are not quotable. In this case we want to convert
|
518
507
|
# the column value to YAML.
|
@@ -547,13 +536,24 @@ module ActiveRecord
|
|
547
536
|
cast_result(internal_execute(...))
|
548
537
|
end
|
549
538
|
|
539
|
+
def default_insert_value(column) # :nodoc:
|
540
|
+
DEFAULT_INSERT_VALUE
|
541
|
+
end
|
542
|
+
|
550
543
|
private
|
544
|
+
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
545
|
+
private_constant :DEFAULT_INSERT_VALUE
|
546
|
+
|
551
547
|
# Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
|
552
548
|
def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
|
553
549
|
type_casted_binds = type_casted_binds(binds)
|
554
|
-
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
|
550
|
+
log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
|
555
551
|
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
556
|
-
|
552
|
+
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
553
|
+
perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
|
554
|
+
end
|
555
|
+
handle_warnings(result, sql)
|
556
|
+
result
|
557
557
|
end
|
558
558
|
end
|
559
559
|
end
|
@@ -562,6 +562,9 @@ module ActiveRecord
|
|
562
562
|
raise NotImplementedError
|
563
563
|
end
|
564
564
|
|
565
|
+
def handle_warnings(raw_result, sql)
|
566
|
+
end
|
567
|
+
|
565
568
|
# Receive a native adapter result object and returns an ActiveRecord::Result object.
|
566
569
|
def cast_result(raw_result)
|
567
570
|
raise NotImplementedError
|
@@ -597,13 +600,6 @@ module ActiveRecord
|
|
597
600
|
end
|
598
601
|
end
|
599
602
|
|
600
|
-
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
601
|
-
private_constant :DEFAULT_INSERT_VALUE
|
602
|
-
|
603
|
-
def default_insert_value(column)
|
604
|
-
DEFAULT_INSERT_VALUE
|
605
|
-
end
|
606
|
-
|
607
603
|
def build_fixture_sql(fixtures, table_name)
|
608
604
|
columns = schema_cache.columns_hash(table_name).reject { |_, column| supports_virtual_columns? && column.virtual? }
|
609
605
|
|
@@ -617,8 +613,8 @@ module ActiveRecord
|
|
617
613
|
|
618
614
|
columns.map do |name, column|
|
619
615
|
if fixture.key?(name)
|
620
|
-
|
621
|
-
with_yaml_fallback(
|
616
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
617
|
+
with_yaml_fallback(column.fetch_cast_type(self).serialize(fixture[name]))
|
622
618
|
else
|
623
619
|
default_insert_value(column)
|
624
620
|
end
|
@@ -31,6 +31,18 @@ module ActiveRecord
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
# This is the actual query cache store.
|
35
|
+
#
|
36
|
+
# It has an internal hash whose keys are either SQL strings, or arrays of
|
37
|
+
# two elements [SQL string, binds], if there are binds. The hash values
|
38
|
+
# are their corresponding ActiveRecord::Result objects.
|
39
|
+
#
|
40
|
+
# Keeping the hash size under max size is achieved with LRU eviction.
|
41
|
+
#
|
42
|
+
# The store gets passed a version object, which is shared among the query
|
43
|
+
# cache stores of a given connection pool (see ConnectionPoolConfiguration
|
44
|
+
# down below). The version value may be externally changed as a way to
|
45
|
+
# signal cache invalidation, that is why all methods have a guard for it.
|
34
46
|
class Store # :nodoc:
|
35
47
|
attr_accessor :enabled, :dirties
|
36
48
|
alias_method :enabled?, :enabled
|
@@ -94,6 +106,12 @@ module ActiveRecord
|
|
94
106
|
end
|
95
107
|
end
|
96
108
|
|
109
|
+
# Each connection pool has one of these registries. They map execution
|
110
|
+
# contexts to query cache stores.
|
111
|
+
#
|
112
|
+
# The keys of the internal map are threads or fibers (whatever
|
113
|
+
# ActiveSupport::IsolatedExecutionState.context returns), and their
|
114
|
+
# associated values are their respective query cache stores.
|
97
115
|
class QueryCacheRegistry # :nodoc:
|
98
116
|
def initialize
|
99
117
|
@mutex = Mutex.new
|
@@ -239,7 +257,7 @@ module ActiveRecord
|
|
239
257
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
240
258
|
# Such queries should not be cached.
|
241
259
|
if @query_cache&.enabled? && !(arel.respond_to?(:locked) && arel.locked)
|
242
|
-
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
|
260
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
|
243
261
|
|
244
262
|
if async
|
245
263
|
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/big_decimal/conversions"
|
4
|
-
require "active_support/multibyte/chars"
|
5
4
|
|
6
5
|
module ActiveRecord
|
7
6
|
module ConnectionAdapters # :nodoc:
|
@@ -84,7 +83,8 @@ module ActiveRecord
|
|
84
83
|
when Type::Time::Value then "'#{quoted_time(value)}'"
|
85
84
|
when Date, Time then "'#{quoted_date(value)}'"
|
86
85
|
when Class then "'#{value}'"
|
87
|
-
else
|
86
|
+
else
|
87
|
+
raise TypeError, "can't quote #{value.class.name}"
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
@@ -93,7 +93,7 @@ module ActiveRecord
|
|
93
93
|
# to a String.
|
94
94
|
def type_cast(value)
|
95
95
|
case value
|
96
|
-
when Symbol,
|
96
|
+
when Symbol, Type::Binary::Data, ActiveSupport::Multibyte::Chars
|
97
97
|
value.to_s
|
98
98
|
when true then unquoted_true
|
99
99
|
when false then unquoted_false
|
@@ -102,7 +102,8 @@ module ActiveRecord
|
|
102
102
|
when nil, Numeric, String then value
|
103
103
|
when Type::Time::Value then quoted_time(value)
|
104
104
|
when Date, Time then quoted_date(value)
|
105
|
-
else
|
105
|
+
else
|
106
|
+
raise TypeError, "can't cast #{value.class.name}"
|
106
107
|
end
|
107
108
|
end
|
108
109
|
|
@@ -113,19 +114,6 @@ module ActiveRecord
|
|
113
114
|
value
|
114
115
|
end
|
115
116
|
|
116
|
-
# If you are having to call this function, you are likely doing something
|
117
|
-
# wrong. The column does not have sufficient type information if the user
|
118
|
-
# provided a custom type on the class level either explicitly (via
|
119
|
-
# Attributes::ClassMethods#attribute) or implicitly (via
|
120
|
-
# AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+).
|
121
|
-
# In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to
|
122
|
-
# represent the type doesn't sufficiently reflect the differences
|
123
|
-
# (varchar vs binary) for example. The type used to get this primitive
|
124
|
-
# should have been provided before reaching the connection adapter.
|
125
|
-
def lookup_cast_type_from_column(column) # :nodoc:
|
126
|
-
lookup_cast_type(column.sql_type)
|
127
|
-
end
|
128
|
-
|
129
117
|
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
130
118
|
# characters.
|
131
119
|
def quote_string(s)
|
@@ -158,7 +146,9 @@ module ActiveRecord
|
|
158
146
|
if value.is_a?(Proc)
|
159
147
|
value.call
|
160
148
|
else
|
161
|
-
|
149
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
150
|
+
cast_type = column.fetch_cast_type(self)
|
151
|
+
value = cast_type.serialize(value)
|
162
152
|
quote(value)
|
163
153
|
end
|
164
154
|
end
|
@@ -208,10 +198,10 @@ module ActiveRecord
|
|
208
198
|
end
|
209
199
|
|
210
200
|
def sanitize_as_sql_comment(value) # :nodoc:
|
211
|
-
# Sanitize a string to appear within
|
201
|
+
# Sanitize a string to appear within an SQL comment
|
212
202
|
# For compatibility, this also surrounding "/*+", "/*", and "*/"
|
213
203
|
# charcacters, possibly with single surrounding space.
|
214
|
-
# Then follows that by replacing any internal "*/" or "
|
204
|
+
# Then follows that by replacing any internal "*/" or "/*" with
|
215
205
|
# "* /" or "/ *"
|
216
206
|
comment = value.to_s.dup
|
217
207
|
comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "")
|
@@ -220,6 +210,11 @@ module ActiveRecord
|
|
220
210
|
comment
|
221
211
|
end
|
222
212
|
|
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
|
+
|
223
218
|
private
|
224
219
|
def type_casted_binds(binds)
|
225
220
|
binds&.map do |value|
|
@@ -230,10 +225,6 @@ module ActiveRecord
|
|
230
225
|
end
|
231
226
|
end
|
232
227
|
end
|
233
|
-
|
234
|
-
def lookup_cast_type(sql_type)
|
235
|
-
type_map.lookup(sql_type)
|
236
|
-
end
|
237
228
|
end
|
238
229
|
end
|
239
230
|
end
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
:options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
|
18
18
|
:quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
|
19
19
|
:supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
|
20
|
-
:supports_nulls_not_distinct?,
|
20
|
+
:supports_nulls_not_distinct?, :lookup_cast_type,
|
21
21
|
to: :@conn, private: true
|
22
22
|
|
23
23
|
private
|
@@ -148,7 +148,7 @@ module ActiveRecord
|
|
148
148
|
end
|
149
149
|
|
150
150
|
def add_column_options!(sql, options)
|
151
|
-
sql << " DEFAULT #{
|
151
|
+
sql << " DEFAULT #{quote_default_expression_for_column_definition(options[:default], options[:column])}" if options_include_default?(options)
|
152
152
|
# must explicitly check for :null to allow change_column to work on migrations
|
153
153
|
if options[:null] == false
|
154
154
|
sql << " NOT NULL"
|
@@ -162,6 +162,11 @@ module ActiveRecord
|
|
162
162
|
sql
|
163
163
|
end
|
164
164
|
|
165
|
+
def quote_default_expression_for_column_definition(default, column_definition)
|
166
|
+
column_definition.cast_type = lookup_cast_type(column_definition.sql_type)
|
167
|
+
quote_default_expression(default, column_definition)
|
168
|
+
end
|
169
|
+
|
165
170
|
def to_sql(sql)
|
166
171
|
sql = sql.to_sql if sql.respond_to?(:to_sql)
|
167
172
|
sql
|
@@ -75,7 +75,7 @@ module ActiveRecord
|
|
75
75
|
# are typically created by methods in TableDefinition, and added to the
|
76
76
|
# +columns+ attribute of said TableDefinition object, in order to be used
|
77
77
|
# for generating a number of table creation or table changing SQL statements.
|
78
|
-
ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
|
78
|
+
ColumnDefinition = Struct.new(:name, :type, :options, :sql_type, :cast_type) do # :nodoc:
|
79
79
|
self::OPTION_NAMES = [
|
80
80
|
:limit,
|
81
81
|
:precision,
|
@@ -108,6 +108,10 @@ module ActiveRecord
|
|
108
108
|
def aliased_types(name, fallback)
|
109
109
|
"timestamp" == name ? :datetime : fallback
|
110
110
|
end
|
111
|
+
|
112
|
+
def fetch_cast_type(connection)
|
113
|
+
cast_type
|
114
|
+
end
|
111
115
|
end
|
112
116
|
|
113
117
|
AddColumnDefinition = Struct.new(:column) # :nodoc:
|
@@ -303,44 +307,32 @@ module ActiveRecord
|
|
303
307
|
module ColumnMethods
|
304
308
|
extend ActiveSupport::Concern
|
305
309
|
|
306
|
-
# Appends a primary key definition to the table definition.
|
307
|
-
# Can be called multiple times, but this is probably not a good idea.
|
308
|
-
def primary_key(name, type = :primary_key, **options)
|
309
|
-
column(name, type, **options.merge(primary_key: true))
|
310
|
-
end
|
311
|
-
|
312
|
-
##
|
313
|
-
# :method: column
|
314
|
-
# :call-seq: column(name, type, **options)
|
315
|
-
#
|
316
|
-
# Appends a column or columns of a specified type.
|
317
|
-
#
|
318
|
-
# t.string(:goat)
|
319
|
-
# t.string(:goat, :sheep)
|
320
|
-
#
|
321
|
-
# See TableDefinition#column
|
322
|
-
|
323
|
-
included do
|
324
|
-
define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
|
325
|
-
:float, :integer, :json, :string, :text, :time, :timestamp, :virtual
|
326
|
-
|
327
|
-
alias :blob :binary
|
328
|
-
alias :numeric :decimal
|
329
|
-
end
|
330
|
-
|
331
310
|
class_methods do
|
332
|
-
|
333
|
-
column_types
|
334
|
-
|
311
|
+
private
|
312
|
+
def define_column_methods(*column_types) # :nodoc:
|
313
|
+
column_types.each do |column_type|
|
314
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
335
315
|
def #{column_type}(*names, **options)
|
336
316
|
raise ArgumentError, "Missing column name(s) for #{column_type}" if names.empty?
|
337
317
|
names.each { |name| column(name, :#{column_type}, **options) }
|
338
318
|
end
|
339
|
-
|
319
|
+
RUBY
|
320
|
+
end
|
340
321
|
end
|
341
|
-
end
|
342
|
-
private :define_column_methods
|
343
322
|
end
|
323
|
+
extend ClassMethods
|
324
|
+
|
325
|
+
# Appends a primary key definition to the table definition.
|
326
|
+
# Can be called multiple times, but this is probably not a good idea.
|
327
|
+
def primary_key(name, type = :primary_key, **options)
|
328
|
+
column(name, type, **options, primary_key: true)
|
329
|
+
end
|
330
|
+
|
331
|
+
define_column_methods :bigint, :binary, :boolean, :date, :datetime, :decimal,
|
332
|
+
:float, :integer, :json, :string, :text, :time, :timestamp, :virtual
|
333
|
+
|
334
|
+
alias :blob :binary
|
335
|
+
alias :numeric :decimal
|
344
336
|
end
|
345
337
|
|
346
338
|
# = Active Record Connection Adapters \Table \Definition
|
@@ -351,7 +343,7 @@ module ActiveRecord
|
|
351
343
|
# Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
|
352
344
|
# is actually of this type:
|
353
345
|
#
|
354
|
-
# class SomeMigration < ActiveRecord::Migration[8.
|
346
|
+
# class SomeMigration < ActiveRecord::Migration[8.1]
|
355
347
|
# def up
|
356
348
|
# create_table :foo do |t|
|
357
349
|
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
|
@@ -765,7 +757,7 @@ module ActiveRecord
|
|
765
757
|
# end
|
766
758
|
#
|
767
759
|
# See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
|
768
|
-
def index_exists?(column_name, **options)
|
760
|
+
def index_exists?(column_name = nil, **options)
|
769
761
|
@base.index_exists?(name, column_name, **options)
|
770
762
|
end
|
771
763
|
|
@@ -85,7 +85,8 @@ module ActiveRecord
|
|
85
85
|
|
86
86
|
def schema_default(column)
|
87
87
|
return unless column.has_default?
|
88
|
-
|
88
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
89
|
+
type = column.fetch_cast_type(@connection)
|
89
90
|
default = type.deserialize(column.default)
|
90
91
|
if default.nil?
|
91
92
|
schema_expression(column)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/string/access"
|
4
|
+
require "active_support/core_ext/string/filters"
|
4
5
|
require "openssl"
|
5
6
|
|
6
7
|
module ActiveRecord
|
@@ -99,7 +100,7 @@ module ActiveRecord
|
|
99
100
|
# # Check a valid index exists (PostgreSQL only)
|
100
101
|
# index_exists?(:suppliers, :company_id, valid: true)
|
101
102
|
#
|
102
|
-
def index_exists?(table_name, column_name, **options)
|
103
|
+
def index_exists?(table_name, column_name = nil, **options)
|
103
104
|
indexes(table_name).any? { |i| i.defined_for?(column_name, **options) }
|
104
105
|
end
|
105
106
|
|
@@ -186,6 +187,9 @@ module ActiveRecord
|
|
186
187
|
# Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
|
187
188
|
#
|
188
189
|
# A Symbol can be used to specify the type of the generated primary key column.
|
190
|
+
#
|
191
|
+
# A Hash can be used to specify the generated primary key column creation options.
|
192
|
+
# See {add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column] for available options.
|
189
193
|
# [<tt>:primary_key</tt>]
|
190
194
|
# The name of the primary key, if one is to be added automatically.
|
191
195
|
# Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
|
@@ -354,11 +358,13 @@ module ActiveRecord
|
|
354
358
|
# # Creates a table called 'music_artists_records' with no id.
|
355
359
|
# create_join_table('music_artists', 'music_records')
|
356
360
|
#
|
361
|
+
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference]
|
362
|
+
# for details of the options you can use in +column_options+. +column_options+
|
363
|
+
# will be applied to both columns.
|
364
|
+
#
|
357
365
|
# You can pass an +options+ hash which can include the following keys:
|
358
366
|
# [<tt>:table_name</tt>]
|
359
367
|
# Sets the table name, overriding the default.
|
360
|
-
# [<tt>:column_options</tt>]
|
361
|
-
# Any extra options you want appended to the columns definition.
|
362
368
|
# [<tt>:options</tt>]
|
363
369
|
# Any extra options you want appended to the table definition.
|
364
370
|
# [<tt>:temporary</tt>]
|
@@ -375,6 +381,19 @@ module ActiveRecord
|
|
375
381
|
# t.index :category_id
|
376
382
|
# end
|
377
383
|
#
|
384
|
+
# ====== Add foreign keys with delete cascade
|
385
|
+
#
|
386
|
+
# create_join_table(:assemblies, :parts, column_options: { foreign_key: { on_delete: :cascade } })
|
387
|
+
#
|
388
|
+
# generates:
|
389
|
+
#
|
390
|
+
# CREATE TABLE assemblies_parts (
|
391
|
+
# assembly_id bigint NOT NULL,
|
392
|
+
# part_id bigint NOT NULL,
|
393
|
+
# CONSTRAINT fk_rails_0d8a572d89 FOREIGN KEY ("assembly_id") REFERENCES "assemblies" ("id") ON DELETE CASCADE,
|
394
|
+
# CONSTRAINT fk_rails_ec7b48402b FOREIGN KEY ("part_id") REFERENCES "parts" ("id") ON DELETE CASCADE
|
395
|
+
# )
|
396
|
+
#
|
378
397
|
# ====== Add a backend specific option to the generated SQL (MySQL)
|
379
398
|
#
|
380
399
|
# create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
|
@@ -548,7 +567,7 @@ module ActiveRecord
|
|
548
567
|
#
|
549
568
|
# See {ActiveRecord::ConnectionAdapters::TableDefinition.column}[rdoc-ref:ActiveRecord::ConnectionAdapters::TableDefinition#column].
|
550
569
|
#
|
551
|
-
# The +type+ parameter is normally one of the
|
570
|
+
# The +type+ parameter is normally one of the migration's native types,
|
552
571
|
# which is one of the following:
|
553
572
|
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
|
554
573
|
# <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
|
@@ -912,6 +931,19 @@ module ActiveRecord
|
|
912
931
|
# Concurrently adding an index is not supported in a transaction.
|
913
932
|
#
|
914
933
|
# For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
|
934
|
+
#
|
935
|
+
# ====== Creating an index that is not used by queries
|
936
|
+
#
|
937
|
+
# add_index(:developers, :name, enabled: false)
|
938
|
+
#
|
939
|
+
# generates:
|
940
|
+
#
|
941
|
+
# CREATE INDEX index_developers_on_name ON developers (name) INVISIBLE -- MySQL
|
942
|
+
#
|
943
|
+
# CREATE INDEX index_developers_on_name ON developers (name) IGNORED -- MariaDB
|
944
|
+
#
|
945
|
+
# Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
|
946
|
+
#
|
915
947
|
def add_index(table_name, column_name, **options)
|
916
948
|
create_index = build_create_index_definition(table_name, column_name, **options)
|
917
949
|
execute schema_creation.accept(create_index)
|
@@ -1172,9 +1204,10 @@ module ActiveRecord
|
|
1172
1204
|
# +:deferred+ or +:immediate+ to specify the default behavior. Defaults to +false+.
|
1173
1205
|
def add_foreign_key(from_table, to_table, **options)
|
1174
1206
|
return unless use_foreign_keys?
|
1175
|
-
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
1176
1207
|
|
1177
1208
|
options = foreign_key_options(from_table, to_table, options)
|
1209
|
+
return if options[:if_not_exists] == true && foreign_key_exists?(from_table, to_table, **options.slice(:column, :primary_key))
|
1210
|
+
|
1178
1211
|
at = create_alter_table from_table
|
1179
1212
|
at.add_foreign_key to_table, options
|
1180
1213
|
|
@@ -1213,7 +1246,7 @@ module ActiveRecord
|
|
1213
1246
|
# The name of the table that contains the referenced primary key.
|
1214
1247
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
1215
1248
|
return unless use_foreign_keys?
|
1216
|
-
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
|
1249
|
+
return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
1217
1250
|
|
1218
1251
|
fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
|
1219
1252
|
|
@@ -1352,7 +1385,7 @@ module ActiveRecord
|
|
1352
1385
|
execute schema_creation.accept(at)
|
1353
1386
|
end
|
1354
1387
|
|
1355
|
-
def
|
1388
|
+
def dump_schema_versions # :nodoc:
|
1356
1389
|
versions = pool.schema_migration.versions
|
1357
1390
|
insert_versions_sql(versions) if versions.any?
|
1358
1391
|
end
|
@@ -1474,7 +1507,7 @@ module ActiveRecord
|
|
1474
1507
|
end
|
1475
1508
|
|
1476
1509
|
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
|
1477
|
-
options.assert_valid_keys(
|
1510
|
+
options.assert_valid_keys(valid_index_options)
|
1478
1511
|
|
1479
1512
|
column_names = index_column_names(column_name)
|
1480
1513
|
|
@@ -1483,7 +1516,7 @@ module ActiveRecord
|
|
1483
1516
|
|
1484
1517
|
validate_index_length!(table_name, index_name, internal)
|
1485
1518
|
|
1486
|
-
index =
|
1519
|
+
index = create_index_definition(
|
1487
1520
|
table_name, index_name,
|
1488
1521
|
options[:unique],
|
1489
1522
|
column_names,
|
@@ -1538,6 +1571,20 @@ module ActiveRecord
|
|
1538
1571
|
raise NotImplementedError, "#{self.class} does not support changing column comments"
|
1539
1572
|
end
|
1540
1573
|
|
1574
|
+
# Enables an index to be used by queries.
|
1575
|
+
#
|
1576
|
+
# enable_index(:users, :email)
|
1577
|
+
def enable_index(table_name, index_name)
|
1578
|
+
raise NotImplementedError, "#{self.class} does not support enabling indexes"
|
1579
|
+
end
|
1580
|
+
|
1581
|
+
# Prevents an index from being used by queries.
|
1582
|
+
#
|
1583
|
+
# disable_index(:users, :email)
|
1584
|
+
def disable_index(table_name, index_name)
|
1585
|
+
raise NotImplementedError, "#{self.class} does not support disabling indexes"
|
1586
|
+
end
|
1587
|
+
|
1541
1588
|
def create_schema_dumper(options) # :nodoc:
|
1542
1589
|
SchemaDumper.create(self, options)
|
1543
1590
|
end
|
@@ -1604,7 +1651,7 @@ module ActiveRecord
|
|
1604
1651
|
name = "idx_on_#{Array(column) * '_'}"
|
1605
1652
|
|
1606
1653
|
short_limit = max_index_name_size - hashed_identifier.bytesize
|
1607
|
-
short_name = name.
|
1654
|
+
short_name = name.truncate_bytes(short_limit, omission: nil)
|
1608
1655
|
|
1609
1656
|
"#{short_name}#{hashed_identifier}"
|
1610
1657
|
end
|
@@ -1626,6 +1673,10 @@ module ActiveRecord
|
|
1626
1673
|
end
|
1627
1674
|
end
|
1628
1675
|
|
1676
|
+
def valid_index_options
|
1677
|
+
[:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm, :include, :nulls_not_distinct]
|
1678
|
+
end
|
1679
|
+
|
1629
1680
|
def options_for_index_columns(options)
|
1630
1681
|
if options.is_a?(Hash)
|
1631
1682
|
options.symbolize_keys
|
@@ -1702,6 +1753,10 @@ module ActiveRecord
|
|
1702
1753
|
TableDefinition.new(self, name, **options)
|
1703
1754
|
end
|
1704
1755
|
|
1756
|
+
def create_index_definition(table_name, name, unique, columns, **options)
|
1757
|
+
IndexDefinition.new(table_name, name, unique, columns, **options)
|
1758
|
+
end
|
1759
|
+
|
1705
1760
|
def create_alter_table(name)
|
1706
1761
|
AlterTable.new create_table_definition(name)
|
1707
1762
|
end
|
@@ -1764,7 +1819,20 @@ module ActiveRecord
|
|
1764
1819
|
|
1765
1820
|
def foreign_key_for(from_table, **options)
|
1766
1821
|
return unless use_foreign_keys?
|
1767
|
-
|
1822
|
+
|
1823
|
+
keys = foreign_keys(from_table)
|
1824
|
+
|
1825
|
+
if options[:_skip_column_match]
|
1826
|
+
return keys.find { |fk| fk.defined_for?(**options) }
|
1827
|
+
end
|
1828
|
+
|
1829
|
+
if options[:column].nil?
|
1830
|
+
default_column = foreign_key_column_for(options[:to_table], "id")
|
1831
|
+
matches = keys.select { |fk| fk.column == default_column }
|
1832
|
+
keys = matches if matches.any?
|
1833
|
+
end
|
1834
|
+
|
1835
|
+
keys.find { |fk| fk.defined_for?(**options) }
|
1768
1836
|
end
|
1769
1837
|
|
1770
1838
|
def foreign_key_for!(from_table, to_table: nil, **options)
|
@@ -1807,13 +1875,19 @@ module ActiveRecord
|
|
1807
1875
|
|
1808
1876
|
def validate_index_length!(table_name, new_name, internal = false)
|
1809
1877
|
if new_name.length > index_name_length
|
1810
|
-
raise ArgumentError,
|
1878
|
+
raise ArgumentError, <<~MSG.squish
|
1879
|
+
Index name '#{new_name}' on table '#{table_name}' is too long (#{new_name.length} characters); the limit
|
1880
|
+
is #{index_name_length} characters
|
1881
|
+
MSG
|
1811
1882
|
end
|
1812
1883
|
end
|
1813
1884
|
|
1814
1885
|
def validate_table_length!(table_name)
|
1815
1886
|
if table_name.length > table_name_length
|
1816
|
-
raise ArgumentError,
|
1887
|
+
raise ArgumentError, <<~MSG.squish
|
1888
|
+
Table name '#{table_name}' is too long (#{table_name.length} characters); the limit is
|
1889
|
+
#{table_name_length} characters
|
1890
|
+
MSG
|
1817
1891
|
end
|
1818
1892
|
end
|
1819
1893
|
|
@@ -1875,16 +1949,8 @@ module ActiveRecord
|
|
1875
1949
|
end
|
1876
1950
|
|
1877
1951
|
def insert_versions_sql(versions)
|
1878
|
-
|
1879
|
-
|
1880
|
-
if versions.is_a?(Array)
|
1881
|
-
sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
|
1882
|
-
sql << versions.reverse.map { |v| "(#{quote(v)})" }.join(",\n")
|
1883
|
-
sql << ";"
|
1884
|
-
sql
|
1885
|
-
else
|
1886
|
-
"INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
|
1887
|
-
end
|
1952
|
+
versions_formatter = ActiveRecord.schema_versions_formatter.new(self)
|
1953
|
+
versions_formatter.format(versions)
|
1888
1954
|
end
|
1889
1955
|
|
1890
1956
|
def data_source_sql(name = nil, type: nil)
|