activerecord 8.0.0 → 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.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +703 -248
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +16 -5
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +3 -3
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/errors.rb +3 -0
  17. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  18. data/lib/active_record/associations/join_dependency.rb +4 -2
  19. data/lib/active_record/associations/preloader/batch.rb +7 -1
  20. data/lib/active_record/associations/preloader/branch.rb +1 -0
  21. data/lib/active_record/associations.rb +159 -21
  22. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  23. data/lib/active_record/attribute_methods/query.rb +34 -0
  24. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  25. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  26. data/lib/active_record/attribute_methods.rb +23 -18
  27. data/lib/active_record/attributes.rb +40 -26
  28. data/lib/active_record/autosave_association.rb +22 -12
  29. data/lib/active_record/base.rb +3 -4
  30. data/lib/active_record/coders/json.rb +14 -5
  31. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
  32. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  34. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -108
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -64
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -20
  45. data/lib/active_record/connection_adapters/column.rb +17 -4
  46. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  47. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  48. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  49. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +33 -4
  51. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  53. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  54. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -17
  56. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  57. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  59. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
  61. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
  62. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +82 -49
  63. data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -10
  64. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  67. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +14 -2
  68. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -12
  69. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +65 -36
  70. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  71. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  72. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -2
  73. data/lib/active_record/connection_adapters.rb +1 -0
  74. data/lib/active_record/connection_handling.rb +15 -10
  75. data/lib/active_record/core.rb +44 -12
  76. data/lib/active_record/counter_cache.rb +34 -9
  77. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  78. data/lib/active_record/database_configurations/database_config.rb +5 -1
  79. data/lib/active_record/database_configurations/hash_config.rb +59 -9
  80. data/lib/active_record/database_configurations/url_config.rb +13 -3
  81. data/lib/active_record/database_configurations.rb +7 -3
  82. data/lib/active_record/delegated_type.rb +19 -19
  83. data/lib/active_record/dynamic_matchers.rb +54 -69
  84. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  85. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  86. data/lib/active_record/encryption/encryptor.rb +39 -25
  87. data/lib/active_record/encryption/scheme.rb +1 -1
  88. data/lib/active_record/enum.rb +37 -20
  89. data/lib/active_record/errors.rb +23 -7
  90. data/lib/active_record/explain.rb +1 -1
  91. data/lib/active_record/explain_registry.rb +51 -2
  92. data/lib/active_record/filter_attribute_handler.rb +73 -0
  93. data/lib/active_record/fixture_set/table_row.rb +19 -2
  94. data/lib/active_record/fixtures.rb +2 -2
  95. data/lib/active_record/future_result.rb +3 -3
  96. data/lib/active_record/gem_version.rb +2 -2
  97. data/lib/active_record/inheritance.rb +1 -1
  98. data/lib/active_record/insert_all.rb +12 -7
  99. data/lib/active_record/locking/optimistic.rb +7 -0
  100. data/lib/active_record/locking/pessimistic.rb +5 -0
  101. data/lib/active_record/log_subscriber.rb +2 -6
  102. data/lib/active_record/middleware/shard_selector.rb +34 -17
  103. data/lib/active_record/migration/command_recorder.rb +19 -3
  104. data/lib/active_record/migration/compatibility.rb +34 -24
  105. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  106. data/lib/active_record/migration.rb +31 -21
  107. data/lib/active_record/model_schema.rb +36 -10
  108. data/lib/active_record/nested_attributes.rb +2 -0
  109. data/lib/active_record/persistence.rb +34 -3
  110. data/lib/active_record/query_cache.rb +22 -15
  111. data/lib/active_record/query_logs.rb +7 -7
  112. data/lib/active_record/querying.rb +4 -4
  113. data/lib/active_record/railtie.rb +35 -6
  114. data/lib/active_record/railties/controller_runtime.rb +11 -6
  115. data/lib/active_record/railties/databases.rake +24 -20
  116. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  117. data/lib/active_record/railties/job_runtime.rb +10 -11
  118. data/lib/active_record/reflection.rb +35 -0
  119. data/lib/active_record/relation/batches.rb +25 -11
  120. data/lib/active_record/relation/calculations.rb +54 -38
  121. data/lib/active_record/relation/delegation.rb +0 -1
  122. data/lib/active_record/relation/finder_methods.rb +42 -25
  123. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  124. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  125. data/lib/active_record/relation/predicate_builder.rb +9 -7
  126. data/lib/active_record/relation/query_attribute.rb +4 -2
  127. data/lib/active_record/relation/query_methods.rb +43 -32
  128. data/lib/active_record/relation/spawn_methods.rb +6 -6
  129. data/lib/active_record/relation/where_clause.rb +10 -11
  130. data/lib/active_record/relation.rb +43 -19
  131. data/lib/active_record/result.rb +44 -21
  132. data/lib/active_record/runtime_registry.rb +42 -58
  133. data/lib/active_record/sanitization.rb +2 -0
  134. data/lib/active_record/schema_dumper.rb +42 -22
  135. data/lib/active_record/scoping.rb +0 -1
  136. data/lib/active_record/secure_token.rb +3 -3
  137. data/lib/active_record/signed_id.rb +47 -18
  138. data/lib/active_record/statement_cache.rb +15 -11
  139. data/lib/active_record/store.rb +44 -19
  140. data/lib/active_record/structured_event_subscriber.rb +85 -0
  141. data/lib/active_record/table_metadata.rb +5 -20
  142. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  143. data/lib/active_record/tasks/database_tasks.rb +44 -45
  144. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  145. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  146. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  147. data/lib/active_record/test_databases.rb +14 -4
  148. data/lib/active_record/test_fixtures.rb +27 -2
  149. data/lib/active_record/testing/query_assertions.rb +8 -2
  150. data/lib/active_record/timestamp.rb +4 -2
  151. data/lib/active_record/transaction.rb +2 -5
  152. data/lib/active_record/transactions.rb +39 -16
  153. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  154. data/lib/active_record/type/internal/timezone.rb +7 -0
  155. data/lib/active_record/type/json.rb +15 -2
  156. data/lib/active_record/type/serialized.rb +11 -4
  157. data/lib/active_record/type/type_map.rb +1 -1
  158. data/lib/active_record/type_caster/connection.rb +2 -1
  159. data/lib/active_record/validations/associated.rb +1 -1
  160. data/lib/active_record.rb +71 -6
  161. data/lib/arel/alias_predication.rb +2 -0
  162. data/lib/arel/collectors/bind.rb +1 -1
  163. data/lib/arel/collectors/sql_string.rb +1 -1
  164. data/lib/arel/collectors/substitute_binds.rb +2 -2
  165. data/lib/arel/crud.rb +8 -11
  166. data/lib/arel/delete_manager.rb +5 -0
  167. data/lib/arel/nodes/binary.rb +1 -1
  168. data/lib/arel/nodes/count.rb +2 -2
  169. data/lib/arel/nodes/delete_statement.rb +4 -2
  170. data/lib/arel/nodes/function.rb +4 -10
  171. data/lib/arel/nodes/named_function.rb +2 -2
  172. data/lib/arel/nodes/node.rb +2 -2
  173. data/lib/arel/nodes/sql_literal.rb +1 -1
  174. data/lib/arel/nodes/update_statement.rb +4 -2
  175. data/lib/arel/nodes.rb +0 -2
  176. data/lib/arel/select_manager.rb +13 -4
  177. data/lib/arel/update_manager.rb +5 -0
  178. data/lib/arel/visitors/dot.rb +2 -3
  179. data/lib/arel/visitors/postgresql.rb +55 -0
  180. data/lib/arel/visitors/sqlite.rb +55 -8
  181. data/lib/arel/visitors/to_sql.rb +6 -22
  182. data/lib/arel.rb +3 -1
  183. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  184. metadata +16 -15
  185. data/lib/active_record/explain_subscriber.rb +0 -34
  186. data/lib/active_record/normalization.rb +0 -163
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  :reverse_order, :distinct, :create_with, :skip_query_cache]
61
61
 
62
62
  CLAUSE_METHODS = [:where, :having, :from]
63
- INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :with, :with_recursive]
63
+ INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL = [:distinct, :with, :with_recursive]
64
64
 
65
65
  VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
66
66
 
@@ -272,7 +272,12 @@ module ActiveRecord
272
272
  # such situation.
273
273
  def create_or_find_by(attributes, &block)
274
274
  with_connection do |connection|
275
- transaction(requires_new: true) { create(attributes, &block) }
275
+ record = nil
276
+ transaction(requires_new: true) do
277
+ record = create(attributes, &block)
278
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
279
+ end
280
+ record
276
281
  rescue ActiveRecord::RecordNotUnique
277
282
  if connection.transaction_open?
278
283
  where(attributes).lock.find_by!(attributes)
@@ -287,7 +292,12 @@ module ActiveRecord
287
292
  # is raised if the created record is invalid.
288
293
  def create_or_find_by!(attributes, &block)
289
294
  with_connection do |connection|
290
- transaction(requires_new: true) { create!(attributes, &block) }
295
+ record = nil
296
+ transaction(requires_new: true) do
297
+ record = create!(attributes, &block)
298
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
299
+ end
300
+ record
291
301
  rescue ActiveRecord::RecordNotUnique
292
302
  if connection.transaction_open?
293
303
  where(attributes).lock.find_by!(attributes)
@@ -297,7 +307,7 @@ module ActiveRecord
297
307
  end
298
308
  end
299
309
 
300
- # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
310
+ # Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
301
311
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
302
312
  def find_or_initialize_by(attributes, &block)
303
313
  find_by(attributes) || new(attributes, &block)
@@ -590,6 +600,18 @@ module ActiveRecord
590
600
 
591
601
  return 0 if @none
592
602
 
603
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
604
+ value = @values[method]
605
+ method == :distinct ? value : value&.any?
606
+ end
607
+ if invalid_methods.any?
608
+ ActiveRecord.deprecator.warn <<~MESSAGE
609
+ `#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query.
610
+
611
+ Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2.
612
+ MESSAGE
613
+ end
614
+
593
615
  if updates.is_a?(Hash)
594
616
  if model.locking_enabled? &&
595
617
  !updates.key?(model.locking_column) &&
@@ -603,17 +625,15 @@ module ActiveRecord
603
625
  end
604
626
 
605
627
  model.with_connection do |c|
606
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
628
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
607
629
  arel.source.left = table
608
630
 
609
- group_values_arel_columns = arel_columns(group_values.uniq)
610
- having_clause_ast = having_clause.ast unless having_clause.empty?
611
631
  key = if model.composite_primary_key?
612
632
  primary_key.map { |pk| table[pk] }
613
633
  else
614
634
  table[primary_key]
615
635
  end
616
- stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
636
+ stmt = arel.compile_update(values, key)
617
637
  c.update(stmt, "#{model} Update All").tap { reset }
618
638
  end
619
639
  end
@@ -820,7 +840,7 @@ module ActiveRecord
820
840
  #
821
841
  # [:returning]
822
842
  # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
823
- # inserted records, which by default is the primary key.
843
+ # upserted records, which by default is the primary key.
824
844
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
825
845
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
826
846
  # clause entirely.
@@ -849,7 +869,9 @@ module ActiveRecord
849
869
  # Active Record's schema_cache.
850
870
  #
851
871
  # [:on_duplicate]
852
- # Configure the SQL update sentence that will be used in case of conflict.
872
+ # Configure the behavior that will be used in case of conflict. Use `:skip`
873
+ # to ignore any conflicts or provide a safe SQL fragment wrapped with
874
+ # `Arel.sql`.
853
875
  #
854
876
  # NOTE: If you use this option you must provide all the columns you want to update
855
877
  # by yourself.
@@ -949,7 +971,7 @@ module ActiveRecord
949
971
  # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
950
972
  # If no time argument is passed, the current time is used as default.
951
973
  #
952
- # === Examples
974
+ # ==== Examples
953
975
  #
954
976
  # # Touch all records
955
977
  # Person.all.touch_all
@@ -1000,7 +1022,7 @@ module ActiveRecord
1000
1022
  #
1001
1023
  # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
1002
1024
  #
1003
- # Both calls delete the affected posts all at once with a single DELETE statement.
1025
+ # This call deletes the affected posts all at once with a single DELETE statement.
1004
1026
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
1005
1027
  # +after_destroy+ callbacks, use the #destroy_all method instead.
1006
1028
  #
@@ -1011,7 +1033,7 @@ module ActiveRecord
1011
1033
  def delete_all
1012
1034
  return 0 if @none
1013
1035
 
1014
- invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
1036
+ invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
1015
1037
  value = @values[method]
1016
1038
  method == :distinct ? value : value&.any?
1017
1039
  end
@@ -1020,17 +1042,15 @@ module ActiveRecord
1020
1042
  end
1021
1043
 
1022
1044
  model.with_connection do |c|
1023
- arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1045
+ arel = eager_loading? ? apply_join_dependency.arel : arel()
1024
1046
  arel.source.left = table
1025
1047
 
1026
- group_values_arel_columns = arel_columns(group_values.uniq)
1027
- having_clause_ast = having_clause.ast unless having_clause.empty?
1028
1048
  key = if model.composite_primary_key?
1029
1049
  primary_key.map { |pk| table[pk] }
1030
1050
  else
1031
1051
  table[primary_key]
1032
1052
  end
1033
- stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1053
+ stmt = arel.compile_delete(key)
1034
1054
 
1035
1055
  c.delete(stmt, "#{model} Delete All").tap { reset }
1036
1056
  end
@@ -1395,12 +1415,16 @@ module ActiveRecord
1395
1415
 
1396
1416
  def _increment_attribute(attribute, value = 1)
1397
1417
  bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
1398
- expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
1418
+ expr = table.coalesce(attribute, 0)
1399
1419
  expr = value < 0 ? expr - bind : expr + bind
1400
1420
  expr.expr
1401
1421
  end
1402
1422
 
1403
1423
  def exec_queries(&block)
1424
+ if lock_value && model.current_preventing_writes
1425
+ raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
1426
+ end
1427
+
1404
1428
  skip_query_cache_if_necessary do
1405
1429
  rows = if scheduled?
1406
1430
  future = @future_result
@@ -1440,7 +1464,7 @@ module ActiveRecord
1440
1464
  else
1441
1465
  relation = join_dependency.apply_column_aliases(relation)
1442
1466
  @_join_dependency = join_dependency
1443
- c.select_all(relation.arel, "SQL", async: async)
1467
+ c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
1444
1468
  end
1445
1469
  end
1446
1470
  end
@@ -29,6 +29,11 @@ module ActiveRecord
29
29
  # ...
30
30
  # ]
31
31
  #
32
+ # # Get the number of rows affected by the query:
33
+ # result = ActiveRecord::Base.lease_connection.exec_query('INSERT INTO posts (title, body) VALUES ("title_3", "body_3"), ("title_4", "body_4")')
34
+ # result.affected_rows
35
+ # # => 2
36
+ #
32
37
  # # ActiveRecord::Result also includes Enumerable.
33
38
  # result.each do |row|
34
39
  # puts row['title'] + " " + row['body']
@@ -89,24 +94,26 @@ module ActiveRecord
89
94
  alias_method :to_hash, :to_h
90
95
  end
91
96
 
92
- attr_reader :columns, :rows, :column_types
97
+ attr_reader :columns, :rows, :affected_rows
93
98
 
94
- def self.empty(async: false) # :nodoc:
99
+ def self.empty(async: false, affected_rows: nil) # :nodoc:
95
100
  if async
96
- EMPTY_ASYNC
101
+ FutureResult.wrap(new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows)).freeze
97
102
  else
98
- EMPTY
103
+ new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows).freeze
99
104
  end
100
105
  end
101
106
 
102
- def initialize(columns, rows, column_types = nil)
107
+ def initialize(columns, rows, column_types = nil, affected_rows: nil)
103
108
  # We freeze the strings to prevent them getting duped when
104
109
  # used as keys in ActiveRecord::Base's @attributes hash
105
110
  @columns = columns.each(&:-@).freeze
106
111
  @rows = rows
107
112
  @hash_rows = nil
108
- @column_types = column_types || EMPTY_HASH
113
+ @column_types = column_types.freeze
114
+ @types_hash = nil
109
115
  @column_indexes = nil
116
+ @affected_rows = affected_rows
110
117
  end
111
118
 
112
119
  # Returns true if this result set includes the column named +name+
@@ -154,6 +161,24 @@ module ActiveRecord
154
161
  n ? hash_rows.last(n) : hash_rows.last
155
162
  end
156
163
 
164
+ # Returns the +ActiveRecord::Type+ type of all columns.
165
+ # Note that not all database adapters return the result types,
166
+ # so the hash may be empty.
167
+ def column_types
168
+ if @column_types
169
+ @types_hash ||= begin
170
+ types = {}
171
+ @columns.each_with_index do |name, index|
172
+ type = @column_types[index] || Type.default_value
173
+ types[name] = types[index] = type
174
+ end
175
+ types.freeze
176
+ end
177
+ else
178
+ EMPTY_HASH
179
+ end
180
+ end
181
+
157
182
  def result # :nodoc:
158
183
  self
159
184
  end
@@ -162,7 +187,7 @@ module ActiveRecord
162
187
  self
163
188
  end
164
189
 
165
- def cast_values(type_overrides = {}) # :nodoc:
190
+ def cast_values(type_overrides = nil) # :nodoc:
166
191
  if columns.one?
167
192
  # Separated to avoid allocating an array per row
168
193
 
@@ -190,13 +215,13 @@ module ActiveRecord
190
215
 
191
216
  def initialize_copy(other)
192
217
  @rows = rows.dup
193
- @column_types = column_types.dup
194
218
  @hash_rows = nil
195
219
  end
196
220
 
197
221
  def freeze # :nodoc:
198
222
  hash_rows.freeze
199
- indexed_rows.freeze
223
+ indexed_rows
224
+ column_types
200
225
  super
201
226
  end
202
227
 
@@ -204,7 +229,7 @@ module ActiveRecord
204
229
  @column_indexes ||= begin
205
230
  index = 0
206
231
  hash = {}
207
- length = columns.length
232
+ length = columns.length
208
233
  while index < length
209
234
  hash[columns[index]] = index
210
235
  index += 1
@@ -222,10 +247,14 @@ module ActiveRecord
222
247
 
223
248
  private
224
249
  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)
250
+ if type_overrides
251
+ type_overrides.fetch(name) do
252
+ column_type(name, index, nil)
228
253
  end
254
+ elsif @column_types
255
+ @column_types[index] || Type.default_value
256
+ else
257
+ Type.default_value
229
258
  end
230
259
  end
231
260
 
@@ -237,14 +266,8 @@ module ActiveRecord
237
266
  end
238
267
  end
239
268
 
240
- empty_array = [].freeze
269
+ EMPTY_ARRAY = [].freeze
241
270
  EMPTY_HASH = {}.freeze
242
- private_constant :EMPTY_HASH
243
-
244
- EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
245
- private_constant :EMPTY
246
-
247
- EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
248
- private_constant :EMPTY_ASYNC
271
+ private_constant :EMPTY_ARRAY, :EMPTY_HASH
249
272
  end
250
273
  end
@@ -3,80 +3,64 @@
3
3
  module ActiveRecord
4
4
  # This is a thread locals registry for Active Record. For example:
5
5
  #
6
- # ActiveRecord::RuntimeRegistry.sql_runtime
6
+ # ActiveRecord::RuntimeRegistry.stats.sql_runtime
7
7
  #
8
8
  # returns the connection handler local to the current unit of execution (either thread of fiber).
9
9
  module RuntimeRegistry # :nodoc:
10
- extend self
11
-
12
- def sql_runtime
13
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
10
+ class Stats
11
+ attr_accessor :sql_runtime, :async_sql_runtime, :queries_count, :cached_queries_count
12
+
13
+ def initialize
14
+ @sql_runtime = 0.0
15
+ @async_sql_runtime = 0.0
16
+ @queries_count = 0
17
+ @cached_queries_count = 0
18
+ end
19
+
20
+ def reset_runtimes
21
+ sql_runtime_was = @sql_runtime
22
+ @sql_runtime = 0.0
23
+ @async_sql_runtime = 0.0
24
+ sql_runtime_was
25
+ end
26
+
27
+ public alias_method :reset, :initialize
14
28
  end
15
29
 
16
- def sql_runtime=(runtime)
17
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
- end
19
-
20
- def async_sql_runtime
21
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
- end
30
+ extend self
23
31
 
24
- def async_sql_runtime=(runtime)
25
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
32
+ def call(name, start, finish, id, payload)
33
+ record(
34
+ payload[:name],
35
+ (finish - start) * 1_000.0,
36
+ cached: payload[:cached],
37
+ async: payload[:async],
38
+ lock_wait: payload[:lock_wait],
39
+ )
26
40
  end
27
41
 
28
- def queries_count
29
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
- end
42
+ def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
43
+ stats = self.stats
31
44
 
32
- def queries_count=(count)
33
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
- end
45
+ unless query_name == "TRANSACTION" || query_name == "SCHEMA"
46
+ stats.queries_count += 1
47
+ stats.cached_queries_count += 1 if cached
48
+ end
35
49
 
36
- def cached_queries_count
37
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
50
+ if async
51
+ stats.async_sql_runtime += (runtime - lock_wait)
52
+ end
53
+ stats.sql_runtime += runtime
38
54
  end
39
55
 
40
- def cached_queries_count=(count)
41
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
56
+ def stats
57
+ ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
42
58
  end
43
59
 
44
60
  def reset
45
- reset_runtimes
46
- reset_queries_count
47
- reset_cached_queries_count
48
- end
49
-
50
- def reset_runtimes
51
- rt, self.sql_runtime = sql_runtime, 0.0
52
- self.async_sql_runtime = 0.0
53
- rt
54
- end
55
-
56
- def reset_queries_count
57
- qc = queries_count
58
- self.queries_count = 0
59
- qc
60
- end
61
-
62
- def reset_cached_queries_count
63
- qc = cached_queries_count
64
- self.cached_queries_count = 0
65
- qc
61
+ stats.reset
66
62
  end
67
63
  end
68
64
  end
69
65
 
70
- ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
- unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
- ActiveRecord::RuntimeRegistry.queries_count += 1
73
- ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
- end
75
-
76
- runtime = (finish - start) * 1_000.0
77
-
78
- if payload[:async]
79
- ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
80
- end
81
- ActiveRecord::RuntimeRegistry.sql_runtime += runtime
82
- end
66
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
@@ -161,6 +161,8 @@ module ActiveRecord
161
161
  #
162
162
  # sanitize_sql_array(["role = ?", 0])
163
163
  # # => "role = '0'"
164
+ #
165
+ # Before using this method, please consider if Arel.sql would be better for your use-case
164
166
  def sanitize_sql_array(ary)
165
167
  statement, *values = ary
166
168
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
@@ -165,7 +165,7 @@ module ActiveRecord
165
165
  # first dump primary key column
166
166
  pk = @connection.primary_key(table)
167
167
 
168
- tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
168
+ tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
169
169
 
170
170
  case pk
171
171
  when String
@@ -192,7 +192,7 @@ module ActiveRecord
192
192
  tbl.puts ", force: :cascade do |t|"
193
193
 
194
194
  # then dump all non-primary key columns
195
- columns.each do |column|
195
+ columns.sort_by(&:name).each do |column|
196
196
  raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
197
197
  next if column.name == pk
198
198
 
@@ -207,12 +207,17 @@ module ActiveRecord
207
207
  end
208
208
 
209
209
  indexes_in_create(table, tbl)
210
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
210
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
211
211
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
212
212
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
213
213
 
214
214
  tbl.puts " end"
215
215
 
216
+ if remaining
217
+ tbl.puts
218
+ tbl.print remaining.string
219
+ end
220
+
216
221
  stream.print tbl.string
217
222
  rescue => e
218
223
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
@@ -227,8 +232,8 @@ module ActiveRecord
227
232
  def indexes(table, stream)
228
233
  if (indexes = @connection.indexes(table)).any?
229
234
  add_index_statements = indexes.map do |index|
230
- table_name = remove_prefix_and_suffix(index.table).inspect
231
- " add_index #{([table_name] + index_parts(index)).join(', ')}"
235
+ table_name = remove_prefix_and_suffix(index.table)
236
+ " add_index #{([relation_name(table_name).inspect] + index_parts(index)).join(', ')}"
232
237
  end
233
238
 
234
239
  stream.puts add_index_statements.sort.join("\n")
@@ -272,35 +277,49 @@ module ActiveRecord
272
277
  index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
273
278
  index_parts << "type: #{index.type.inspect}" if index.type
274
279
  index_parts << "comment: #{index.comment.inspect}" if index.comment
280
+ index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
275
281
  index_parts
276
282
  end
277
283
 
278
284
  def check_constraints_in_create(table, stream)
279
285
  if (check_constraints = @connection.check_constraints(table)).any?
280
- add_check_constraint_statements = check_constraints.map do |check_constraint|
281
- parts = [
282
- "t.check_constraint #{check_constraint.expression.inspect}"
283
- ]
286
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
284
287
 
285
- if check_constraint.export_name_on_schema_dump?
286
- parts << "name: #{check_constraint.name.inspect}"
288
+ unless check_valid.empty?
289
+ check_constraint_statements = check_valid.map do |check|
290
+ " t.check_constraint #{check_parts(check).join(', ')}"
287
291
  end
288
292
 
289
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
290
-
291
- " #{parts.join(', ')}"
293
+ stream.puts check_constraint_statements.sort.join("\n")
292
294
  end
293
295
 
294
- stream.puts add_check_constraint_statements.sort.join("\n")
296
+ unless check_invalid.empty?
297
+ remaining = StringIO.new
298
+ table_name = remove_prefix_and_suffix(table).inspect
299
+
300
+ add_check_constraint_statements = check_invalid.map do |check|
301
+ " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
302
+ end
303
+
304
+ remaining.puts add_check_constraint_statements.sort.join("\n")
305
+ remaining
306
+ end
295
307
  end
296
308
  end
297
309
 
310
+ def check_parts(check)
311
+ check_parts = [ check.expression.inspect ]
312
+ check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
313
+ check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
314
+ check_parts
315
+ end
316
+
298
317
  def foreign_keys(table, stream)
299
318
  if (foreign_keys = @connection.foreign_keys(table)).any?
300
319
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
301
320
  parts = [
302
- "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
303
- remove_prefix_and_suffix(foreign_key.to_table).inspect,
321
+ relation_name(remove_prefix_and_suffix(foreign_key.from_table)).inspect,
322
+ relation_name(remove_prefix_and_suffix(foreign_key.to_table)).inspect,
304
323
  ]
305
324
 
306
325
  if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
@@ -311,16 +330,13 @@ module ActiveRecord
311
330
  parts << "primary_key: #{foreign_key.primary_key.inspect}"
312
331
  end
313
332
 
314
- if foreign_key.export_name_on_schema_dump?
315
- parts << "name: #{foreign_key.name.inspect}"
316
- end
317
-
333
+ parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
318
334
  parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
319
335
  parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
320
336
  parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
321
337
  parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
322
338
 
323
- " #{parts.join(', ')}"
339
+ " add_foreign_key #{parts.join(', ')}"
324
340
  end
325
341
 
326
342
  stream.puts add_foreign_key_statements.sort.join("\n")
@@ -345,6 +361,10 @@ module ActiveRecord
345
361
  end
346
362
  end
347
363
 
364
+ def relation_name(name)
365
+ name
366
+ end
367
+
348
368
  def remove_prefix_and_suffix(table)
349
369
  # This method appears at the top when profiling active_record test cases run.
350
370
  # Avoid costly calculation when there are no prefix and suffix.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  module Scoping
@@ -30,13 +30,13 @@ module ActiveRecord
30
30
  # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
31
31
  # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
32
32
  #
33
- # === Options
33
+ # ==== Options
34
34
  #
35
- # [:length]
35
+ # [+:length+]
36
36
  # Length of the Secure Random, with a minimum of 24 characters. It will
37
37
  # default to 24.
38
38
  #
39
- # [:on]
39
+ # [+:on+]
40
40
  # The callback when the value is generated. When called with <tt>on:
41
41
  # :initialize</tt>, the value is generated in an
42
42
  # <tt>after_initialize</tt> callback, otherwise the value will be used