activerecord 7.2.3 → 8.1.3

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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +612 -1055
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +35 -11
  6. data/lib/active_record/associations/builder/association.rb +23 -11
  7. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  8. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  11. data/lib/active_record/associations/collection_association.rb +1 -1
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  17. data/lib/active_record/associations/join_dependency.rb +4 -2
  18. data/lib/active_record/associations/preloader/association.rb +2 -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/singular_association.rb +8 -3
  22. data/lib/active_record/associations.rb +192 -24
  23. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  24. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  25. data/lib/active_record/attribute_methods/query.rb +34 -0
  26. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  27. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  28. data/lib/active_record/attributes.rb +3 -0
  29. data/lib/active_record/autosave_association.rb +69 -27
  30. data/lib/active_record/base.rb +1 -2
  31. data/lib/active_record/coders/json.rb +14 -5
  32. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  34. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
  36. data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
  37. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  38. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  39. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  40. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  41. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
  43. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  44. data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
  45. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
  46. data/lib/active_record/connection_adapters/column.rb +17 -4
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  49. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  57. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  58. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  59. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  61. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  62. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  63. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  64. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
  66. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  67. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  68. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  69. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  70. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  71. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  72. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  73. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -14
  74. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
  75. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  76. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  77. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  78. data/lib/active_record/connection_adapters.rb +1 -56
  79. data/lib/active_record/connection_handling.rb +25 -2
  80. data/lib/active_record/core.rb +33 -17
  81. data/lib/active_record/counter_cache.rb +33 -8
  82. data/lib/active_record/database_configurations/database_config.rb +9 -1
  83. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  84. data/lib/active_record/database_configurations/url_config.rb +13 -3
  85. data/lib/active_record/database_configurations.rb +7 -3
  86. data/lib/active_record/delegated_type.rb +1 -1
  87. data/lib/active_record/dynamic_matchers.rb +54 -69
  88. data/lib/active_record/encryption/config.rb +3 -1
  89. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  90. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  91. data/lib/active_record/encryption/encryptor.rb +28 -8
  92. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  93. data/lib/active_record/encryption/scheme.rb +9 -2
  94. data/lib/active_record/enum.rb +33 -30
  95. data/lib/active_record/errors.rb +33 -9
  96. data/lib/active_record/explain.rb +1 -1
  97. data/lib/active_record/explain_registry.rb +51 -2
  98. data/lib/active_record/filter_attribute_handler.rb +73 -0
  99. data/lib/active_record/fixtures.rb +2 -4
  100. data/lib/active_record/future_result.rb +15 -9
  101. data/lib/active_record/gem_version.rb +2 -2
  102. data/lib/active_record/inheritance.rb +1 -1
  103. data/lib/active_record/insert_all.rb +14 -9
  104. data/lib/active_record/locking/optimistic.rb +8 -1
  105. data/lib/active_record/locking/pessimistic.rb +5 -0
  106. data/lib/active_record/log_subscriber.rb +3 -13
  107. data/lib/active_record/middleware/shard_selector.rb +34 -17
  108. data/lib/active_record/migration/command_recorder.rb +45 -12
  109. data/lib/active_record/migration/compatibility.rb +37 -24
  110. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  111. data/lib/active_record/migration.rb +48 -42
  112. data/lib/active_record/model_schema.rb +38 -13
  113. data/lib/active_record/nested_attributes.rb +6 -6
  114. data/lib/active_record/persistence.rb +162 -133
  115. data/lib/active_record/query_cache.rb +22 -15
  116. data/lib/active_record/query_logs.rb +100 -52
  117. data/lib/active_record/query_logs_formatter.rb +17 -28
  118. data/lib/active_record/querying.rb +8 -8
  119. data/lib/active_record/railtie.rb +35 -30
  120. data/lib/active_record/railties/controller_runtime.rb +11 -6
  121. data/lib/active_record/railties/databases.rake +26 -38
  122. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  123. data/lib/active_record/railties/job_runtime.rb +10 -11
  124. data/lib/active_record/reflection.rb +53 -21
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  126. data/lib/active_record/relation/batches.rb +147 -73
  127. data/lib/active_record/relation/calculations.rb +52 -40
  128. data/lib/active_record/relation/delegation.rb +25 -15
  129. data/lib/active_record/relation/finder_methods.rb +40 -24
  130. data/lib/active_record/relation/merger.rb +8 -8
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  132. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  133. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  135. data/lib/active_record/relation/predicate_builder.rb +22 -7
  136. data/lib/active_record/relation/query_attribute.rb +3 -1
  137. data/lib/active_record/relation/query_methods.rb +140 -86
  138. data/lib/active_record/relation/spawn_methods.rb +7 -7
  139. data/lib/active_record/relation/where_clause.rb +2 -9
  140. data/lib/active_record/relation.rb +107 -75
  141. data/lib/active_record/result.rb +109 -24
  142. data/lib/active_record/runtime_registry.rb +42 -58
  143. data/lib/active_record/sanitization.rb +9 -6
  144. data/lib/active_record/schema_dumper.rb +18 -11
  145. data/lib/active_record/schema_migration.rb +2 -1
  146. data/lib/active_record/scoping/named.rb +5 -2
  147. data/lib/active_record/scoping.rb +0 -1
  148. data/lib/active_record/signed_id.rb +43 -15
  149. data/lib/active_record/statement_cache.rb +24 -20
  150. data/lib/active_record/store.rb +51 -22
  151. data/lib/active_record/structured_event_subscriber.rb +85 -0
  152. data/lib/active_record/table_metadata.rb +6 -23
  153. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  154. data/lib/active_record/tasks/database_tasks.rb +85 -85
  155. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  156. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  157. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  158. data/lib/active_record/test_databases.rb +14 -4
  159. data/lib/active_record/test_fixtures.rb +39 -2
  160. data/lib/active_record/testing/query_assertions.rb +8 -2
  161. data/lib/active_record/timestamp.rb +4 -2
  162. data/lib/active_record/token_for.rb +1 -1
  163. data/lib/active_record/transaction.rb +2 -5
  164. data/lib/active_record/transactions.rb +37 -16
  165. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  166. data/lib/active_record/type/internal/timezone.rb +7 -0
  167. data/lib/active_record/type/json.rb +13 -2
  168. data/lib/active_record/type/serialized.rb +16 -4
  169. data/lib/active_record/type/type_map.rb +1 -1
  170. data/lib/active_record/type_caster/connection.rb +2 -1
  171. data/lib/active_record/validations/associated.rb +1 -1
  172. data/lib/active_record/validations/uniqueness.rb +8 -8
  173. data/lib/active_record.rb +84 -49
  174. data/lib/arel/alias_predication.rb +2 -0
  175. data/lib/arel/collectors/bind.rb +2 -2
  176. data/lib/arel/collectors/sql_string.rb +1 -1
  177. data/lib/arel/collectors/substitute_binds.rb +2 -2
  178. data/lib/arel/crud.rb +6 -11
  179. data/lib/arel/nodes/binary.rb +1 -1
  180. data/lib/arel/nodes/count.rb +2 -2
  181. data/lib/arel/nodes/function.rb +4 -10
  182. data/lib/arel/nodes/named_function.rb +2 -2
  183. data/lib/arel/nodes/node.rb +2 -2
  184. data/lib/arel/nodes/sql_literal.rb +1 -1
  185. data/lib/arel/nodes.rb +0 -2
  186. data/lib/arel/predications.rb +1 -3
  187. data/lib/arel/select_manager.rb +7 -2
  188. data/lib/arel/table.rb +3 -7
  189. data/lib/arel/visitors/dot.rb +0 -3
  190. data/lib/arel/visitors/postgresql.rb +55 -0
  191. data/lib/arel/visitors/sqlite.rb +55 -8
  192. data/lib/arel/visitors/to_sql.rb +3 -21
  193. data/lib/arel.rb +3 -1
  194. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  195. metadata +16 -13
  196. data/lib/active_record/explain_subscriber.rb +0 -34
  197. data/lib/active_record/normalization.rb +0 -163
  198. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -8,7 +8,8 @@ module ActiveRecord
8
8
  class Config
9
9
  attr_accessor :primary_key, :deterministic_key, :store_key_references, :key_derivation_salt, :hash_digest_class,
10
10
  :support_unencrypted_data, :encrypt_fixtures, :validate_column_size, :add_to_filter_parameters,
11
- :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption
11
+ :excluded_from_filter_parameters, :extend_queries, :previous_schemes, :forced_encoding_for_deterministic_encryption,
12
+ :compressor
12
13
 
13
14
  def initialize
14
15
  set_defaults
@@ -55,6 +56,7 @@ module ActiveRecord
55
56
  self.previous_schemes = []
56
57
  self.forced_encoding_for_deterministic_encryption = Encoding::UTF_8
57
58
  self.hash_digest_class = OpenSSL::Digest::SHA1
59
+ self.compressor = Zlib
58
60
 
59
61
  # TODO: Setting to false for now as the implementation is a bit experimental
60
62
  self.extend_queries = false
@@ -30,10 +30,10 @@ module ActiveRecord
30
30
  # will use the oldest encryption scheme to encrypt new data by default. You can change this by setting
31
31
  # <tt>deterministic: { fixed: false }</tt>. That will make it use the newest encryption scheme for encrypting new
32
32
  # data.
33
- # * <tt>:support_unencrypted_data</tt> - If `config.active_record.encryption.support_unencrypted_data` is +true+,
34
- # you can set this to +false+ to opt out of unencrypted data support for this attribute. This is useful for
35
- # scenarios where you encrypt one column, and want to disable support for unencrypted data without having to tweak
36
- # the global setting.
33
+ # * <tt>:support_unencrypted_data</tt> - When true, unencrypted data can be read normally. When false, it will raise errors.
34
+ # Falls back to +config.active_record.encryption.support_unencrypted_data+ if no value is provided.
35
+ # This is useful for scenarios where you encrypt one column, and want to disable support for unencrypted data
36
+ # without having to tweak the global setting.
37
37
  # * <tt>:downcase</tt> - When true, it converts the encrypted content to downcase automatically. This allows to
38
38
  # effectively ignore case when querying data. Notice that the case is lost. Use +:ignore_case+ if you are interested
39
39
  # in preserving it.
@@ -46,11 +46,11 @@ module ActiveRecord
46
46
  # * <tt>:previous</tt> - List of previous encryption schemes. When provided, they will be used in order when trying to read
47
47
  # the attribute. Each entry of the list can contain the properties supported by #encrypts. Also, when deterministic
48
48
  # encryption is used, they will be used to generate additional ciphertexts to check in the queries.
49
- def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
49
+ def encrypts(*names, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties)
50
50
  self.encrypted_attributes ||= Set.new # not using :default because the instance would be shared across classes
51
51
 
52
52
  names.each do |name|
53
- encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
53
+ encrypt_attribute name, key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties
54
54
  end
55
55
  end
56
56
 
@@ -81,12 +81,12 @@ module ActiveRecord
81
81
  end
82
82
  end
83
83
 
84
- def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], **context_properties)
84
+ def encrypt_attribute(name, key_provider: nil, key: nil, deterministic: false, support_unencrypted_data: nil, downcase: false, ignore_case: false, previous: [], compress: true, compressor: nil, **context_properties)
85
85
  encrypted_attributes << name.to_sym
86
86
 
87
87
  decorate_attributes([name]) do |name, cast_type|
88
88
  scheme = scheme_for key_provider: key_provider, key: key, deterministic: deterministic, support_unencrypted_data: support_unencrypted_data, \
89
- downcase: downcase, ignore_case: ignore_case, previous: previous, **context_properties
89
+ downcase: downcase, ignore_case: ignore_case, previous: previous, compress: compress, compressor: compressor, **context_properties
90
90
 
91
91
  ActiveRecord::Encryption::EncryptedAttributeType.new(scheme: scheme, cast_type: cast_type, default: columns_hash[name.to_s]&.default)
92
92
  end
@@ -59,7 +59,7 @@ module ActiveRecord
59
59
  end
60
60
 
61
61
  def support_unencrypted_data?
62
- ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
62
+ scheme.support_unencrypted_data? && !previous_type?
63
63
  end
64
64
 
65
65
  private
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  end
101
101
 
102
102
  def decrypt(value)
103
- text_to_database_type decrypt_as_text(value)
103
+ text_to_database_type decrypt_as_text(database_type_to_text(value))
104
104
  end
105
105
 
106
106
  def try_to_deserialize_with_previous_encrypted_types(value)
@@ -170,6 +170,15 @@ module ActiveRecord
170
170
  value
171
171
  end
172
172
  end
173
+
174
+ def database_type_to_text(value)
175
+ if value && cast_type.binary?
176
+ binary_cast_type = cast_type.serialized? ? cast_type.subtype : cast_type
177
+ binary_cast_type.deserialize(value)
178
+ else
179
+ value
180
+ end
181
+ end
173
182
  end
174
183
  end
175
184
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "openssl"
4
- require "zlib"
5
4
  require "active_support/core_ext/numeric"
6
5
 
7
6
  module ActiveRecord
@@ -12,13 +11,22 @@ module ActiveRecord
12
11
  # It interacts with a KeyProvider for getting the keys, and delegate to
13
12
  # ActiveRecord::Encryption::Cipher the actual encryption algorithm.
14
13
  class Encryptor
14
+ # The compressor to use for compressing the payload.
15
+ attr_reader :compressor
16
+
15
17
  # ==== Options
16
18
  #
17
19
  # [+:compress+]
18
20
  # Boolean indicating whether records should be compressed before
19
21
  # encryption. Defaults to +true+.
20
- def initialize(compress: true)
22
+ #
23
+ # [+:compressor+]
24
+ # The compressor to use. It must respond to +deflate+ and +inflate+.
25
+ # If not provided, will default to +ActiveRecord::Encryption.config.compressor+,
26
+ # which itself defaults to +Zlib+.
27
+ def initialize(compress: true, compressor: nil)
21
28
  @compress = compress
29
+ @compressor = compressor || ActiveRecord::Encryption.config.compressor
22
30
  end
23
31
 
24
32
  # Encrypts +clean_text+ and returns the encrypted result.
@@ -79,9 +87,25 @@ module ActiveRecord
79
87
  serializer.binary?
80
88
  end
81
89
 
90
+ def compress? # :nodoc:
91
+ @compress
92
+ end
93
+
82
94
  private
83
95
  DECRYPT_ERRORS = [OpenSSL::Cipher::CipherError, Errors::EncryptedContentIntegrity, Errors::Decryption]
84
96
  ENCODING_ERRORS = [EncodingError, Errors::Encoding]
97
+
98
+ # This threshold cannot be changed.
99
+ #
100
+ # Users can search for attributes encrypted with `deterministic: true`.
101
+ # That is possible because we are able to generate the message for the
102
+ # given clear text deterministically, and with that perform a regular
103
+ # string lookup in SQL.
104
+ #
105
+ # Problem is, messages may have a "c" header that is present or not
106
+ # depending on whether compression was applied on encryption. If this
107
+ # threshold was modified, the message generated for lookup could vary
108
+ # for the same clear text, and searches on exisiting data could fail.
85
109
  THRESHOLD_TO_JUSTIFY_COMPRESSION = 140.bytes
86
110
 
87
111
  def default_key_provider
@@ -131,12 +155,8 @@ module ActiveRecord
131
155
  end
132
156
  end
133
157
 
134
- def compress?
135
- @compress
136
- end
137
-
138
158
  def compress(data)
139
- Zlib::Deflate.deflate(data).tap do |compressed_data|
159
+ @compressor.deflate(data).tap do |compressed_data|
140
160
  compressed_data.force_encoding(data.encoding)
141
161
  end
142
162
  end
@@ -150,7 +170,7 @@ module ActiveRecord
150
170
  end
151
171
 
152
172
  def uncompress(data)
153
- Zlib::Inflate.inflate(data).tap do |uncompressed_data|
173
+ @compressor.inflate(data).tap do |uncompressed_data|
154
174
  uncompressed_data.force_encoding(data.encoding)
155
175
  end
156
176
  end
@@ -41,6 +41,8 @@ module ActiveRecord
41
41
  module EncryptedQuery # :nodoc:
42
42
  class << self
43
43
  def process_arguments(owner, args, check_for_additional_values)
44
+ owner = owner.model if owner.is_a?(Relation)
45
+
44
46
  return args if owner.deterministic_encrypted_attributes&.empty?
45
47
 
46
48
  if args.is_a?(Array) && (options = args.first).is_a?(Hash)
@@ -102,12 +104,12 @@ module ActiveRecord
102
104
  end
103
105
 
104
106
  def scope_for_create
105
- return super unless klass.deterministic_encrypted_attributes&.any?
107
+ return super unless model.deterministic_encrypted_attributes&.any?
106
108
 
107
109
  scope_attributes = super
108
110
  wheres = where_values_hash
109
111
 
110
- klass.deterministic_encrypted_attributes.each do |attribute_name|
112
+ model.deterministic_encrypted_attributes.each do |attribute_name|
111
113
  attribute_name = attribute_name.to_s
112
114
  values = wheres[attribute_name]
113
115
  if values.is_a?(Array) && values[1..].all?(AdditionalValue)
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  attr_accessor :previous_schemes
12
12
 
13
13
  def initialize(key_provider: nil, key: nil, deterministic: nil, support_unencrypted_data: nil, downcase: nil, ignore_case: nil,
14
- previous_schemes: nil, **context_properties)
14
+ previous_schemes: nil, compress: true, compressor: nil, **context_properties)
15
15
  # Initializing all attributes to +nil+ as we want to allow a "not set" semantics so that we
16
16
  # can merge schemes without overriding values with defaults. See +#merge+
17
17
 
@@ -24,8 +24,13 @@ module ActiveRecord
24
24
  @previous_schemes_param = previous_schemes
25
25
  @previous_schemes = Array.wrap(previous_schemes)
26
26
  @context_properties = context_properties
27
+ @compress = compress
28
+ @compressor = compressor
27
29
 
28
30
  validate_config!
31
+
32
+ @context_properties[:encryptor] = Encryptor.new(compress: @compress) unless @compress
33
+ @context_properties[:encryptor] = Encryptor.new(compressor: compressor) if compressor
29
34
  end
30
35
 
31
36
  def ignore_case?
@@ -54,7 +59,7 @@ module ActiveRecord
54
59
  end
55
60
 
56
61
  def merge(other_scheme)
57
- self.class.new(**to_h.merge(other_scheme.to_h))
62
+ self.class.new(**to_h, **other_scheme.to_h)
58
63
  end
59
64
 
60
65
  def to_h
@@ -78,6 +83,8 @@ module ActiveRecord
78
83
  def validate_config!
79
84
  raise Errors::Configuration, "ignore_case: can only be used with deterministic encryption" if @ignore_case && !@deterministic
80
85
  raise Errors::Configuration, "key_provider: and key: can't be used simultaneously" if @key_provider_param && @key
86
+ raise Errors::Configuration, "compressor: can't be used with compress: false" if !@compress && @compressor
87
+ raise Errors::Configuration, "compressor: can't be used with encryptor" if @compressor && @context_properties[:encryptor]
81
88
  end
82
89
 
83
90
  def key_provider_from_key
@@ -214,34 +214,16 @@ module ActiveRecord
214
214
  attr_reader :name, :mapping
215
215
  end
216
216
 
217
- def enum(name = nil, values = nil, **options)
218
- if name
219
- values, options = options, {} unless values
220
- return _enum(name, values, **options)
221
- end
222
-
223
- definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
224
- options.transform_keys! { |key| :"#{key[1..-1]}" }
225
-
226
- definitions.each { |name, values| _enum(name, values, **options) }
227
-
228
- ActiveRecord.deprecator.warn(<<~MSG)
229
- Defining enums with keyword arguments is deprecated and will be removed
230
- in Rails 8.0. Positional arguments should be used instead:
231
-
232
- #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
233
- MSG
217
+ def enum(name, values = nil, **options)
218
+ values, options = options, {} unless values
219
+ _enum(name, values, **options)
234
220
  end
235
221
 
236
222
  private
237
- def inherited(base)
238
- base.defined_enums = defined_enums.deep_dup
239
- super
240
- end
241
-
242
223
  def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
243
- assert_valid_enum_definition_values(values)
224
+ values = assert_valid_enum_definition_values(values)
244
225
  assert_valid_enum_options(options)
226
+
245
227
  # statuses = { }
246
228
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
247
229
  name = name.to_s
@@ -305,6 +287,11 @@ module ActiveRecord
305
287
  enum_values.freeze
306
288
  end
307
289
 
290
+ def inherited(base)
291
+ base.defined_enums = defined_enums.deep_dup
292
+ super
293
+ end
294
+
308
295
  class EnumMethods < Module # :nodoc:
309
296
  def initialize(klass)
310
297
  @klass = klass
@@ -355,6 +342,20 @@ module ActiveRecord
355
342
  if values.keys.any?(&:blank?)
356
343
  raise ArgumentError, "Enum values #{values} must not contain a blank name."
357
344
  end
345
+
346
+ values = values.transform_values do |value|
347
+ value.is_a?(Symbol) ? value.name : value
348
+ end
349
+
350
+ values.each_value do |value|
351
+ case value
352
+ when String, Integer, Float, true, false, nil
353
+ # noop
354
+ else
355
+ raise ArgumentError, "Enum values #{values} must be only booleans, integers, floats, symbols or strings, got: #{value.class}"
356
+ end
357
+ end
358
+
358
359
  when Array
359
360
  if values.empty?
360
361
  raise ArgumentError, "Enum values #{values} must not be empty."
@@ -370,6 +371,8 @@ module ActiveRecord
370
371
  else
371
372
  raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
372
373
  end
374
+
375
+ values
373
376
  end
374
377
 
375
378
  def assert_valid_enum_options(options)
@@ -381,25 +384,25 @@ module ActiveRecord
381
384
 
382
385
  ENUM_CONFLICT_MESSAGE = \
383
386
  "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
384
- "this will generate a %{type} method \"%{method}\", which is already defined " \
387
+ "this will generate %{type} method \"%{method}\", which is already defined " \
385
388
  "by %{source}."
386
389
  private_constant :ENUM_CONFLICT_MESSAGE
387
390
 
388
391
  def detect_enum_conflict!(enum_name, method_name, klass_method = false)
389
392
  if klass_method && dangerous_class_method?(method_name)
390
- raise_conflict_error(enum_name, method_name, type: "class")
393
+ raise_conflict_error(enum_name, method_name, "a class")
391
394
  elsif klass_method && method_defined_within?(method_name, Relation)
392
- raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
395
+ raise_conflict_error(enum_name, method_name, "a class", source: Relation.name)
393
396
  elsif klass_method && method_name.to_sym == :id
394
- raise_conflict_error(enum_name, method_name)
397
+ raise_conflict_error(enum_name, method_name, "an instance")
395
398
  elsif !klass_method && dangerous_attribute_method?(method_name)
396
- raise_conflict_error(enum_name, method_name)
399
+ raise_conflict_error(enum_name, method_name, "an instance")
397
400
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
398
- raise_conflict_error(enum_name, method_name, source: "another enum")
401
+ raise_conflict_error(enum_name, method_name, "an instance", source: "another enum")
399
402
  end
400
403
  end
401
404
 
402
- def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record")
405
+ def raise_conflict_error(enum_name, method_name, type, source: "Active Record")
403
406
  raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
404
407
  enum: enum_name,
405
408
  klass: name,
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/deprecation"
4
3
 
5
4
  module ActiveRecord
6
5
  include ActiveSupport::Deprecation::DeprecatedConstantAccessor
@@ -84,6 +83,19 @@ module ActiveRecord
84
83
  class ConnectionTimeoutError < ConnectionNotEstablished
85
84
  end
86
85
 
86
+ # Raised when a database connection pool is requested but
87
+ # has not been defined.
88
+ class ConnectionNotDefined < ConnectionNotEstablished
89
+ def initialize(message = nil, connection_name: nil, role: nil, shard: nil)
90
+ super(message)
91
+ @connection_name = connection_name
92
+ @role = role
93
+ @shard = shard
94
+ end
95
+
96
+ attr_reader :connection_name, :role, :shard
97
+ end
98
+
87
99
  # Raised when connection to the database could not been established because it was not
88
100
  # able to connect to the host or when the authorization failed.
89
101
  class DatabaseConnectionError < ConnectionNotEstablished
@@ -280,6 +292,14 @@ module ActiveRecord
280
292
  class NotNullViolation < StatementInvalid
281
293
  end
282
294
 
295
+ # Raised when a record cannot be inserted or updated because it would violate a check constraint.
296
+ class CheckViolation < StatementInvalid
297
+ end
298
+
299
+ # Raised when a record cannot be inserted or updated because it would violate an exclusion constraint.
300
+ class ExclusionViolation < StatementInvalid
301
+ end
302
+
283
303
  # Raised when a record cannot be inserted or updated because a value too long for a column type.
284
304
  class ValueTooLong < StatementInvalid
285
305
  end
@@ -326,15 +346,15 @@ module ActiveRecord
326
346
  class << self
327
347
  def db_error(db_name)
328
348
  NoDatabaseError.new(<<~MSG)
329
- We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml.
349
+ Database not found: #{db_name}. Available database configurations can be found in config/database.yml.
330
350
 
331
351
  To resolve this error:
332
352
 
333
- - Did you not create the database, or did you delete it? To create the database, run:
353
+ - Create the database by running:
334
354
 
335
355
  bin/rails db:create
336
356
 
337
- - Has the database name changed? Verify that config/database.yml contains the correct database name.
357
+ - Verify that config/database.yml contains the correct database name.
338
358
  MSG
339
359
  end
340
360
  end
@@ -477,6 +497,7 @@ module ActiveRecord
477
497
  # end
478
498
  #
479
499
  # relation = Task.all
500
+ # relation.load
480
501
  # relation.loaded? # => true
481
502
  #
482
503
  # # Methods which try to mutate a loaded relation fail.
@@ -484,11 +505,6 @@ module ActiveRecord
484
505
  # relation.limit!(5) # => ActiveRecord::UnmodifiableRelation
485
506
  class UnmodifiableRelation < ActiveRecordError
486
507
  end
487
- deprecate_constant(
488
- :ImmutableRelation,
489
- "ActiveRecord::UnmodifiableRelation",
490
- deprecator: ActiveRecord.deprecator
491
- )
492
508
 
493
509
  # TransactionIsolationError will be raised under the following conditions:
494
510
  #
@@ -544,6 +560,11 @@ module ActiveRecord
544
560
  class Deadlocked < TransactionRollbackError
545
561
  end
546
562
 
563
+ # MissingRequiredOrderError is raised when a relation requires ordering but
564
+ # lacks any +order+ values in scope or any model order columns to use.
565
+ class MissingRequiredOrderError < ActiveRecordError
566
+ end
567
+
547
568
  # IrreversibleOrderError is raised when a relation's order is too complex for
548
569
  # +reverse_order+ to automatically reverse.
549
570
  class IrreversibleOrderError < ActiveRecordError
@@ -601,6 +622,9 @@ module ActiveRecord
601
622
  # the database version cannot be determined.
602
623
  class DatabaseVersionError < ActiveRecordError
603
624
  end
625
+
626
+ class DeprecatedAssociationError < ActiveRecordError
627
+ end
604
628
  end
605
629
 
606
630
  require "active_record/associations/errors"
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  # Executes the block with the collect flag enabled. Queries are collected
8
8
  # asynchronously by the subscriber and returned.
9
9
  def collecting_queries_for_explain # :nodoc:
10
- ExplainRegistry.collect = true
10
+ ExplainRegistry.start
11
11
  yield
12
12
  ExplainRegistry.queries
13
13
  ensure
@@ -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
  # This is a thread locals registry for EXPLAIN. For example
@@ -9,8 +8,53 @@ module ActiveRecord
9
8
  #
10
9
  # returns the collected queries local to the current thread.
11
10
  class ExplainRegistry # :nodoc:
11
+ class Subscriber
12
+ MUTEX = Mutex.new
13
+ @subscribed = false
14
+
15
+ class << self
16
+ def ensure_subscribed
17
+ return if @subscribed
18
+ MUTEX.synchronize do
19
+ return if @subscribed
20
+
21
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
22
+ @subscribed = true
23
+ end
24
+ end
25
+ end
26
+
27
+ def start(name, id, payload)
28
+ # unused
29
+ end
30
+
31
+ def finish(name, id, payload)
32
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
33
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
34
+ end
35
+ end
36
+
37
+ def silenced?(_name)
38
+ !ExplainRegistry.collect?
39
+ end
40
+
41
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
42
+ # our own EXPLAINs no matter how loopingly beautiful that would be.
43
+ #
44
+ # On the other hand, we want to monitor the performance of our real database
45
+ # queries, not the performance of the access to the query cache.
46
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
47
+ EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i
48
+ def ignore_payload?(payload)
49
+ payload[:exception] ||
50
+ payload[:cached] ||
51
+ IGNORED_PAYLOADS.include?(payload[:name]) ||
52
+ !payload[:sql].match?(EXPLAINED_SQLS)
53
+ end
54
+ end
55
+
12
56
  class << self
13
- delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
57
+ delegate :start, :reset, :collect, :collect=, :collect?, :queries, to: :instance
14
58
 
15
59
  private
16
60
  def instance
@@ -25,6 +69,11 @@ module ActiveRecord
25
69
  reset
26
70
  end
27
71
 
72
+ def start
73
+ Subscriber.ensure_subscribed
74
+ @collect = true
75
+ end
76
+
28
77
  def collect?
29
78
  @collect
30
79
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class FilterAttributeHandler # :nodoc:
5
+ class << self
6
+ def on_sensitive_attribute_declared(&block)
7
+ @sensitive_attribute_declaration_listeners ||= Concurrent::Array.new
8
+ @sensitive_attribute_declaration_listeners << block
9
+ end
10
+
11
+ def sensitive_attribute_was_declared(klass, list)
12
+ @sensitive_attribute_declaration_listeners&.each do |block|
13
+ block.call(klass, list)
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ @attributes_by_class = Concurrent::Map.new
21
+ @collecting = true
22
+ end
23
+
24
+ def enable
25
+ install_collecting_hook
26
+
27
+ apply_collected_attributes
28
+ @collecting = false
29
+ end
30
+
31
+ private
32
+ attr_reader :app
33
+
34
+ def install_collecting_hook
35
+ self.class.on_sensitive_attribute_declared do |klass, list|
36
+ attribute_was_declared(klass, list)
37
+ end
38
+ end
39
+
40
+ def attribute_was_declared(klass, list)
41
+ if collecting?
42
+ collect_for_later(klass, list)
43
+ else
44
+ apply_filter(klass, list)
45
+ end
46
+ end
47
+
48
+ def apply_collected_attributes
49
+ @attributes_by_class.each do |klass, list|
50
+ apply_filter(klass, list)
51
+ end
52
+ end
53
+
54
+ def collecting?
55
+ @collecting
56
+ end
57
+
58
+ def collect_for_later(klass, list)
59
+ @attributes_by_class[klass] ||= Concurrent::Array.new
60
+ @attributes_by_class[klass] += list
61
+ end
62
+
63
+ def apply_filter(klass, list)
64
+ list.each do |attribute|
65
+ next if klass.abstract_class? || klass == Base
66
+
67
+ klass_name = klass.name ? klass.model_name.element : nil
68
+ filter = [klass_name, attribute.to_s].compact.join(".")
69
+ app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "erb"
4
4
  require "yaml"
5
- require "zlib"
6
- require "set"
7
5
  require "active_support/dependencies"
8
6
  require "active_support/core_ext/digest/uuid"
9
7
  require "active_record/test_fixtures"
@@ -244,10 +242,10 @@ module ActiveRecord
244
242
  # and one for the humans. Why don't we generate the primary key instead?
245
243
  # Hashing each fixture's label yields a consistent ID:
246
244
  #
247
- # george: # generated id: 503576764
245
+ # george: # generated id: 380982691
248
246
  # name: George the Monkey
249
247
  #
250
- # reginald: # generated id: 324201669
248
+ # reginald: # generated id: 41001176
251
249
  # name: Reginald the Pirate
252
250
  #
253
251
  # Active Record looks at the fixture's model class, discovers the correct
@@ -100,17 +100,23 @@ module ActiveRecord
100
100
  def execute_or_skip
101
101
  return unless pending?
102
102
 
103
- @pool.with_connection do |connection|
104
- return unless @mutex.try_lock
105
- begin
106
- if pending?
107
- @event_buffer = EventBuffer.new(self, @instrumenter)
108
- connection.with_instrumenter(@event_buffer) do
103
+ @session.synchronize do
104
+ return unless pending?
105
+
106
+ @pool.with_connection do |connection|
107
+ return unless @mutex.try_lock
108
+ previous_instrumenter = ActiveSupport::IsolatedExecutionState[:active_record_instrumenter]
109
+ begin
110
+ if pending?
111
+ @event_buffer = EventBuffer.new(self, @instrumenter)
112
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
113
+
109
114
  execute_query(connection, async: true)
110
115
  end
116
+ ensure
117
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = previous_instrumenter
118
+ @mutex.unlock
111
119
  end
112
- ensure
113
- @mutex.unlock
114
120
  end
115
121
  end
116
122
  end
@@ -163,7 +169,7 @@ module ActiveRecord
163
169
  end
164
170
 
165
171
  def exec_query(connection, *args, **kwargs)
166
- connection.internal_exec_query(*args, **kwargs)
172
+ connection.raw_exec_query(*args, **kwargs)
167
173
  end
168
174
 
169
175
  class SelectAll < FutureResult # :nodoc: