activerecord 7.0.8.7 → 7.2.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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -2,11 +2,13 @@
2
2
 
3
3
  module ActiveRecord
4
4
  ###
5
+ # = Active Record \Result
6
+ #
5
7
  # This class encapsulates a result returned from calling
6
8
  # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
7
9
  # on any database connection adapter. For example:
8
10
  #
9
- # 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')
10
12
  # result # => #<ActiveRecord::Result:0xdeadbeef>
11
13
  #
12
14
  # # Get the column names of the result:
@@ -36,20 +38,24 @@ module ActiveRecord
36
38
 
37
39
  attr_reader :columns, :rows, :column_types
38
40
 
39
- def self.empty # :nodoc:
40
- EMPTY
41
+ def self.empty(async: false) # :nodoc:
42
+ if async
43
+ EMPTY_ASYNC
44
+ else
45
+ EMPTY
46
+ end
41
47
  end
42
48
 
43
- def initialize(columns, rows, column_types = {})
44
- @columns = columns
49
+ def initialize(columns, rows, column_types = nil)
50
+ # We freeze the strings to prevent them getting duped when
51
+ # used as keys in ActiveRecord::Base's @attributes hash
52
+ @columns = columns.each(&:-@).freeze
45
53
  @rows = rows
46
54
  @hash_rows = nil
47
- @column_types = column_types
55
+ @column_types = column_types || EMPTY_HASH
56
+ @column_indexes = nil
48
57
  end
49
58
 
50
- EMPTY = new([].freeze, [].freeze, {}.freeze)
51
- private_constant :EMPTY
52
-
53
59
  # Returns true if this result set includes the column named +name+
54
60
  def includes_column?(name)
55
61
  @columns.include? name
@@ -128,12 +134,30 @@ module ActiveRecord
128
134
  end
129
135
 
130
136
  def initialize_copy(other)
131
- @columns = columns.dup
137
+ @columns = columns
132
138
  @rows = rows.dup
133
139
  @column_types = column_types.dup
134
140
  @hash_rows = nil
135
141
  end
136
142
 
143
+ def freeze # :nodoc:
144
+ hash_rows.freeze
145
+ super
146
+ end
147
+
148
+ def column_indexes # :nodoc:
149
+ @column_indexes ||= begin
150
+ index = 0
151
+ hash = {}
152
+ length = columns.length
153
+ while index < length
154
+ hash[columns[index]] = index
155
+ index += 1
156
+ end
157
+ hash
158
+ end
159
+ end
160
+
137
161
  private
138
162
  def column_type(name, index, type_overrides)
139
163
  type_overrides.fetch(name) do
@@ -144,44 +168,21 @@ module ActiveRecord
144
168
  end
145
169
 
146
170
  def hash_rows
147
- @hash_rows ||=
148
- begin
149
- # We freeze the strings to prevent them getting duped when
150
- # used as keys in ActiveRecord::Base's @attributes hash
151
- columns = @columns.map(&:-@)
152
- length = columns.length
153
- template = nil
154
-
155
- @rows.map { |row|
156
- if template
157
- # We use transform_values to build subsequent rows from the
158
- # hash of the first row. This is faster because we avoid any
159
- # reallocs and in Ruby 2.7+ avoid hashing entirely.
160
- index = -1
161
- template.transform_values do
162
- row[index += 1]
163
- end
164
- else
165
- # In the past we used Hash[columns.zip(row)]
166
- # though elegant, the verbose way is much more efficient
167
- # both time and memory wise cause it avoids a big array allocation
168
- # this method is called a lot and needs to be micro optimised
169
- hash = {}
170
-
171
- index = 0
172
- while index < length
173
- hash[columns[index]] = row[index]
174
- index += 1
175
- end
176
-
177
- # It's possible to select the same column twice, in which case
178
- # we can't use a template
179
- template = hash if hash.length == length
180
-
181
- hash
182
- end
183
- }
184
- end
171
+ # We use transform_values to rows.
172
+ # This is faster because we avoid any reallocs and avoid hashing entirely.
173
+ @hash_rows ||= @rows.map do |row|
174
+ column_indexes.transform_values { |index| row[index] }
175
+ end
185
176
  end
177
+
178
+ empty_array = [].freeze
179
+ EMPTY_HASH = {}.freeze
180
+ private_constant :EMPTY_HASH
181
+
182
+ EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
183
+ private_constant :EMPTY
184
+
185
+ EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
186
+ private_constant :EMPTY_ASYNC
186
187
  end
187
188
  end
@@ -10,11 +10,73 @@ module ActiveRecord
10
10
  extend self
11
11
 
12
12
  def sql_runtime
13
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime]
13
+ ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
14
14
  end
15
15
 
16
16
  def sql_runtime=(runtime)
17
17
  ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
18
  end
19
+
20
+ def async_sql_runtime
21
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
+ end
23
+
24
+ def async_sql_runtime=(runtime)
25
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
26
+ end
27
+
28
+ def queries_count
29
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
+ end
31
+
32
+ def queries_count=(count)
33
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
+ end
35
+
36
+ def cached_queries_count
37
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
38
+ end
39
+
40
+ def cached_queries_count=(count)
41
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
42
+ end
43
+
44
+ def reset
45
+ reset_runtimes
46
+ reset_queries_count
47
+ reset_cached_queries_count
48
+ end
49
+
50
+ def reset_runtimes
51
+ rt, self.sql_runtime = sql_runtime, 0.0
52
+ self.async_sql_runtime = 0.0
53
+ rt
54
+ end
55
+
56
+ def reset_queries_count
57
+ qc = queries_count
58
+ self.queries_count = 0
59
+ qc
60
+ end
61
+
62
+ def reset_cached_queries_count
63
+ qc = cached_queries_count
64
+ self.cached_queries_count = 0
65
+ qc
66
+ end
67
+ end
68
+ end
69
+
70
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
+ unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
+ ActiveRecord::RuntimeRegistry.queries_count += 1
73
+ ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
+ end
75
+
76
+ runtime = (finish - start) * 1_000.0
77
+
78
+ if payload[:async]
79
+ ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
19
80
  end
81
+ ActiveRecord::RuntimeRegistry.sql_runtime += runtime
20
82
  end
@@ -5,8 +5,8 @@ module ActiveRecord
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  module ClassMethods
8
- # Accepts an array or string of SQL conditions and sanitizes
9
- # them into a valid SQL fragment for a WHERE clause.
8
+ # Accepts an array of SQL conditions and sanitizes them into a valid
9
+ # SQL fragment for a WHERE clause.
10
10
  #
11
11
  # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
12
12
  # # => "name='foo''bar' and group_id=4"
@@ -17,8 +17,19 @@ 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 an SQL string since it won't contain
21
+ # any conditions in it and will return the string as is.
22
+ #
20
23
  # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
21
24
  # # => "name='foo''bar' and group_id='4'"
25
+ #
26
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
27
+ # and will directly use the database adapter's +quote+ method.
28
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
29
+ # to prevent query manipulation attacks.
30
+ #
31
+ # sanitize_sql_for_conditions(["role = ?", 0])
32
+ # # => "role = '0'"
22
33
  def sanitize_sql_for_conditions(condition)
23
34
  return nil if condition.blank?
24
35
 
@@ -29,8 +40,8 @@ module ActiveRecord
29
40
  end
30
41
  alias :sanitize_sql :sanitize_sql_for_conditions
31
42
 
32
- # Accepts an array, hash, or string of SQL conditions and sanitizes
33
- # them into a valid SQL fragment for a SET clause.
43
+ # Accepts an array or hash of SQL conditions and sanitizes them into
44
+ # a valid SQL fragment for a SET clause.
34
45
  #
35
46
  # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
36
47
  # # => "name=NULL and group_id=4"
@@ -41,8 +52,19 @@ module ActiveRecord
41
52
  # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
42
53
  # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
43
54
  #
55
+ # This method will NOT sanitize an SQL string since it won't contain
56
+ # any conditions in it and will return the string as is.
57
+ #
44
58
  # sanitize_sql_for_assignment("name=NULL and group_id='4'")
45
59
  # # => "name=NULL and group_id='4'"
60
+ #
61
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
62
+ # and will directly use the database adapter's +quote+ method.
63
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
64
+ # to prevent query manipulation attacks.
65
+ #
66
+ # sanitize_sql_for_assignment(["role = ?", 0])
67
+ # # => "role = '0'"
46
68
  def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
47
69
  case assignments
48
70
  when Array; sanitize_sql_array(assignments)
@@ -63,7 +85,7 @@ module ActiveRecord
63
85
  if condition.is_a?(Array) && condition.first.to_s.include?("?")
64
86
  disallow_raw_sql!(
65
87
  [condition.first],
66
- permit: connection.column_name_with_order_matcher
88
+ permit: adapter_class.column_name_with_order_matcher
67
89
  )
68
90
 
69
91
  # Ensure we aren't dealing with a subclass of String that might
@@ -107,12 +129,17 @@ module ActiveRecord
107
129
  # sanitize_sql_like("snake_cased_string", "!")
108
130
  # # => "snake!_cased!_string"
109
131
  def sanitize_sql_like(string, escape_character = "\\")
110
- pattern = Regexp.union(escape_character, "%", "_")
111
- string.gsub(pattern) { |x| [escape_character, x].join }
132
+ if string.include?(escape_character) && escape_character != "%" && escape_character != "_"
133
+ string = string.gsub(escape_character, '\0\0')
134
+ end
135
+
136
+ string.gsub(/(?=[%_])/, escape_character)
112
137
  end
113
138
 
114
139
  # Accepts an array of conditions. The array has each value
115
- # sanitized and interpolated into the SQL statement.
140
+ # sanitized and interpolated into the SQL statement. If using named bind
141
+ # variables in SQL statements where a colon is required verbatim use a
142
+ # backslash to escape.
116
143
  #
117
144
  # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
118
145
  # # => "name='foo''bar' and group_id=4"
@@ -120,22 +147,39 @@ module ActiveRecord
120
147
  # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
121
148
  # # => "name='foo''bar' and group_id=4"
122
149
  #
150
+ # sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"])
151
+ # # => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')"
152
+ #
123
153
  # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
124
154
  # # => "name='foo''bar' and group_id='4'"
155
+ #
156
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
157
+ # and will directly use the database adapter's +quote+ method.
158
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
159
+ # to prevent query manipulation attacks.
160
+ #
161
+ # sanitize_sql_array(["role = ?", 0])
162
+ # # => "role = '0'"
125
163
  def sanitize_sql_array(ary)
126
164
  statement, *values = ary
127
165
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
128
- replace_named_bind_variables(statement, values.first)
166
+ with_connection do |c|
167
+ replace_named_bind_variables(c, statement, values.first)
168
+ end
129
169
  elsif statement.include?("?")
130
- replace_bind_variables(statement, values)
170
+ with_connection do |c|
171
+ replace_bind_variables(c, statement, values)
172
+ end
131
173
  elsif statement.blank?
132
174
  statement
133
175
  else
134
- statement % values.collect { |value| connection.quote_string(value.to_s) }
176
+ with_connection do |c|
177
+ statement % values.collect { |value| c.quote_string(value.to_s) }
178
+ end
135
179
  end
136
180
  end
137
181
 
138
- def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
182
+ def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
139
183
  unexpected = nil
140
184
  args.each do |arg|
141
185
  next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
@@ -155,46 +199,47 @@ module ActiveRecord
155
199
  end
156
200
 
157
201
  private
158
- def replace_bind_variables(statement, values)
202
+ def replace_bind_variables(connection, statement, values)
159
203
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
160
204
  bound = values.dup
161
- c = connection
162
205
  statement.gsub(/\?/) do
163
- replace_bind_variable(bound.shift, c)
206
+ replace_bind_variable(connection, bound.shift)
164
207
  end
165
208
  end
166
209
 
167
- def replace_bind_variable(value, c = connection)
210
+ def replace_bind_variable(connection, value)
168
211
  if ActiveRecord::Relation === value
169
212
  value.to_sql
170
213
  else
171
- quote_bound_value(value, c)
214
+ quote_bound_value(connection, value)
172
215
  end
173
216
  end
174
217
 
175
- def replace_named_bind_variables(statement, bind_vars)
176
- statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
177
- if $1 == ":" # skip postgresql casts
218
+ def replace_named_bind_variables(connection, statement, bind_vars)
219
+ statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
220
+ if $1 == ":" # skip PostgreSQL casts
178
221
  match # return the whole match
222
+ elsif $1 == "\\" # escaped literal colon
223
+ match[1..-1] # return match with escaping backlash char removed
179
224
  elsif bind_vars.include?(match = $2.to_sym)
180
- replace_bind_variable(bind_vars[match])
225
+ replace_bind_variable(connection, bind_vars[match])
181
226
  else
182
227
  raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
183
228
  end
184
229
  end
185
230
  end
186
231
 
187
- def quote_bound_value(value, c = connection)
232
+ def quote_bound_value(connection, value)
188
233
  if value.respond_to?(:map) && !value.acts_like?(:string)
189
234
  values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
190
235
  if values.empty?
191
- c.quote_bound_value(nil)
236
+ connection.quote(connection.cast_bound_value(nil))
192
237
  else
193
- values.map! { |v| c.quote_bound_value(v) }.join(",")
238
+ values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
194
239
  end
195
240
  else
196
241
  value = value.id_for_database if value.respond_to?(:id_for_database)
197
- c.quote_bound_value(value)
242
+ connection.quote(connection.cast_bound_value(value))
198
243
  end
199
244
  end
200
245
 
@@ -52,15 +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
- if info[:version].present?
58
- connection.schema_migration.create_table
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
- ActiveRecord::InternalMetadata.create_table
63
- ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
63
+ connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
64
+ end
64
65
  end
65
66
  end
66
67
 
@@ -13,8 +13,7 @@ module ActiveRecord
13
13
  ##
14
14
  # :singleton-method:
15
15
  # A list of tables which should not be dumped to the schema.
16
- # Acceptable values are strings as well as regexp if ActiveRecord.schema_format == :ruby.
17
- # Only strings are accepted if ActiveRecord.schema_format == :sql.
16
+ # Acceptable values are strings and regexps.
18
17
  cattr_accessor :ignore_tables, default: []
19
18
 
20
19
  ##
@@ -29,9 +28,23 @@ module ActiveRecord
29
28
  # should not be dumped to db/schema.rb.
30
29
  cattr_accessor :chk_ignore_pattern, default: /^chk_rails_[0-9a-f]{10}$/
31
30
 
31
+ ##
32
+ # :singleton-method:
33
+ # Specify a custom regular expression matching exclusion constraints which name
34
+ # should not be dumped to db/schema.rb.
35
+ cattr_accessor :excl_ignore_pattern, default: /^excl_rails_[0-9a-f]{10}$/
36
+
37
+ ##
38
+ # :singleton-method:
39
+ # Specify a custom regular expression matching unique constraints which name
40
+ # should not be dumped to db/schema.rb.
41
+ cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/
42
+
32
43
  class << self
33
- def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
34
- connection.create_schema_dumper(generate_options(config)).dump(stream)
44
+ def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base)
45
+ pool.with_connection do |connection|
46
+ connection.create_schema_dumper(generate_options(config)).dump(stream)
47
+ end
35
48
  stream
36
49
  end
37
50
 
@@ -46,6 +59,7 @@ module ActiveRecord
46
59
 
47
60
  def dump(stream)
48
61
  header(stream)
62
+ schemas(stream)
49
63
  extensions(stream)
50
64
  types(stream)
51
65
  tables(stream)
@@ -58,8 +72,13 @@ module ActiveRecord
58
72
 
59
73
  def initialize(connection, options = {})
60
74
  @connection = connection
61
- @version = connection.migration_context.current_version rescue nil
75
+ @version = connection.pool.migration_context.current_version rescue nil
62
76
  @options = options
77
+ @ignore_tables = [
78
+ ActiveRecord::Base.schema_migrations_table_name,
79
+ ActiveRecord::Base.internal_metadata_table_name,
80
+ self.class.ignore_tables
81
+ ].flatten
63
82
  end
64
83
 
65
84
  # turns 20170404131909 into "2017_04_04_131909"
@@ -103,18 +122,31 @@ module ActiveRecord
103
122
  def types(stream)
104
123
  end
105
124
 
125
+ # schemas are only supported by PostgreSQL
126
+ def schemas(stream)
127
+ end
128
+
106
129
  def tables(stream)
107
130
  sorted_tables = @connection.tables.sort
108
131
 
109
- sorted_tables.each do |table_name|
110
- table(table_name, stream) unless ignored?(table_name)
132
+ not_ignored_tables = sorted_tables.reject { |table_name| ignored?(table_name) }
133
+
134
+ not_ignored_tables.each_with_index do |table_name, index|
135
+ table(table_name, stream)
136
+ stream.puts if index < not_ignored_tables.count - 1
111
137
  end
112
138
 
113
139
  # dump foreign keys at the end to make sure all dependent tables exist.
114
140
  if @connection.supports_foreign_keys?
115
- sorted_tables.each do |tbl|
116
- foreign_keys(tbl, stream) unless ignored?(tbl)
141
+ foreign_keys_stream = StringIO.new
142
+ not_ignored_tables.each do |tbl|
143
+ foreign_keys(tbl, foreign_keys_stream)
117
144
  end
145
+
146
+ foreign_keys_string = foreign_keys_stream.string
147
+ stream.puts if foreign_keys_string.length > 0
148
+
149
+ stream.print foreign_keys_string
118
150
  end
119
151
  end
120
152
 
@@ -171,12 +203,12 @@ module ActiveRecord
171
203
 
172
204
  indexes_in_create(table, tbl)
173
205
  check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
206
+ exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
207
+ unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
174
208
 
175
209
  tbl.puts " end"
176
- tbl.puts
177
210
 
178
- tbl.rewind
179
- stream.print tbl.read
211
+ stream.print tbl.string
180
212
  rescue => e
181
213
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
182
214
  stream.puts "# #{e.message}"
@@ -201,6 +233,18 @@ module ActiveRecord
201
233
 
202
234
  def indexes_in_create(table, stream)
203
235
  if (indexes = @connection.indexes(table)).any?
236
+ if @connection.supports_exclusion_constraints? && (exclusion_constraints = @connection.exclusion_constraints(table)).any?
237
+ exclusion_constraint_names = exclusion_constraints.collect(&:name)
238
+
239
+ indexes = indexes.reject { |index| exclusion_constraint_names.include?(index.name) }
240
+ end
241
+
242
+ if @connection.supports_unique_constraints? && (unique_constraints = @connection.unique_constraints(table)).any?
243
+ unique_constraint_names = unique_constraints.collect(&:name)
244
+
245
+ indexes = indexes.reject { |index| unique_constraint_names.include?(index.name) }
246
+ end
247
+
204
248
  index_statements = indexes.map do |index|
205
249
  " t.index #{index_parts(index).join(', ')}"
206
250
  end
@@ -219,6 +263,8 @@ module ActiveRecord
219
263
  index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
220
264
  index_parts << "where: #{index.where.inspect}" if index.where
221
265
  index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
266
+ index_parts << "include: #{index.include.inspect}" if index.include
267
+ index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
222
268
  index_parts << "type: #{index.type.inspect}" if index.type
223
269
  index_parts << "comment: #{index.comment.inspect}" if index.comment
224
270
  index_parts
@@ -235,6 +281,8 @@ module ActiveRecord
235
281
  parts << "name: #{check_constraint.name.inspect}"
236
282
  end
237
283
 
284
+ parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
285
+
238
286
  " #{parts.join(', ')}"
239
287
  end
240
288
 
@@ -250,7 +298,7 @@ module ActiveRecord
250
298
  remove_prefix_and_suffix(foreign_key.to_table).inspect,
251
299
  ]
252
300
 
253
- if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
301
+ if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
254
302
  parts << "column: #{foreign_key.column.inspect}"
255
303
  end
256
304
 
@@ -265,6 +313,7 @@ module ActiveRecord
265
313
  parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
266
314
  parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
267
315
  parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
316
+ parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
268
317
 
269
318
  " #{parts.join(', ')}"
270
319
  end
@@ -302,7 +351,7 @@ module ActiveRecord
302
351
  end
303
352
 
304
353
  def ignored?(table_name)
305
- [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored|
354
+ @ignore_tables.any? do |ignored|
306
355
  ignored === remove_prefix_and_suffix(table_name)
307
356
  end
308
357
  end