activerecord 7.2.2.1 → 8.1.2
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 +564 -753
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- 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 +10 -8
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
- 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/quoting.rb +7 -9
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +37 -10
- data/lib/active_record/core.rb +61 -25
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -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 +19 -19
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +9 -9
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +49 -28
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +46 -42
- data/lib/active_record/errors.rb +36 -12
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- 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 -4
- data/lib/active_record/future_result.rb +13 -9
- 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 +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +44 -11
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +50 -43
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +104 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +37 -32
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -37
- 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 +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +80 -63
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +54 -37
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +156 -95
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +122 -80
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +47 -22
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +39 -16
- 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/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- 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 +2 -2
- data/lib/arel/nodes/sql_literal.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/table.rb +3 -7
- 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 +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +17 -17
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -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
|
|
@@ -102,16 +105,16 @@ module ActiveRecord
|
|
|
102
105
|
select_all(arel, name, binds, async: async).then(&:rows)
|
|
103
106
|
end
|
|
104
107
|
|
|
105
|
-
def query_value(
|
|
106
|
-
single_value_from_rows(query(
|
|
108
|
+
def query_value(...) # :nodoc:
|
|
109
|
+
single_value_from_rows(query(...))
|
|
107
110
|
end
|
|
108
111
|
|
|
109
|
-
def query_values(
|
|
110
|
-
query(
|
|
112
|
+
def query_values(...) # :nodoc:
|
|
113
|
+
query(...).map(&:first)
|
|
111
114
|
end
|
|
112
115
|
|
|
113
|
-
def query(sql, name = nil) # :nodoc:
|
|
114
|
-
internal_exec_query(sql, name).rows
|
|
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.
|
|
@@ -163,14 +166,14 @@ module ActiveRecord
|
|
|
163
166
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
|
164
167
|
# the executed +sql+ statement.
|
|
165
168
|
def exec_delete(sql, name = nil, binds = [])
|
|
166
|
-
|
|
169
|
+
affected_rows(internal_execute(sql, name, binds))
|
|
167
170
|
end
|
|
168
171
|
|
|
169
172
|
# Executes update +sql+ statement in the context of this connection using
|
|
170
173
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
|
171
174
|
# the executed +sql+ statement.
|
|
172
175
|
def exec_update(sql, name = nil, binds = [])
|
|
173
|
-
|
|
176
|
+
affected_rows(internal_execute(sql, name, binds))
|
|
174
177
|
end
|
|
175
178
|
|
|
176
179
|
def exec_insert_all(sql, name) # :nodoc:
|
|
@@ -224,11 +227,9 @@ module ActiveRecord
|
|
|
224
227
|
|
|
225
228
|
return if table_names.empty?
|
|
226
229
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
execute_batch(statements, "Truncate Tables")
|
|
231
|
-
end
|
|
230
|
+
disable_referential_integrity do
|
|
231
|
+
statements = build_truncate_statements(table_names)
|
|
232
|
+
execute_batch(statements, "Truncate Tables")
|
|
232
233
|
end
|
|
233
234
|
end
|
|
234
235
|
|
|
@@ -352,13 +353,29 @@ module ActiveRecord
|
|
|
352
353
|
# isolation level.
|
|
353
354
|
# :args: (requires_new: nil, isolation: nil, &block)
|
|
354
355
|
def transaction(requires_new: nil, isolation: nil, joinable: true, &block)
|
|
356
|
+
# If we're running inside the single, non-joinable transaction that
|
|
357
|
+
# ActiveRecord::TestFixtures starts around each example (depth == 1),
|
|
358
|
+
# an `isolation:` hint must be validated then ignored so that the
|
|
359
|
+
# adapter isn't asked to change the isolation level mid-transaction.
|
|
360
|
+
if isolation && !requires_new && open_transactions == 1 && !current_transaction.joinable?
|
|
361
|
+
iso = isolation.to_sym
|
|
362
|
+
|
|
363
|
+
unless transaction_isolation_levels.include?(iso)
|
|
364
|
+
raise ActiveRecord::TransactionIsolationError,
|
|
365
|
+
"invalid transaction isolation level: #{iso.inspect}"
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
current_transaction.isolation = iso
|
|
369
|
+
isolation = nil
|
|
370
|
+
end
|
|
371
|
+
|
|
355
372
|
if !requires_new && current_transaction.joinable?
|
|
356
|
-
if isolation
|
|
373
|
+
if isolation && current_transaction.isolation != isolation
|
|
357
374
|
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
|
358
375
|
end
|
|
359
376
|
yield current_transaction.user_transaction
|
|
360
377
|
else
|
|
361
|
-
|
|
378
|
+
within_new_transaction(isolation: isolation, joinable: joinable, &block)
|
|
362
379
|
end
|
|
363
380
|
rescue ActiveRecord::Rollback
|
|
364
381
|
# rollbacks are silently swallowed
|
|
@@ -371,10 +388,10 @@ module ActiveRecord
|
|
|
371
388
|
:disable_lazy_transactions!, :enable_lazy_transactions!, :dirty_current_transaction,
|
|
372
389
|
to: :transaction_manager
|
|
373
390
|
|
|
374
|
-
def
|
|
391
|
+
def mark_transaction_written # :nodoc:
|
|
375
392
|
transaction = current_transaction
|
|
376
393
|
if transaction.open?
|
|
377
|
-
transaction.written ||=
|
|
394
|
+
transaction.written ||= true
|
|
378
395
|
end
|
|
379
396
|
end
|
|
380
397
|
|
|
@@ -411,13 +428,24 @@ module ActiveRecord
|
|
|
411
428
|
# Begins the transaction (and turns off auto-committing).
|
|
412
429
|
def begin_db_transaction() end
|
|
413
430
|
|
|
431
|
+
def begin_deferred_transaction(isolation_level = nil) # :nodoc:
|
|
432
|
+
if isolation_level
|
|
433
|
+
begin_isolated_db_transaction(isolation_level)
|
|
434
|
+
else
|
|
435
|
+
begin_db_transaction
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
TRANSACTION_ISOLATION_LEVELS = {
|
|
440
|
+
read_uncommitted: "READ UNCOMMITTED",
|
|
441
|
+
read_committed: "READ COMMITTED",
|
|
442
|
+
repeatable_read: "REPEATABLE READ",
|
|
443
|
+
serializable: "SERIALIZABLE"
|
|
444
|
+
}.freeze
|
|
445
|
+
private_constant :TRANSACTION_ISOLATION_LEVELS
|
|
446
|
+
|
|
414
447
|
def transaction_isolation_levels
|
|
415
|
-
|
|
416
|
-
read_uncommitted: "READ UNCOMMITTED",
|
|
417
|
-
read_committed: "READ COMMITTED",
|
|
418
|
-
repeatable_read: "REPEATABLE READ",
|
|
419
|
-
serializable: "SERIALIZABLE"
|
|
420
|
-
}
|
|
448
|
+
TRANSACTION_ISOLATION_LEVELS
|
|
421
449
|
end
|
|
422
450
|
|
|
423
451
|
# Begins the transaction with the isolation level set. Raises an error by
|
|
@@ -427,6 +455,15 @@ module ActiveRecord
|
|
|
427
455
|
raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
|
|
428
456
|
end
|
|
429
457
|
|
|
458
|
+
# Hook point called after an isolated DB transaction is committed
|
|
459
|
+
# or rolled back.
|
|
460
|
+
# Most adapters don't need to implement anything because the isolation
|
|
461
|
+
# level is set on a per transaction basis.
|
|
462
|
+
# But some databases like SQLite set it on a per connection level
|
|
463
|
+
# and need to explicitly reset it after commit or rollback.
|
|
464
|
+
def reset_isolation_level
|
|
465
|
+
end
|
|
466
|
+
|
|
430
467
|
# Commits the transaction (and turns on auto-committing).
|
|
431
468
|
def commit_db_transaction() end
|
|
432
469
|
|
|
@@ -473,11 +510,9 @@ module ActiveRecord
|
|
|
473
510
|
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
|
|
474
511
|
statements = table_deletes + fixture_inserts
|
|
475
512
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
execute_batch(statements, "Fixtures Load")
|
|
480
|
-
end
|
|
513
|
+
transaction(requires_new: true) do
|
|
514
|
+
disable_referential_integrity do
|
|
515
|
+
execute_batch(statements, "Fixtures Load")
|
|
481
516
|
end
|
|
482
517
|
end
|
|
483
518
|
end
|
|
@@ -486,20 +521,6 @@ module ActiveRecord
|
|
|
486
521
|
"DEFAULT VALUES"
|
|
487
522
|
end
|
|
488
523
|
|
|
489
|
-
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
|
|
490
|
-
#
|
|
491
|
-
# The +limit+ may be anything that can evaluate to a string via #to_s. It
|
|
492
|
-
# should look like an integer, or an Arel SQL literal.
|
|
493
|
-
#
|
|
494
|
-
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
|
|
495
|
-
def sanitize_limit(limit)
|
|
496
|
-
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
|
|
497
|
-
limit
|
|
498
|
-
else
|
|
499
|
-
Integer(limit)
|
|
500
|
-
end
|
|
501
|
-
end
|
|
502
|
-
|
|
503
524
|
# Fixture value is quoted by Arel, however scalar values
|
|
504
525
|
# are not quotable. In this case we want to convert
|
|
505
526
|
# the column value to YAML.
|
|
@@ -524,35 +545,78 @@ module ActiveRecord
|
|
|
524
545
|
HIGH_PRECISION_CURRENT_TIMESTAMP
|
|
525
546
|
end
|
|
526
547
|
|
|
527
|
-
|
|
528
|
-
|
|
548
|
+
# Same as raw_execute but returns an ActiveRecord::Result object.
|
|
549
|
+
def raw_exec_query(...) # :nodoc:
|
|
550
|
+
cast_result(raw_execute(...))
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Execute a query and returns an ActiveRecord::Result
|
|
554
|
+
def internal_exec_query(...) # :nodoc:
|
|
555
|
+
cast_result(internal_execute(...))
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def default_insert_value(column) # :nodoc:
|
|
559
|
+
DEFAULT_INSERT_VALUE
|
|
529
560
|
end
|
|
530
561
|
|
|
531
562
|
private
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
check_if_write_query(sql)
|
|
563
|
+
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
|
564
|
+
private_constant :DEFAULT_INSERT_VALUE
|
|
535
565
|
|
|
536
|
-
|
|
566
|
+
# Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
|
|
567
|
+
def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
|
|
568
|
+
type_casted_binds = type_casted_binds(binds)
|
|
569
|
+
log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
|
|
570
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
571
|
+
result = perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)
|
|
572
|
+
handle_warnings(result, sql)
|
|
573
|
+
result
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
537
577
|
|
|
538
|
-
|
|
578
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch:)
|
|
579
|
+
raise NotImplementedError
|
|
539
580
|
end
|
|
540
581
|
|
|
541
|
-
def
|
|
542
|
-
statements.each do |statement|
|
|
543
|
-
internal_execute(statement, name)
|
|
544
|
-
end
|
|
582
|
+
def handle_warnings(raw_result, sql)
|
|
545
583
|
end
|
|
546
584
|
|
|
547
|
-
|
|
585
|
+
# Receive a native adapter result object and returns an ActiveRecord::Result object.
|
|
586
|
+
def cast_result(raw_result)
|
|
548
587
|
raise NotImplementedError
|
|
549
588
|
end
|
|
550
589
|
|
|
551
|
-
|
|
552
|
-
|
|
590
|
+
def affected_rows(raw_result)
|
|
591
|
+
raise NotImplementedError
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def preprocess_query(sql)
|
|
595
|
+
if write_query?(sql)
|
|
596
|
+
ensure_writes_are_allowed(sql)
|
|
597
|
+
mark_transaction_written
|
|
598
|
+
end
|
|
553
599
|
|
|
554
|
-
|
|
555
|
-
|
|
600
|
+
# We call tranformers after the write checks so we don't add extra parsing work.
|
|
601
|
+
# This means we assume no transformer whille change a read for a write
|
|
602
|
+
# but it would be insane to do such a thing.
|
|
603
|
+
ActiveRecord.query_transformers.each do |transformer|
|
|
604
|
+
sql = transformer.call(sql, self)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
sql
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Same as #internal_exec_query, but yields a native adapter result
|
|
611
|
+
def internal_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, &block)
|
|
612
|
+
sql = preprocess_query(sql)
|
|
613
|
+
raw_execute(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions, &block)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
617
|
+
statements.each do |statement|
|
|
618
|
+
raw_execute(statement, name, **kwargs)
|
|
619
|
+
end
|
|
556
620
|
end
|
|
557
621
|
|
|
558
622
|
def build_fixture_sql(fixtures, table_name)
|
|
@@ -568,8 +632,8 @@ module ActiveRecord
|
|
|
568
632
|
|
|
569
633
|
columns.map do |name, column|
|
|
570
634
|
if fixture.key?(name)
|
|
571
|
-
|
|
572
|
-
with_yaml_fallback(
|
|
635
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
636
|
+
with_yaml_fallback(column.fetch_cast_type(self).serialize(fixture[name]))
|
|
573
637
|
else
|
|
574
638
|
default_insert_value(column)
|
|
575
639
|
end
|
|
@@ -614,10 +678,6 @@ module ActiveRecord
|
|
|
614
678
|
end
|
|
615
679
|
end
|
|
616
680
|
|
|
617
|
-
def with_multi_statements
|
|
618
|
-
yield
|
|
619
|
-
end
|
|
620
|
-
|
|
621
681
|
def combine_multi_statements(total_sql)
|
|
622
682
|
total_sql.join(";\n")
|
|
623
683
|
end
|
|
@@ -629,6 +689,8 @@ module ActiveRecord
|
|
|
629
689
|
raise AsynchronousQueryInsideTransactionError, "Asynchronous queries are not allowed inside transactions"
|
|
630
690
|
end
|
|
631
691
|
|
|
692
|
+
# We make sure to run query transformers on the original thread
|
|
693
|
+
sql = preprocess_query(sql)
|
|
632
694
|
future_result = async.new(
|
|
633
695
|
pool,
|
|
634
696
|
sql,
|
|
@@ -636,19 +698,19 @@ module ActiveRecord
|
|
|
636
698
|
binds,
|
|
637
699
|
prepare: prepare,
|
|
638
700
|
)
|
|
639
|
-
if supports_concurrent_connections? && current_transaction.
|
|
701
|
+
if supports_concurrent_connections? && !current_transaction.joinable?
|
|
640
702
|
future_result.schedule!(ActiveRecord::Base.asynchronous_queries_session)
|
|
641
703
|
else
|
|
642
704
|
future_result.execute!(self)
|
|
643
705
|
end
|
|
644
|
-
|
|
645
|
-
end
|
|
646
|
-
|
|
647
|
-
result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
|
|
648
|
-
if async
|
|
649
|
-
FutureResult.wrap(result)
|
|
706
|
+
future_result
|
|
650
707
|
else
|
|
651
|
-
result
|
|
708
|
+
result = internal_exec_query(sql, name, binds, prepare: prepare, allow_retry: allow_retry)
|
|
709
|
+
if async
|
|
710
|
+
FutureResult.wrap(result)
|
|
711
|
+
else
|
|
712
|
+
result
|
|
713
|
+
end
|
|
652
714
|
end
|
|
653
715
|
end
|
|
654
716
|
|
|
@@ -13,8 +13,6 @@ module ActiveRecord
|
|
|
13
13
|
dirties_query_cache base, :exec_query, :execute, :create, :insert, :update, :delete, :truncate,
|
|
14
14
|
:truncate_tables, :rollback_to_savepoint, :rollback_db_transaction, :restart_db_transaction,
|
|
15
15
|
:exec_insert_all
|
|
16
|
-
|
|
17
|
-
base.set_callback :checkin, :after, :unset_query_cache!
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
def dirties_query_cache(base, *method_names)
|
|
@@ -31,6 +29,18 @@ module ActiveRecord
|
|
|
31
29
|
end
|
|
32
30
|
end
|
|
33
31
|
|
|
32
|
+
# This is the actual query cache store.
|
|
33
|
+
#
|
|
34
|
+
# It has an internal hash whose keys are either SQL strings, or arrays of
|
|
35
|
+
# two elements [SQL string, binds], if there are binds. The hash values
|
|
36
|
+
# are their corresponding ActiveRecord::Result objects.
|
|
37
|
+
#
|
|
38
|
+
# Keeping the hash size under max size is achieved with LRU eviction.
|
|
39
|
+
#
|
|
40
|
+
# The store gets passed a version object, which is shared among the query
|
|
41
|
+
# cache stores of a given connection pool (see ConnectionPoolConfiguration
|
|
42
|
+
# down below). The version value may be externally changed as a way to
|
|
43
|
+
# signal cache invalidation, that is why all methods have a guard for it.
|
|
34
44
|
class Store # :nodoc:
|
|
35
45
|
attr_accessor :enabled, :dirties
|
|
36
46
|
alias_method :enabled?, :enabled
|
|
@@ -94,6 +104,12 @@ module ActiveRecord
|
|
|
94
104
|
end
|
|
95
105
|
end
|
|
96
106
|
|
|
107
|
+
# Each connection pool has one of these registries. They map execution
|
|
108
|
+
# contexts to query cache stores.
|
|
109
|
+
#
|
|
110
|
+
# The keys of the internal map are threads or fibers (whatever
|
|
111
|
+
# ActiveSupport::IsolatedExecutionState.context returns), and their
|
|
112
|
+
# associated values are their respective query cache stores.
|
|
97
113
|
class QueryCacheRegistry # :nodoc:
|
|
98
114
|
def initialize
|
|
99
115
|
@mutex = Mutex.new
|
|
@@ -191,15 +207,26 @@ module ActiveRecord
|
|
|
191
207
|
end
|
|
192
208
|
end
|
|
193
209
|
|
|
194
|
-
attr_accessor :query_cache
|
|
195
|
-
|
|
196
210
|
def initialize(*)
|
|
197
211
|
super
|
|
198
212
|
@query_cache = nil
|
|
199
213
|
end
|
|
200
214
|
|
|
215
|
+
attr_writer :query_cache
|
|
216
|
+
|
|
217
|
+
def query_cache
|
|
218
|
+
if @pinned && @owner != ActiveSupport::IsolatedExecutionState.context
|
|
219
|
+
# With transactional tests, if the connection is pinned, any thread
|
|
220
|
+
# other than the one that pinned the connection need to go through the
|
|
221
|
+
# query cache pool, so each thread get a different cache.
|
|
222
|
+
pool.query_cache
|
|
223
|
+
else
|
|
224
|
+
@query_cache
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
201
228
|
def query_cache_enabled
|
|
202
|
-
|
|
229
|
+
query_cache&.enabled?
|
|
203
230
|
end
|
|
204
231
|
|
|
205
232
|
# Enable the query cache within the block.
|
|
@@ -238,8 +265,8 @@ module ActiveRecord
|
|
|
238
265
|
|
|
239
266
|
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch.
|
|
240
267
|
# Such queries should not be cached.
|
|
241
|
-
if
|
|
242
|
-
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable)
|
|
268
|
+
if query_cache_enabled && !(arel.respond_to?(:locked) && arel.locked)
|
|
269
|
+
sql, binds, preparable, allow_retry = to_sql_and_binds(arel, binds, preparable, allow_retry)
|
|
243
270
|
|
|
244
271
|
if async
|
|
245
272
|
result = lookup_sql_cache(sql, name, binds) || super(sql, name, binds, preparable: preparable, async: async, allow_retry: allow_retry)
|
|
@@ -262,13 +289,13 @@ module ActiveRecord
|
|
|
262
289
|
|
|
263
290
|
result = nil
|
|
264
291
|
@lock.synchronize do
|
|
265
|
-
result =
|
|
292
|
+
result = query_cache[key]
|
|
266
293
|
end
|
|
267
294
|
|
|
268
295
|
if result
|
|
269
296
|
ActiveSupport::Notifications.instrument(
|
|
270
297
|
"sql.active_record",
|
|
271
|
-
|
|
298
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
272
299
|
)
|
|
273
300
|
end
|
|
274
301
|
|
|
@@ -281,7 +308,7 @@ module ActiveRecord
|
|
|
281
308
|
hit = true
|
|
282
309
|
|
|
283
310
|
@lock.synchronize do
|
|
284
|
-
result =
|
|
311
|
+
result = query_cache.compute_if_absent(key) do
|
|
285
312
|
hit = false
|
|
286
313
|
yield
|
|
287
314
|
end
|
|
@@ -290,13 +317,19 @@ module ActiveRecord
|
|
|
290
317
|
if hit
|
|
291
318
|
ActiveSupport::Notifications.instrument(
|
|
292
319
|
"sql.active_record",
|
|
293
|
-
|
|
320
|
+
cache_notification_info_result(sql, name, binds, result)
|
|
294
321
|
)
|
|
295
322
|
end
|
|
296
323
|
|
|
297
324
|
result.dup
|
|
298
325
|
end
|
|
299
326
|
|
|
327
|
+
def cache_notification_info_result(sql, name, binds, result)
|
|
328
|
+
payload = cache_notification_info(sql, name, binds)
|
|
329
|
+
payload[:row_count] = result.length
|
|
330
|
+
payload
|
|
331
|
+
end
|
|
332
|
+
|
|
300
333
|
# Database adapters can override this method to
|
|
301
334
|
# provide custom cache information.
|
|
302
335
|
def cache_notification_info(sql, name, binds)
|
|
@@ -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,9 +210,14 @@ 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
|
-
binds
|
|
220
|
+
binds&.map do |value|
|
|
226
221
|
if ActiveModel::Attribute === value
|
|
227
222
|
type_cast(value.value_for_database)
|
|
228
223
|
else
|
|
@@ -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
|
|
@@ -28,6 +28,7 @@ module ActiveRecord
|
|
|
28
28
|
sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ")
|
|
29
29
|
sql << o.check_constraint_adds.map { |con| visit_AddCheckConstraint con }.join(" ")
|
|
30
30
|
sql << o.check_constraint_drops.map { |con| visit_DropCheckConstraint con }.join(" ")
|
|
31
|
+
sql << o.constraint_drops.map { |con| visit_DropConstraint con }.join(" ")
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def visit_ColumnDefinition(o)
|
|
@@ -96,9 +97,11 @@ module ActiveRecord
|
|
|
96
97
|
"ADD #{accept(o)}"
|
|
97
98
|
end
|
|
98
99
|
|
|
99
|
-
def
|
|
100
|
+
def visit_DropConstraint(name)
|
|
100
101
|
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
101
102
|
end
|
|
103
|
+
alias :visit_DropForeignKey :visit_DropConstraint
|
|
104
|
+
alias :visit_DropCheckConstraint :visit_DropConstraint
|
|
102
105
|
|
|
103
106
|
def visit_CreateIndexDefinition(o)
|
|
104
107
|
index = o.index
|
|
@@ -127,10 +130,6 @@ module ActiveRecord
|
|
|
127
130
|
"ADD #{accept(o)}"
|
|
128
131
|
end
|
|
129
132
|
|
|
130
|
-
def visit_DropCheckConstraint(name)
|
|
131
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
132
|
-
end
|
|
133
|
-
|
|
134
133
|
def quoted_columns(o)
|
|
135
134
|
String === o.columns ? o.columns : quoted_columns_for_index(o.columns, o.column_options)
|
|
136
135
|
end
|
|
@@ -149,7 +148,7 @@ module ActiveRecord
|
|
|
149
148
|
end
|
|
150
149
|
|
|
151
150
|
def add_column_options!(sql, options)
|
|
152
|
-
sql << " DEFAULT #{
|
|
151
|
+
sql << " DEFAULT #{quote_default_expression_for_column_definition(options[:default], options[:column])}" if options_include_default?(options)
|
|
153
152
|
# must explicitly check for :null to allow change_column to work on migrations
|
|
154
153
|
if options[:null] == false
|
|
155
154
|
sql << " NOT NULL"
|
|
@@ -163,6 +162,11 @@ module ActiveRecord
|
|
|
163
162
|
sql
|
|
164
163
|
end
|
|
165
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
|
+
|
|
166
170
|
def to_sql(sql)
|
|
167
171
|
sql = sql.to_sql if sql.respond_to?(:to_sql)
|
|
168
172
|
sql
|