activerecord 5.2.8.1 → 6.0.6.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (294) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +938 -573
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +5 -3
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/advisory_lock_base.rb +18 -0
  7. data/lib/active_record/aggregations.rb +4 -3
  8. data/lib/active_record/association_relation.rb +10 -8
  9. data/lib/active_record/associations/alias_tracker.rb +0 -1
  10. data/lib/active_record/associations/association.rb +55 -19
  11. data/lib/active_record/associations/association_scope.rb +11 -7
  12. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  14. data/lib/active_record/associations/builder/association.rb +14 -18
  15. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  16. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -40
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +35 -1
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +19 -23
  22. data/lib/active_record/associations/collection_proxy.rb +14 -17
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +2 -11
  25. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  26. data/lib/active_record/associations/has_one_association.rb +28 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  28. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  29. data/lib/active_record/associations/join_dependency/join_part.rb +4 -4
  30. data/lib/active_record/associations/join_dependency.rb +47 -30
  31. data/lib/active_record/associations/preloader/association.rb +61 -41
  32. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  33. data/lib/active_record/associations/preloader.rb +44 -33
  34. data/lib/active_record/associations/singular_association.rb +2 -16
  35. data/lib/active_record/associations/through_association.rb +1 -1
  36. data/lib/active_record/associations.rb +21 -16
  37. data/lib/active_record/attribute_assignment.rb +7 -11
  38. data/lib/active_record/attribute_decorators.rb +0 -2
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -2
  40. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  41. data/lib/active_record/attribute_methods/primary_key.rb +15 -24
  42. data/lib/active_record/attribute_methods/query.rb +2 -3
  43. data/lib/active_record/attribute_methods/read.rb +15 -54
  44. data/lib/active_record/attribute_methods/serialization.rb +1 -2
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -3
  46. data/lib/active_record/attribute_methods/write.rb +17 -25
  47. data/lib/active_record/attribute_methods.rb +28 -100
  48. data/lib/active_record/attributes.rb +13 -1
  49. data/lib/active_record/autosave_association.rb +12 -14
  50. data/lib/active_record/base.rb +2 -3
  51. data/lib/active_record/callbacks.rb +6 -21
  52. data/lib/active_record/coders/yaml_column.rb +15 -6
  53. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -18
  54. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  55. data/lib/active_record/connection_adapters/abstract/database_statements.rb +102 -124
  56. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -9
  57. data/lib/active_record/connection_adapters/abstract/quoting.rb +77 -17
  58. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +20 -14
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +105 -72
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +175 -79
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -57
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -43
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -217
  65. data/lib/active_record/connection_adapters/column.rb +17 -13
  66. data/lib/active_record/connection_adapters/connection_specification.rb +54 -45
  67. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  68. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/database_statements.rb +70 -14
  70. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +0 -1
  71. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +4 -6
  73. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  74. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  75. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  76. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -10
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  89. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -3
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +63 -75
  98. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +168 -75
  101. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  102. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -12
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +137 -147
  107. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  108. data/lib/active_record/connection_handling.rb +139 -26
  109. data/lib/active_record/core.rb +108 -67
  110. data/lib/active_record/counter_cache.rb +8 -30
  111. data/lib/active_record/database_configurations/database_config.rb +37 -0
  112. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  113. data/lib/active_record/database_configurations/url_config.rb +78 -0
  114. data/lib/active_record/database_configurations.rb +233 -0
  115. data/lib/active_record/dynamic_matchers.rb +3 -4
  116. data/lib/active_record/enum.rb +44 -7
  117. data/lib/active_record/errors.rb +15 -7
  118. data/lib/active_record/explain.rb +1 -2
  119. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  120. data/lib/active_record/fixture_set/render_context.rb +17 -0
  121. data/lib/active_record/fixture_set/table_row.rb +152 -0
  122. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  123. data/lib/active_record/fixtures.rb +144 -474
  124. data/lib/active_record/gem_version.rb +3 -3
  125. data/lib/active_record/inheritance.rb +13 -6
  126. data/lib/active_record/insert_all.rb +179 -0
  127. data/lib/active_record/integration.rb +68 -16
  128. data/lib/active_record/internal_metadata.rb +11 -3
  129. data/lib/active_record/locking/optimistic.rb +14 -7
  130. data/lib/active_record/locking/pessimistic.rb +3 -3
  131. data/lib/active_record/log_subscriber.rb +8 -27
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
  134. data/lib/active_record/middleware/database_selector.rb +74 -0
  135. data/lib/active_record/migration/command_recorder.rb +54 -22
  136. data/lib/active_record/migration/compatibility.rb +79 -52
  137. data/lib/active_record/migration/join_table.rb +0 -1
  138. data/lib/active_record/migration.rb +104 -85
  139. data/lib/active_record/model_schema.rb +62 -11
  140. data/lib/active_record/nested_attributes.rb +2 -4
  141. data/lib/active_record/no_touching.rb +9 -2
  142. data/lib/active_record/null_relation.rb +0 -1
  143. data/lib/active_record/persistence.rb +232 -29
  144. data/lib/active_record/query_cache.rb +11 -4
  145. data/lib/active_record/querying.rb +33 -21
  146. data/lib/active_record/railtie.rb +80 -61
  147. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  148. data/lib/active_record/railties/controller_runtime.rb +30 -35
  149. data/lib/active_record/railties/databases.rake +199 -46
  150. data/lib/active_record/reflection.rb +51 -51
  151. data/lib/active_record/relation/batches.rb +13 -11
  152. data/lib/active_record/relation/calculations.rb +55 -49
  153. data/lib/active_record/relation/delegation.rb +35 -50
  154. data/lib/active_record/relation/finder_methods.rb +23 -28
  155. data/lib/active_record/relation/from_clause.rb +4 -0
  156. data/lib/active_record/relation/merger.rb +12 -17
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  158. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  159. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  160. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  161. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  162. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  163. data/lib/active_record/relation/predicate_builder.rb +5 -11
  164. data/lib/active_record/relation/query_attribute.rb +13 -8
  165. data/lib/active_record/relation/query_methods.rb +234 -69
  166. data/lib/active_record/relation/spawn_methods.rb +1 -2
  167. data/lib/active_record/relation/where_clause.rb +14 -11
  168. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  169. data/lib/active_record/relation.rb +326 -81
  170. data/lib/active_record/result.rb +30 -12
  171. data/lib/active_record/sanitization.rb +32 -40
  172. data/lib/active_record/schema.rb +2 -11
  173. data/lib/active_record/schema_dumper.rb +22 -7
  174. data/lib/active_record/schema_migration.rb +6 -2
  175. data/lib/active_record/scoping/default.rb +4 -6
  176. data/lib/active_record/scoping/named.rb +25 -16
  177. data/lib/active_record/scoping.rb +8 -9
  178. data/lib/active_record/statement_cache.rb +30 -3
  179. data/lib/active_record/store.rb +87 -8
  180. data/lib/active_record/suppressor.rb +2 -2
  181. data/lib/active_record/table_metadata.rb +23 -15
  182. data/lib/active_record/tasks/database_tasks.rb +194 -25
  183. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -6
  184. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -8
  185. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -9
  186. data/lib/active_record/test_databases.rb +23 -0
  187. data/lib/active_record/test_fixtures.rb +243 -0
  188. data/lib/active_record/timestamp.rb +39 -26
  189. data/lib/active_record/touch_later.rb +5 -4
  190. data/lib/active_record/transactions.rb +64 -73
  191. data/lib/active_record/translation.rb +1 -1
  192. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  193. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  194. data/lib/active_record/type/serialized.rb +0 -1
  195. data/lib/active_record/type/time.rb +10 -0
  196. data/lib/active_record/type/type_map.rb +0 -1
  197. data/lib/active_record/type/unsigned_integer.rb +0 -1
  198. data/lib/active_record/type.rb +3 -5
  199. data/lib/active_record/type_caster/connection.rb +15 -14
  200. data/lib/active_record/type_caster/map.rb +1 -4
  201. data/lib/active_record/validations/associated.rb +0 -1
  202. data/lib/active_record/validations/uniqueness.rb +15 -27
  203. data/lib/active_record/validations.rb +3 -3
  204. data/lib/active_record.rb +10 -2
  205. data/lib/arel/alias_predication.rb +9 -0
  206. data/lib/arel/attributes/attribute.rb +37 -0
  207. data/lib/arel/attributes.rb +22 -0
  208. data/lib/arel/collectors/bind.rb +24 -0
  209. data/lib/arel/collectors/composite.rb +31 -0
  210. data/lib/arel/collectors/plain_string.rb +20 -0
  211. data/lib/arel/collectors/sql_string.rb +20 -0
  212. data/lib/arel/collectors/substitute_binds.rb +28 -0
  213. data/lib/arel/crud.rb +42 -0
  214. data/lib/arel/delete_manager.rb +18 -0
  215. data/lib/arel/errors.rb +9 -0
  216. data/lib/arel/expressions.rb +29 -0
  217. data/lib/arel/factory_methods.rb +49 -0
  218. data/lib/arel/insert_manager.rb +49 -0
  219. data/lib/arel/math.rb +45 -0
  220. data/lib/arel/nodes/and.rb +32 -0
  221. data/lib/arel/nodes/ascending.rb +23 -0
  222. data/lib/arel/nodes/binary.rb +52 -0
  223. data/lib/arel/nodes/bind_param.rb +36 -0
  224. data/lib/arel/nodes/case.rb +55 -0
  225. data/lib/arel/nodes/casted.rb +50 -0
  226. data/lib/arel/nodes/comment.rb +29 -0
  227. data/lib/arel/nodes/count.rb +12 -0
  228. data/lib/arel/nodes/delete_statement.rb +45 -0
  229. data/lib/arel/nodes/descending.rb +23 -0
  230. data/lib/arel/nodes/equality.rb +18 -0
  231. data/lib/arel/nodes/extract.rb +24 -0
  232. data/lib/arel/nodes/false.rb +16 -0
  233. data/lib/arel/nodes/full_outer_join.rb +8 -0
  234. data/lib/arel/nodes/function.rb +44 -0
  235. data/lib/arel/nodes/grouping.rb +8 -0
  236. data/lib/arel/nodes/in.rb +8 -0
  237. data/lib/arel/nodes/infix_operation.rb +80 -0
  238. data/lib/arel/nodes/inner_join.rb +8 -0
  239. data/lib/arel/nodes/insert_statement.rb +37 -0
  240. data/lib/arel/nodes/join_source.rb +20 -0
  241. data/lib/arel/nodes/matches.rb +18 -0
  242. data/lib/arel/nodes/named_function.rb +23 -0
  243. data/lib/arel/nodes/node.rb +50 -0
  244. data/lib/arel/nodes/node_expression.rb +13 -0
  245. data/lib/arel/nodes/outer_join.rb +8 -0
  246. data/lib/arel/nodes/over.rb +15 -0
  247. data/lib/arel/nodes/regexp.rb +16 -0
  248. data/lib/arel/nodes/right_outer_join.rb +8 -0
  249. data/lib/arel/nodes/select_core.rb +67 -0
  250. data/lib/arel/nodes/select_statement.rb +41 -0
  251. data/lib/arel/nodes/sql_literal.rb +16 -0
  252. data/lib/arel/nodes/string_join.rb +11 -0
  253. data/lib/arel/nodes/table_alias.rb +27 -0
  254. data/lib/arel/nodes/terminal.rb +16 -0
  255. data/lib/arel/nodes/true.rb +16 -0
  256. data/lib/arel/nodes/unary.rb +45 -0
  257. data/lib/arel/nodes/unary_operation.rb +20 -0
  258. data/lib/arel/nodes/unqualified_column.rb +22 -0
  259. data/lib/arel/nodes/update_statement.rb +41 -0
  260. data/lib/arel/nodes/values_list.rb +9 -0
  261. data/lib/arel/nodes/window.rb +126 -0
  262. data/lib/arel/nodes/with.rb +11 -0
  263. data/lib/arel/nodes.rb +68 -0
  264. data/lib/arel/order_predications.rb +13 -0
  265. data/lib/arel/predications.rb +256 -0
  266. data/lib/arel/select_manager.rb +271 -0
  267. data/lib/arel/table.rb +110 -0
  268. data/lib/arel/tree_manager.rb +72 -0
  269. data/lib/arel/update_manager.rb +34 -0
  270. data/lib/arel/visitors/depth_first.rb +203 -0
  271. data/lib/arel/visitors/dot.rb +296 -0
  272. data/lib/arel/visitors/ibm_db.rb +34 -0
  273. data/lib/arel/visitors/informix.rb +62 -0
  274. data/lib/arel/visitors/mssql.rb +156 -0
  275. data/lib/arel/visitors/mysql.rb +83 -0
  276. data/lib/arel/visitors/oracle.rb +158 -0
  277. data/lib/arel/visitors/oracle12.rb +65 -0
  278. data/lib/arel/visitors/postgresql.rb +109 -0
  279. data/lib/arel/visitors/sqlite.rb +38 -0
  280. data/lib/arel/visitors/to_sql.rb +888 -0
  281. data/lib/arel/visitors/visitor.rb +45 -0
  282. data/lib/arel/visitors/where_sql.rb +22 -0
  283. data/lib/arel/visitors.rb +20 -0
  284. data/lib/arel/window_predications.rb +9 -0
  285. data/lib/arel.rb +62 -0
  286. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  287. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  288. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  289. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  290. data/lib/rails/generators/active_record/migration.rb +14 -2
  291. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  292. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  293. metadata +113 -26
  294. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -7,9 +7,9 @@ module ActiveRecord
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 8
10
+ MAJOR = 6
11
+ MINOR = 0
12
+ TINY = 6
13
13
  PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -55,7 +55,11 @@ module ActiveRecord
55
55
  if has_attribute?(inheritance_column)
56
56
  subclass = subclass_from_attributes(attributes)
57
57
 
58
- if subclass.nil? && base_class == self
58
+ if subclass.nil? && scope_attributes = current_scope&.scope_for_create
59
+ subclass = subclass_from_attributes(scope_attributes)
60
+ end
61
+
62
+ if subclass.nil? && base_class?
59
63
  subclass = subclass_from_attributes(column_defaults)
60
64
  end
61
65
  end
@@ -104,6 +108,12 @@ module ActiveRecord
104
108
  end
105
109
  end
106
110
 
111
+ # Returns whether the class is a base class.
112
+ # See #base_class for more information.
113
+ def base_class?
114
+ base_class == self
115
+ end
116
+
107
117
  # Set this to +true+ if this is an abstract class (see
108
118
  # <tt>abstract_class?</tt>).
109
119
  # If you are using inheritance with Active Record and don't want a class
@@ -166,11 +176,10 @@ module ActiveRecord
166
176
  end
167
177
 
168
178
  protected
169
-
170
179
  # Returns the class type of the record using the current module as a prefix. So descendants of
171
180
  # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
172
181
  def compute_type(type_name)
173
- if type_name.start_with?("::".freeze)
182
+ if type_name.start_with?("::")
174
183
  # If the type is prefixed with a scope operator then we assume that
175
184
  # the type_name is an absolute reference.
176
185
  ActiveSupport::Dependencies.constantize(type_name)
@@ -198,7 +207,6 @@ module ActiveRecord
198
207
  end
199
208
 
200
209
  private
201
-
202
210
  # Called by +instantiate+ to decide which class to use for a new
203
211
  # record instance. For single-table inheritance, we check the record
204
212
  # for a +type+ column and return the corresponding class.
@@ -239,7 +247,7 @@ module ActiveRecord
239
247
  sti_column = arel_attribute(inheritance_column, table)
240
248
  sti_names = ([self] + descendants).map(&:sti_name)
241
249
 
242
- sti_column.in(sti_names)
250
+ predicate_builder.build(sti_column, sti_names)
243
251
  end
244
252
 
245
253
  # Detect the subclass from the inheritance column of attrs. If the inheritance column value
@@ -262,7 +270,6 @@ module ActiveRecord
262
270
  end
263
271
 
264
272
  private
265
-
266
273
  def initialize_internals_callback
267
274
  super
268
275
  ensure_proper_type
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class InsertAll # :nodoc:
5
+ attr_reader :model, :connection, :inserts, :keys
6
+ attr_reader :on_duplicate, :returning, :unique_by
7
+
8
+ def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
9
+ raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
10
+
11
+ @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s).to_set
12
+ @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
13
+
14
+ @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
15
+ @returning = false if @returning == []
16
+
17
+ @unique_by = find_unique_index_for(unique_by) if unique_by
18
+ @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
19
+
20
+ ensure_valid_options_for_connection!
21
+ end
22
+
23
+ def execute
24
+ message = +"#{model} "
25
+ message << "Bulk " if inserts.many?
26
+ message << (on_duplicate == :update ? "Upsert" : "Insert")
27
+ connection.exec_insert_all to_sql, message
28
+ end
29
+
30
+ def updatable_columns
31
+ keys - readonly_columns - unique_by_columns
32
+ end
33
+
34
+ def primary_keys
35
+ Array(model.primary_key)
36
+ end
37
+
38
+
39
+ def skip_duplicates?
40
+ on_duplicate == :skip
41
+ end
42
+
43
+ def update_duplicates?
44
+ on_duplicate == :update
45
+ end
46
+
47
+ def map_key_with_value
48
+ inserts.map do |attributes|
49
+ attributes = attributes.stringify_keys
50
+ verify_attributes(attributes)
51
+
52
+ keys.map do |key|
53
+ yield key, attributes[key]
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+ def find_unique_index_for(unique_by)
60
+ match = Array(unique_by).map(&:to_s)
61
+
62
+ if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
63
+ index
64
+ else
65
+ raise ArgumentError, "No unique index found for #{unique_by}"
66
+ end
67
+ end
68
+
69
+ def unique_indexes
70
+ connection.schema_cache.indexes(model.table_name).select(&:unique)
71
+ end
72
+
73
+
74
+ def ensure_valid_options_for_connection!
75
+ if returning && !connection.supports_insert_returning?
76
+ raise ArgumentError, "#{connection.class} does not support :returning"
77
+ end
78
+
79
+ if skip_duplicates? && !connection.supports_insert_on_duplicate_skip?
80
+ raise ArgumentError, "#{connection.class} does not support skipping duplicates"
81
+ end
82
+
83
+ if update_duplicates? && !connection.supports_insert_on_duplicate_update?
84
+ raise ArgumentError, "#{connection.class} does not support upsert"
85
+ end
86
+
87
+ if unique_by && !connection.supports_insert_conflict_target?
88
+ raise ArgumentError, "#{connection.class} does not support :unique_by"
89
+ end
90
+ end
91
+
92
+
93
+ def to_sql
94
+ connection.build_insert_sql(ActiveRecord::InsertAll::Builder.new(self))
95
+ end
96
+
97
+
98
+ def readonly_columns
99
+ primary_keys + model.readonly_attributes.to_a
100
+ end
101
+
102
+ def unique_by_columns
103
+ Array(unique_by&.columns)
104
+ end
105
+
106
+
107
+ def verify_attributes(attributes)
108
+ if keys != attributes.keys.to_set
109
+ raise ArgumentError, "All objects being inserted must have the same keys"
110
+ end
111
+ end
112
+
113
+ class Builder # :nodoc:
114
+ attr_reader :model
115
+
116
+ delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all
117
+
118
+ def initialize(insert_all)
119
+ @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
120
+ end
121
+
122
+ def into
123
+ "INTO #{model.quoted_table_name} (#{columns_list})"
124
+ end
125
+
126
+ def values_list
127
+ types = extract_types_from_columns_on(model.table_name, keys: keys)
128
+
129
+ values_list = insert_all.map_key_with_value do |key, value|
130
+ connection.with_yaml_fallback(types[key].serialize(value))
131
+ end
132
+
133
+ connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
134
+ end
135
+
136
+ def returning
137
+ format_columns(insert_all.returning) if insert_all.returning
138
+ end
139
+
140
+ def conflict_target
141
+ if index = insert_all.unique_by
142
+ sql = +"(#{format_columns(index.columns)})"
143
+ sql << " WHERE #{index.where}" if index.where
144
+ sql
145
+ elsif update_duplicates?
146
+ "(#{format_columns(insert_all.primary_keys)})"
147
+ end
148
+ end
149
+
150
+ def updatable_columns
151
+ quote_columns(insert_all.updatable_columns)
152
+ end
153
+
154
+ private
155
+ attr_reader :connection, :insert_all
156
+
157
+ def columns_list
158
+ format_columns(insert_all.keys)
159
+ end
160
+
161
+ def extract_types_from_columns_on(table_name, keys:)
162
+ columns = connection.schema_cache.columns_hash(table_name)
163
+
164
+ unknown_column = (keys - columns.keys).first
165
+ raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
166
+
167
+ keys.index_with { |key| model.type_for_attribute(key) }
168
+ end
169
+
170
+ def format_columns(columns)
171
+ columns.respond_to?(:map) ? quote_columns(columns).join(",") : columns
172
+ end
173
+
174
+ def quote_columns(columns)
175
+ columns.map(&connection.method(:quote_column_name))
176
+ end
177
+ end
178
+ end
179
+ end
@@ -20,8 +20,16 @@ module ActiveRecord
20
20
  # Indicates whether to use a stable #cache_key method that is accompanied
21
21
  # by a changing version in the #cache_version method.
22
22
  #
23
- # This is +false+, by default until Rails 6.0.
23
+ # This is +true+, by default on Rails 5.2 and above.
24
24
  class_attribute :cache_versioning, instance_writer: false, default: false
25
+
26
+ ##
27
+ # :singleton-method:
28
+ # Indicates whether to use a stable #cache_key method that is accompanied
29
+ # by a changing version in the #cache_version method on collections.
30
+ #
31
+ # This is +false+, by default until Rails 6.1.
32
+ class_attribute :collection_cache_versioning, instance_writer: false, default: false
25
33
  end
26
34
 
27
35
  # Returns a +String+, which Action Pack uses for constructing a URL to this
@@ -60,24 +68,15 @@ module ActiveRecord
60
68
  # the cache key will also include a version.
61
69
  #
62
70
  # Product.cache_versioning = false
63
- # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
64
- def cache_key(*timestamp_names)
71
+ # Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available)
72
+ def cache_key
65
73
  if new_record?
66
74
  "#{model_name.cache_key}/new"
67
75
  else
68
- if cache_version && timestamp_names.none?
76
+ if cache_version
69
77
  "#{model_name.cache_key}/#{id}"
70
78
  else
71
- timestamp = if timestamp_names.any?
72
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
73
- Specifying a timestamp name for #cache_key has been deprecated in favor of
74
- the explicit #cache_version method that can be overwritten.
75
- MSG
76
-
77
- max_updated_column_timestamp(timestamp_names)
78
- else
79
- max_updated_column_timestamp
80
- end
79
+ timestamp = max_updated_column_timestamp
81
80
 
82
81
  if timestamp
83
82
  timestamp = timestamp.utc.to_s(cache_timestamp_format)
@@ -96,8 +95,19 @@ module ActiveRecord
96
95
  # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
97
96
  # +false+ (which it is by default until Rails 6.0).
98
97
  def cache_version
99
- if cache_versioning && timestamp = try(:updated_at)
100
- timestamp.utc.to_s(:usec)
98
+ return unless cache_versioning
99
+
100
+ if has_attribute?("updated_at")
101
+ timestamp = updated_at_before_type_cast
102
+ if can_use_fast_cache_version?(timestamp)
103
+ raw_timestamp_to_cache_version(timestamp)
104
+ elsif timestamp = updated_at
105
+ timestamp.utc.to_s(cache_timestamp_format)
106
+ end
107
+ else
108
+ if self.class.has_attribute?("updated_at")
109
+ raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
110
+ end
101
111
  end
102
112
  end
103
113
 
@@ -150,6 +160,48 @@ module ActiveRecord
150
160
  end
151
161
  end
152
162
  end
163
+
164
+ def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
165
+ collection.send(:compute_cache_key, timestamp_column)
166
+ end
153
167
  end
168
+
169
+ private
170
+ # Detects if the value before type cast
171
+ # can be used to generate a cache_version.
172
+ #
173
+ # The fast cache version only works with a
174
+ # string value directly from the database.
175
+ #
176
+ # We also must check if the timestamp format has been changed
177
+ # or if the timezone is not set to UTC then
178
+ # we cannot apply our transformations correctly.
179
+ def can_use_fast_cache_version?(timestamp)
180
+ timestamp.is_a?(String) &&
181
+ cache_timestamp_format == :usec &&
182
+ default_timezone == :utc &&
183
+ !updated_at_came_from_user?
184
+ end
185
+
186
+ # Converts a raw database string to `:usec`
187
+ # format.
188
+ #
189
+ # Example:
190
+ #
191
+ # timestamp = "2018-10-15 20:02:15.266505"
192
+ # raw_timestamp_to_cache_version(timestamp)
193
+ # # => "20181015200215266505"
194
+ #
195
+ # PostgreSQL truncates trailing zeros,
196
+ # https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214
197
+ # to account for this we pad the output with zeros
198
+ def raw_timestamp_to_cache_version(timestamp)
199
+ key = timestamp.delete("- :.")
200
+ if key.length < 20
201
+ key.ljust(20, "0")
202
+ else
203
+ key
204
+ end
205
+ end
154
206
  end
155
207
  end
@@ -8,16 +8,20 @@ module ActiveRecord
8
8
  # as which environment migrations were run in.
9
9
  class InternalMetadata < ActiveRecord::Base # :nodoc:
10
10
  class << self
11
+ def _internal?
12
+ true
13
+ end
14
+
11
15
  def primary_key
12
16
  "key"
13
17
  end
14
18
 
15
19
  def table_name
16
- "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
20
+ "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
17
21
  end
18
22
 
19
23
  def []=(key, value)
20
- find_or_initialize_by(key: key).update_attributes!(value: value)
24
+ find_or_initialize_by(key: key).update!(value: value)
21
25
  end
22
26
 
23
27
  def [](key)
@@ -34,12 +38,16 @@ module ActiveRecord
34
38
  key_options = connection.internal_string_options_for_primary_key
35
39
 
36
40
  connection.create_table(table_name, id: false) do |t|
37
- t.string :key, key_options
41
+ t.string :key, **key_options
38
42
  t.string :value
39
43
  t.timestamps
40
44
  end
41
45
  end
42
46
  end
47
+
48
+ def drop_table
49
+ connection.drop_table table_name, if_exists: true
50
+ end
43
51
  end
44
52
  end
45
53
  end
@@ -60,8 +60,17 @@ module ActiveRecord
60
60
  self.class.locking_enabled?
61
61
  end
62
62
 
63
+ def increment!(*, **) #:nodoc:
64
+ super.tap do
65
+ if locking_enabled?
66
+ self[self.class.locking_column] += 1
67
+ clear_attribute_change(self.class.locking_column)
68
+ end
69
+ end
70
+ end
71
+
63
72
  private
64
- def _create_record(attribute_names = self.attribute_names, *)
73
+ def _create_record(attribute_names = self.attribute_names)
65
74
  if locking_enabled?
66
75
  # We always want to persist the locking version, even if we don't detect
67
76
  # a change from the default, since the database might have no default
@@ -71,9 +80,8 @@ module ActiveRecord
71
80
  end
72
81
 
73
82
  def _touch_row(attribute_names, time)
83
+ @_touch_attr_names << self.class.locking_column if locking_enabled?
74
84
  super
75
- ensure
76
- clear_attribute_change(self.class.locking_column) if locking_enabled?
77
85
  end
78
86
 
79
87
  def _update_row(attribute_names, attempted_action = "update")
@@ -88,7 +96,7 @@ module ActiveRecord
88
96
 
89
97
  affected_rows = self.class._update_record(
90
98
  attributes_with_values(attribute_names),
91
- self.class.primary_key => id_in_database,
99
+ @primary_key => id_in_database,
92
100
  locking_column => previous_lock_value
93
101
  )
94
102
 
@@ -111,7 +119,7 @@ module ActiveRecord
111
119
  locking_column = self.class.locking_column
112
120
 
113
121
  affected_rows = self.class._delete_record(
114
- self.class.primary_key => id_in_database,
122
+ @primary_key => id_in_database,
115
123
  locking_column => read_attribute_before_type_cast(locking_column)
116
124
  )
117
125
 
@@ -157,7 +165,6 @@ module ActiveRecord
157
165
  end
158
166
 
159
167
  private
160
-
161
168
  # We need to apply this decorator here, rather than on module inclusion. The closure
162
169
  # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
163
170
  # sub class being decorated. As such, changes to `lock_optimistically`, or
@@ -165,7 +172,7 @@ module ActiveRecord
165
172
  def inherited(subclass)
166
173
  subclass.class_eval do
167
174
  is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
168
- decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
175
+ decorate_matching_attribute_types(is_lock_column, "_optimistic_locking") do |type|
169
176
  LockingType.new(type)
170
177
  end
171
178
  end
@@ -14,9 +14,9 @@ module ActiveRecord
14
14
  # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
15
15
  #
16
16
  # Account.transaction do
17
- # # select * from accounts where name = 'shugo' limit 1 for update
18
- # shugo = Account.where("name = 'shugo'").lock(true).first
19
- # yuko = Account.where("name = 'yuko'").lock(true).first
17
+ # # select * from accounts where name = 'shugo' limit 1 for update nowait
18
+ # shugo = Account.lock("FOR UPDATE NOWAIT").find_by(name: "shugo")
19
+ # yuko = Account.lock("FOR UPDATE NOWAIT").find_by(name: "yuko")
20
20
  # shugo.balance -= 100
21
21
  # shugo.save!
22
22
  # yuko.balance += 100
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  class LogSubscriber < ActiveSupport::LogSubscriber
5
5
  IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
6
6
 
7
+ class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
8
+
7
9
  def self.runtime=(value)
8
10
  ActiveRecord::RuntimeRegistry.sql_runtime = value
9
11
  end
@@ -38,7 +40,7 @@ module ActiveRecord
38
40
  end
39
41
 
40
42
  name = colorize_payload_name(name, payload[:name])
41
- sql = color(sql, sql_color(sql), true)
43
+ sql = color(sql, sql_color(sql), true) if colorize_logging
42
44
 
43
45
  debug " #{name} #{sql}#{binds}"
44
46
  end
@@ -100,36 +102,15 @@ module ActiveRecord
100
102
  end
101
103
 
102
104
  def log_query_source
103
- source_line, line_number = extract_callstack(caller_locations)
104
-
105
- if source_line
106
- if defined?(::Rails.root)
107
- app_root = "#{::Rails.root.to_s}/".freeze
108
- source_line = source_line.sub(app_root, "")
109
- end
110
-
111
- logger.debug(" ↳ #{ source_line }:#{ line_number }")
112
- end
113
- end
105
+ source = extract_query_source_location(caller)
114
106
 
115
- def extract_callstack(callstack)
116
- line = callstack.find do |frame|
117
- frame.absolute_path && !ignored_callstack(frame.absolute_path)
107
+ if source
108
+ logger.debug(" ↳ #{source}")
118
109
  end
119
-
120
- offending_line = line || callstack.first
121
-
122
- [
123
- offending_line.path,
124
- offending_line.lineno
125
- ]
126
110
  end
127
111
 
128
- RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
129
-
130
- def ignored_callstack(path)
131
- path.start_with?(RAILS_GEM_ROOT) ||
132
- path.start_with?(RbConfig::CONFIG["rubylibdir"])
112
+ def extract_query_source_location(locations)
113
+ backtrace_cleaner.clean(locations.lazy).first
133
114
  end
134
115
  end
135
116
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Middleware
5
+ class DatabaseSelector
6
+ class Resolver
7
+ # The session class is used by the DatabaseSelector::Resolver to save
8
+ # timestamps of the last write in the session.
9
+ #
10
+ # The last_write is used to determine whether it's safe to read
11
+ # from the replica or the request needs to be sent to the primary.
12
+ class Session # :nodoc:
13
+ def self.call(request)
14
+ new(request.session)
15
+ end
16
+
17
+ # Converts time to a timestamp that represents milliseconds since
18
+ # epoch.
19
+ def self.convert_time_to_timestamp(time)
20
+ time.to_i * 1000 + time.usec / 1000
21
+ end
22
+
23
+ # Converts milliseconds since epoch timestamp into a time object.
24
+ def self.convert_timestamp_to_time(timestamp)
25
+ timestamp ? Time.at(timestamp / 1000, (timestamp % 1000) * 1000) : Time.at(0)
26
+ end
27
+
28
+ def initialize(session)
29
+ @session = session
30
+ end
31
+
32
+ attr_reader :session
33
+
34
+ def last_write_timestamp
35
+ self.class.convert_timestamp_to_time(session[:last_write])
36
+ end
37
+
38
+ def update_last_write_timestamp
39
+ session[:last_write] = self.class.convert_time_to_timestamp(Time.now)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/middleware/database_selector/resolver/session"
4
+
5
+ module ActiveRecord
6
+ module Middleware
7
+ class DatabaseSelector
8
+ # The Resolver class is used by the DatabaseSelector middleware to
9
+ # determine which database the request should use.
10
+ #
11
+ # To change the behavior of the Resolver class in your application,
12
+ # create a custom resolver class that inherits from
13
+ # DatabaseSelector::Resolver and implements the methods that need to
14
+ # be changed.
15
+ #
16
+ # By default the Resolver class will send read traffic to the replica
17
+ # if it's been 2 seconds since the last write.
18
+ class Resolver # :nodoc:
19
+ SEND_TO_REPLICA_DELAY = 2.seconds
20
+
21
+ def self.call(context, options = {})
22
+ new(context, options)
23
+ end
24
+
25
+ def initialize(context, options = {})
26
+ @context = context
27
+ @options = options
28
+ @delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
29
+ @instrumenter = ActiveSupport::Notifications.instrumenter
30
+ end
31
+
32
+ attr_reader :context, :delay, :instrumenter
33
+
34
+ def read(&blk)
35
+ if read_from_primary?
36
+ read_from_primary(&blk)
37
+ else
38
+ read_from_replica(&blk)
39
+ end
40
+ end
41
+
42
+ def write(&blk)
43
+ write_to_primary(&blk)
44
+ end
45
+
46
+ private
47
+ def read_from_primary(&blk)
48
+ ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
49
+ instrumenter.instrument("database_selector.active_record.read_from_primary") do
50
+ yield
51
+ end
52
+ end
53
+ end
54
+
55
+ def read_from_replica(&blk)
56
+ ActiveRecord::Base.connected_to(role: ActiveRecord::Base.reading_role, prevent_writes: true) do
57
+ instrumenter.instrument("database_selector.active_record.read_from_replica") do
58
+ yield
59
+ end
60
+ end
61
+ end
62
+
63
+ def write_to_primary(&blk)
64
+ ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: false) do
65
+ instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
66
+ yield
67
+ ensure
68
+ context.update_last_write_timestamp
69
+ end
70
+ end
71
+ end
72
+
73
+ def read_from_primary?
74
+ !time_since_last_write_ok?
75
+ end
76
+
77
+ def send_to_replica_delay
78
+ delay
79
+ end
80
+
81
+ def time_since_last_write_ok?
82
+ Time.now - context.last_write_timestamp >= send_to_replica_delay
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end