activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
9
9
  # on any database connection adapter. For example:
10
10
  #
11
- # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
11
+ # result = ActiveRecord::Base.lease_connection.exec_query('SELECT id, title, body FROM posts')
12
12
  # result # => #<ActiveRecord::Result:0xdeadbeef>
13
13
  #
14
14
  # # Get the column names of the result:
@@ -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']
@@ -36,21 +41,79 @@ module ActiveRecord
36
41
  class Result
37
42
  include Enumerable
38
43
 
39
- attr_reader :columns, :rows, :column_types
44
+ class IndexedRow
45
+ def initialize(column_indexes, row)
46
+ @column_indexes = column_indexes
47
+ @row = row
48
+ end
49
+
50
+ def size
51
+ @column_indexes.size
52
+ end
53
+ alias_method :length, :size
54
+
55
+ def each_key(&block)
56
+ @column_indexes.each_key(&block)
57
+ end
58
+
59
+ def keys
60
+ @column_indexes.keys
61
+ end
62
+
63
+ def ==(other)
64
+ if other.is_a?(Hash)
65
+ to_hash == other
66
+ else
67
+ super
68
+ end
69
+ end
70
+
71
+ def key?(column)
72
+ @column_indexes.key?(column)
73
+ end
40
74
 
41
- def self.empty(async: false) # :nodoc:
75
+ def fetch(column)
76
+ if index = @column_indexes[column]
77
+ @row[index]
78
+ elsif block_given?
79
+ yield
80
+ else
81
+ raise KeyError, "key not found: #{column.inspect}"
82
+ end
83
+ end
84
+
85
+ def [](column)
86
+ if index = @column_indexes[column]
87
+ @row[index]
88
+ end
89
+ end
90
+
91
+ def to_h
92
+ @column_indexes.transform_values { |index| @row[index] }
93
+ end
94
+ alias_method :to_hash, :to_h
95
+ end
96
+
97
+ attr_reader :columns, :rows, :affected_rows
98
+
99
+ def self.empty(async: false, affected_rows: nil) # :nodoc:
42
100
  if async
43
- EMPTY_ASYNC
101
+ FutureResult.wrap(new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows)).freeze
44
102
  else
45
- EMPTY
103
+ new(EMPTY_ARRAY, EMPTY_ARRAY, EMPTY_HASH, affected_rows: affected_rows).freeze
46
104
  end
47
105
  end
48
106
 
49
- def initialize(columns, rows, column_types = {})
50
- @columns = columns
107
+ def initialize(columns, rows, column_types = nil, affected_rows: nil)
108
+ # We freeze the strings to prevent them getting duped when
109
+ # used as keys in ActiveRecord::Base's @attributes hash
110
+ @columns = columns.each(&:-@).freeze
51
111
  @rows = rows
52
112
  @hash_rows = nil
53
- @column_types = column_types
113
+ @column_types = column_types.freeze
114
+ @types_hash = nil
115
+ @column_indexes = nil
116
+ @affected_rows = affected_rows
54
117
  end
55
118
 
56
119
  # Returns true if this result set includes the column named +name+
@@ -64,7 +127,9 @@ module ActiveRecord
64
127
  end
65
128
 
66
129
  # Calls the given block once for each element in row collection, passing
67
- # row as parameter.
130
+ # row as parameter. Each row is a Hash-like, read only object.
131
+ #
132
+ # To get real hashes, use +.to_a.each+.
68
133
  #
69
134
  # Returns an +Enumerator+ if no block is given.
70
135
  def each(&block)
@@ -96,6 +161,24 @@ module ActiveRecord
96
161
  n ? hash_rows.last(n) : hash_rows.last
97
162
  end
98
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
+
99
182
  def result # :nodoc:
100
183
  self
101
184
  end
@@ -104,7 +187,7 @@ module ActiveRecord
104
187
  self
105
188
  end
106
189
 
107
- def cast_values(type_overrides = {}) # :nodoc:
190
+ def cast_values(type_overrides = nil) # :nodoc:
108
191
  if columns.one?
109
192
  # Separated to avoid allocating an array per row
110
193
 
@@ -131,71 +214,60 @@ module ActiveRecord
131
214
  end
132
215
 
133
216
  def initialize_copy(other)
134
- @columns = columns.dup
135
- @rows = rows.dup
136
- @column_types = column_types.dup
217
+ @rows = rows.dup
137
218
  @hash_rows = nil
138
219
  end
139
220
 
140
221
  def freeze # :nodoc:
141
222
  hash_rows.freeze
223
+ indexed_rows
224
+ column_types
142
225
  super
143
226
  end
144
227
 
228
+ def column_indexes # :nodoc:
229
+ @column_indexes ||= begin
230
+ index = 0
231
+ hash = {}
232
+ length = columns.length
233
+ while index < length
234
+ hash[columns[index]] = index
235
+ index += 1
236
+ end
237
+ hash.freeze
238
+ end
239
+ end
240
+
241
+ def indexed_rows # :nodoc:
242
+ @indexed_rows ||= begin
243
+ columns = column_indexes
244
+ @rows.map { |row| IndexedRow.new(columns, row) }.freeze
245
+ end
246
+ end
247
+
145
248
  private
146
249
  def column_type(name, index, type_overrides)
147
- type_overrides.fetch(name) do
148
- column_types.fetch(index) do
149
- column_types.fetch(name, Type.default_value)
250
+ if type_overrides
251
+ type_overrides.fetch(name) do
252
+ column_type(name, index, nil)
150
253
  end
254
+ elsif @column_types
255
+ @column_types[index] || Type.default_value
256
+ else
257
+ Type.default_value
151
258
  end
152
259
  end
153
260
 
154
261
  def hash_rows
155
- @hash_rows ||=
156
- begin
157
- # We freeze the strings to prevent them getting duped when
158
- # used as keys in ActiveRecord::Base's @attributes hash
159
- columns = @columns.map(&:-@)
160
- length = columns.length
161
- template = nil
162
-
163
- @rows.map { |row|
164
- if template
165
- # We use transform_values to build subsequent rows from the
166
- # hash of the first row. This is faster because we avoid any
167
- # reallocs and in Ruby 2.7+ avoid hashing entirely.
168
- index = -1
169
- template.transform_values do
170
- row[index += 1]
171
- end
172
- else
173
- # In the past we used Hash[columns.zip(row)]
174
- # though elegant, the verbose way is much more efficient
175
- # both time and memory wise cause it avoids a big array allocation
176
- # this method is called a lot and needs to be micro optimised
177
- hash = {}
178
-
179
- index = 0
180
- while index < length
181
- hash[columns[index]] = row[index]
182
- index += 1
183
- end
184
-
185
- # It's possible to select the same column twice, in which case
186
- # we can't use a template
187
- template = hash if hash.length == length
188
-
189
- hash
190
- end
191
- }
192
- end
262
+ # We use transform_values to rows.
263
+ # This is faster because we avoid any reallocs and avoid hashing entirely.
264
+ @hash_rows ||= @rows.map do |row|
265
+ column_indexes.transform_values { |index| row[index] }
266
+ end
193
267
  end
194
268
 
195
- EMPTY = new([].freeze, [].freeze, {}.freeze).freeze
196
- private_constant :EMPTY
197
-
198
- EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
199
- private_constant :EMPTY_ASYNC
269
+ EMPTY_ARRAY = [].freeze
270
+ EMPTY_HASH = {}.freeze
271
+ private_constant :EMPTY_ARRAY, :EMPTY_HASH
200
272
  end
201
273
  end
@@ -3,41 +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
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
11
26
 
12
- def sql_runtime
13
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
27
+ public alias_method :reset, :initialize
14
28
  end
15
29
 
16
- def sql_runtime=(runtime)
17
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
30
+ extend self
31
+
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
+ )
18
40
  end
19
41
 
20
- def async_sql_runtime
21
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
42
+ def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
43
+ stats = self.stats
44
+
45
+ unless query_name == "TRANSACTION" || query_name == "SCHEMA"
46
+ stats.queries_count += 1
47
+ stats.cached_queries_count += 1 if cached
48
+ end
49
+
50
+ if async
51
+ stats.async_sql_runtime += (runtime - lock_wait)
52
+ end
53
+ stats.sql_runtime += runtime
22
54
  end
23
55
 
24
- def async_sql_runtime=(runtime)
25
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
56
+ def stats
57
+ ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
26
58
  end
27
59
 
28
60
  def reset
29
- rt, self.sql_runtime = sql_runtime, 0.0
30
- self.async_sql_runtime = 0.0
31
- rt
61
+ stats.reset
32
62
  end
33
63
  end
34
64
  end
35
65
 
36
- ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
37
- runtime = (finish - start) * 1_000.0
38
-
39
- if payload[:async]
40
- ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
41
- end
42
- ActiveRecord::RuntimeRegistry.sql_runtime += runtime
43
- end
66
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
18
  # # => "name='foo''bar' and group_id='4'"
19
19
  #
20
- # This method will NOT sanitize a SQL string since it won't contain
20
+ # This method will NOT sanitize an SQL string since it won't contain
21
21
  # any conditions in it and will return the string as is.
22
22
  #
23
23
  # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
@@ -52,7 +52,7 @@ module ActiveRecord
52
52
  # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
53
53
  # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
54
54
  #
55
- # This method will NOT sanitize a SQL string since it won't contain
55
+ # This method will NOT sanitize an SQL string since it won't contain
56
56
  # any conditions in it and will return the string as is.
57
57
  #
58
58
  # sanitize_sql_for_assignment("name=NULL and group_id='4'")
@@ -85,7 +85,7 @@ module ActiveRecord
85
85
  if condition.is_a?(Array) && condition.first.to_s.include?("?")
86
86
  disallow_raw_sql!(
87
87
  [condition.first],
88
- permit: connection.column_name_with_order_matcher
88
+ permit: adapter_class.column_name_with_order_matcher
89
89
  )
90
90
 
91
91
  # Ensure we aren't dealing with a subclass of String that might
@@ -105,12 +105,13 @@ module ActiveRecord
105
105
  # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
106
106
  # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
107
107
  def sanitize_sql_hash_for_assignment(attrs, table)
108
- c = connection
109
- attrs.map do |attr, value|
110
- type = type_for_attribute(attr)
111
- value = type.serialize(type.cast(value))
112
- "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
113
- end.join(", ")
108
+ with_connection do |c|
109
+ attrs.map do |attr, value|
110
+ type = type_for_attribute(attr)
111
+ value = type.serialize(type.cast(value))
112
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
113
+ end.join(", ")
114
+ end
114
115
  end
115
116
 
116
117
  # Sanitizes a +string+ so that it is safe to use within an SQL
@@ -160,20 +161,28 @@ module ActiveRecord
160
161
  #
161
162
  # sanitize_sql_array(["role = ?", 0])
162
163
  # # => "role = '0'"
164
+ #
165
+ # Before using this method, please consider if Arel.sql would be better for your use-case
163
166
  def sanitize_sql_array(ary)
164
167
  statement, *values = ary
165
168
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
166
- replace_named_bind_variables(statement, values.first)
169
+ with_connection do |c|
170
+ replace_named_bind_variables(c, statement, values.first)
171
+ end
167
172
  elsif statement.include?("?")
168
- replace_bind_variables(statement, values)
173
+ with_connection do |c|
174
+ replace_bind_variables(c, statement, values)
175
+ end
169
176
  elsif statement.blank?
170
177
  statement
171
178
  else
172
- statement % values.collect { |value| connection.quote_string(value.to_s) }
179
+ with_connection do |c|
180
+ statement % values.collect { |value| c.quote_string(value.to_s) }
181
+ end
173
182
  end
174
183
  end
175
184
 
176
- def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
185
+ def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
177
186
  unexpected = nil
178
187
  args.each do |arg|
179
188
  next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
@@ -193,48 +202,47 @@ module ActiveRecord
193
202
  end
194
203
 
195
204
  private
196
- def replace_bind_variables(statement, values)
205
+ def replace_bind_variables(connection, statement, values)
197
206
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
198
207
  bound = values.dup
199
- c = connection
200
208
  statement.gsub(/\?/) do
201
- replace_bind_variable(bound.shift, c)
209
+ replace_bind_variable(connection, bound.shift)
202
210
  end
203
211
  end
204
212
 
205
- def replace_bind_variable(value, c = connection)
213
+ def replace_bind_variable(connection, value)
206
214
  if ActiveRecord::Relation === value
207
215
  value.to_sql
208
216
  else
209
- quote_bound_value(value, c)
217
+ quote_bound_value(connection, value)
210
218
  end
211
219
  end
212
220
 
213
- def replace_named_bind_variables(statement, bind_vars)
221
+ def replace_named_bind_variables(connection, statement, bind_vars)
214
222
  statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
215
- if $1 == ":" # skip postgresql casts
223
+ if $1 == ":" # skip PostgreSQL casts
216
224
  match # return the whole match
217
225
  elsif $1 == "\\" # escaped literal colon
218
226
  match[1..-1] # return match with escaping backlash char removed
219
227
  elsif bind_vars.include?(match = $2.to_sym)
220
- replace_bind_variable(bind_vars[match])
228
+ replace_bind_variable(connection, bind_vars[match])
221
229
  else
222
230
  raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
223
231
  end
224
232
  end
225
233
  end
226
234
 
227
- def quote_bound_value(value, c = connection)
235
+ def quote_bound_value(connection, value)
228
236
  if value.respond_to?(:map) && !value.acts_like?(:string)
229
237
  values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
230
238
  if values.empty?
231
- c.quote(c.cast_bound_value(nil))
239
+ connection.quote(connection.cast_bound_value(nil))
232
240
  else
233
- values.map! { |v| c.quote(c.cast_bound_value(v)) }.join(",")
241
+ values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
234
242
  end
235
243
  else
236
244
  value = value.id_for_database if value.respond_to?(:id_for_database)
237
- c.quote(c.cast_bound_value(value))
245
+ connection.quote(connection.cast_bound_value(value))
238
246
  end
239
247
  end
240
248
 
@@ -52,14 +52,16 @@ module ActiveRecord
52
52
  end
53
53
 
54
54
  def define(info, &block) # :nodoc:
55
- instance_eval(&block)
55
+ connection_pool.with_connection do |connection|
56
+ instance_eval(&block)
56
57
 
57
- connection.schema_migration.create_table
58
- if info[:version].present?
59
- connection.assume_migrated_upto_version(info[:version])
60
- end
58
+ connection_pool.schema_migration.create_table
59
+ if info[:version].present?
60
+ connection.assume_migrated_upto_version(info[:version])
61
+ end
61
62
 
62
- connection.internal_metadata.create_table_and_set_flags(connection.migration_context.current_environment)
63
+ connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
64
+ end
63
65
  end
64
66
  end
65
67