activerecord 6.1.7 → 7.2.0

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 (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +51 -49
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +18 -34
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +56 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +19 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +234 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +842 -150
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +5 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +277 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +64 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -4,19 +4,22 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module DatabaseStatements
7
- def explain(arel, binds = [])
8
- sql = "EXPLAIN #{to_sql(arel, binds)}"
9
- PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
7
+ def explain(arel, binds = [], options = [])
8
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
9
+ result = internal_exec_query(sql, "EXPLAIN", binds)
10
+ PostgreSQL::ExplainPrettyPrinter.new.pp(result)
10
11
  end
11
12
 
12
13
  # Queries the database and returns the results in an Array-like object
13
- def query(sql, name = nil) #:nodoc:
14
- materialize_transactions
14
+ def query(sql, name = nil) # :nodoc:
15
15
  mark_transaction_written_if_write(sql)
16
16
 
17
- log(sql, name) do
18
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
19
- @connection.async_exec(sql).map_types!(@type_map_for_results).values
17
+ log(sql, name) do |notification_payload|
18
+ with_raw_connection do |conn|
19
+ result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
+ verified!
21
+ notification_payload[:row_count] = result.count
22
+ result
20
23
  end
21
24
  end
22
25
  end
@@ -34,65 +37,54 @@ module ActiveRecord
34
37
 
35
38
  # Executes an SQL statement, returning a PG::Result object on success
36
39
  # or raising a PG::Error exception otherwise.
40
+ #
41
+ # Setting +allow_retry+ to true causes the db to reconnect and retry
42
+ # executing the SQL statement in case of a connection-related exception.
43
+ # This option should only be enabled for known idempotent queries.
44
+ #
37
45
  # Note: the PG::Result object is manually memory managed; if you don't
38
46
  # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
39
- def execute(sql, name = nil)
40
- if preventing_writes? && write_query?(sql)
41
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
42
- end
43
-
44
- materialize_transactions
45
- mark_transaction_written_if_write(sql)
47
+ def execute(...) # :nodoc:
48
+ super
49
+ ensure
50
+ @notice_receiver_sql_warnings = []
51
+ end
46
52
 
47
- log(sql, name) do
48
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
49
- @connection.async_exec(sql)
53
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
54
+ log(sql, name, async: async) do |notification_payload|
55
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
56
+ result = conn.async_exec(sql)
57
+ verified!
58
+ handle_warnings(result)
59
+ notification_payload[:row_count] = result.count
60
+ result
50
61
  end
51
62
  end
52
63
  end
53
64
 
54
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
55
- execute_and_clear(sql, name, binds, prepare: prepare) do |result|
65
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
66
+ execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
56
67
  types = {}
57
68
  fields = result.fields
58
69
  fields.each_with_index do |fname, i|
59
70
  ftype = result.ftype i
60
71
  fmod = result.fmod i
61
- case type = get_oid_type(ftype, fmod, fname)
62
- when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
63
- # skip if a column has already been type casted by pg decoders
64
- else types[fname] = type
65
- end
72
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
66
73
  end
67
- build_result(columns: fields, rows: result.values, column_types: types)
74
+ build_result(columns: fields, rows: result.values, column_types: types.freeze)
68
75
  end
69
76
  end
70
77
 
71
- def exec_delete(sql, name = nil, binds = [])
78
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
72
79
  execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
73
80
  end
74
81
  alias :exec_update :exec_delete
75
82
 
76
- def sql_for_insert(sql, pk, binds) # :nodoc:
77
- if pk.nil?
78
- # Extract the table from the insert sql. Yuck.
79
- table_ref = extract_table_ref_from_insert_sql(sql)
80
- pk = primary_key(table_ref) if table_ref
81
- end
82
-
83
- if pk = suppress_composite_primary_key(pk)
84
- sql = "#{sql} RETURNING #{quote_column_name(pk)}"
85
- end
86
-
87
- super
88
- end
89
- private :sql_for_insert
90
-
91
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
83
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
92
84
  if use_insert_returning? || pk == false
93
85
  super
94
86
  else
95
- result = exec_query(sql, name, binds)
87
+ result = internal_exec_query(sql, name, binds)
96
88
  unless sequence_name
97
89
  table_ref = extract_table_ref_from_insert_sql(sql)
98
90
  if table_ref
@@ -107,26 +99,77 @@ module ActiveRecord
107
99
  end
108
100
 
109
101
  # Begins a transaction.
110
- def begin_db_transaction
111
- execute("BEGIN", "TRANSACTION")
102
+ def begin_db_transaction # :nodoc:
103
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
112
104
  end
113
105
 
114
- def begin_isolated_db_transaction(isolation)
115
- begin_db_transaction
116
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
106
+ def begin_isolated_db_transaction(isolation) # :nodoc:
107
+ internal_execute("BEGIN ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
117
108
  end
118
109
 
119
110
  # Commits a transaction.
120
- def commit_db_transaction
121
- execute("COMMIT", "TRANSACTION")
111
+ def commit_db_transaction # :nodoc:
112
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
122
113
  end
123
114
 
124
115
  # Aborts a transaction.
125
- def exec_rollback_db_transaction
126
- execute("ROLLBACK", "TRANSACTION")
116
+ def exec_rollback_db_transaction # :nodoc:
117
+ cancel_any_running_query
118
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
119
+ end
120
+
121
+ def exec_restart_db_transaction # :nodoc:
122
+ cancel_any_running_query
123
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
124
+ end
125
+
126
+ # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
127
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
128
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
129
+
130
+ def high_precision_current_timestamp
131
+ HIGH_PRECISION_CURRENT_TIMESTAMP
132
+ end
133
+
134
+ def build_explain_clause(options = [])
135
+ return "EXPLAIN" if options.empty?
136
+
137
+ "EXPLAIN (#{options.join(", ").upcase})"
138
+ end
139
+
140
+ # Set when constraints will be checked for the current transaction.
141
+ #
142
+ # Not passing any specific constraint names will set the value for all deferrable constraints.
143
+ #
144
+ # [<tt>deferred</tt>]
145
+ # Valid values are +:deferred+ or +:immediate+.
146
+ #
147
+ # See https://www.postgresql.org/docs/current/sql-set-constraints.html
148
+ def set_constraints(deferred, *constraints)
149
+ unless %i[deferred immediate].include?(deferred)
150
+ raise ArgumentError, "deferred must be :deferred or :immediate"
151
+ end
152
+
153
+ constraints = if constraints.empty?
154
+ "ALL"
155
+ else
156
+ constraints.map { |c| quote_table_name(c) }.join(", ")
157
+ end
158
+ execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}")
127
159
  end
128
160
 
129
161
  private
162
+ IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
163
+ private_constant :IDLE_TRANSACTION_STATUSES
164
+
165
+ def cancel_any_running_query
166
+ return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
167
+
168
+ @raw_connection.cancel
169
+ @raw_connection.block
170
+ rescue PG::Error
171
+ end
172
+
130
173
  def execute_batch(statements, name = nil)
131
174
  execute(combine_multi_statements(statements))
132
175
  end
@@ -137,12 +180,29 @@ module ActiveRecord
137
180
 
138
181
  # Returns the current ID of a table's sequence.
139
182
  def last_insert_id_result(sequence_name)
140
- exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
183
+ internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
184
+ end
185
+
186
+ def returning_column_values(result)
187
+ result.rows.first
141
188
  end
142
189
 
143
190
  def suppress_composite_primary_key(pk)
144
191
  pk unless pk.is_a?(Array)
145
192
  end
193
+
194
+ def handle_warnings(sql)
195
+ @notice_receiver_sql_warnings.each do |warning|
196
+ next if warning_ignored?(warning)
197
+
198
+ warning.sql = sql
199
+ ActiveRecord.db_warnings_action.call(warning)
200
+ end
201
+ end
202
+
203
+ def warning_ignored?(warning)
204
+ ["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
205
+ end
146
206
  end
147
207
  end
148
208
  end
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def map(value, &block)
68
- value.map(&block)
68
+ value.map { |v| subtype.map(v, &block) }
69
69
  end
70
70
 
71
71
  def changed_in_place?(raw_old_value, new_value)
@@ -28,6 +28,12 @@ module ActiveRecord
28
28
  end
29
29
  end
30
30
 
31
+ # TODO: Remove when IPAddr#== compares IPAddr#prefix. See
32
+ # https://github.com/ruby/ipaddr/issues/21
33
+ def changed?(old_value, new_value, _new_value_before_type_cast)
34
+ super || !old_value.nil? && old_value.prefix != new_value.prefix
35
+ end
36
+
31
37
  def cast_value(value)
32
38
  if value.nil?
33
39
  nil
@@ -16,6 +16,14 @@ module ActiveRecord
16
16
  super
17
17
  end
18
18
  end
19
+
20
+ def type_cast_for_schema(value)
21
+ case value
22
+ when ::Float::INFINITY then "::Float::INFINITY"
23
+ when -::Float::INFINITY then "-::Float::INFINITY"
24
+ else super
25
+ end
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -24,6 +24,11 @@ module ActiveRecord
24
24
  else super
25
25
  end
26
26
  end
27
+
28
+ protected
29
+ def real_type_unless_aliased(real_type)
30
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type == real_type ? :datetime : real_type
31
+ end
27
32
  end
28
33
  end
29
34
  end
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "strscan"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module PostgreSQL
6
8
  module OID # :nodoc:
7
9
  class Hstore < Type::Value # :nodoc:
10
+ ERROR = "Invalid Hstore document: %s"
11
+
8
12
  include ActiveModel::Type::Helpers::Mutable
9
13
 
10
14
  def type
@@ -12,15 +16,56 @@ module ActiveRecord
12
16
  end
13
17
 
14
18
  def deserialize(value)
15
- if value.is_a?(::String)
16
- ::Hash[value.scan(HstorePair).map { |k, v|
17
- v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
18
- k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
19
- [k, v]
20
- }]
21
- else
22
- value
19
+ return value unless value.is_a?(::String)
20
+
21
+ scanner = StringScanner.new(value)
22
+ hash = {}
23
+
24
+ until scanner.eos?
25
+ unless scanner.skip(/"/)
26
+ raise(ArgumentError, ERROR % scanner.string.inspect)
27
+ end
28
+
29
+ unless key = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
30
+ raise(ArgumentError, ERROR % scanner.string.inspect)
31
+ end
32
+
33
+ unless scanner.skip(/"=>?/)
34
+ raise(ArgumentError, ERROR % scanner.string.inspect)
35
+ end
36
+
37
+ if scanner.scan(/NULL/)
38
+ value = nil
39
+ else
40
+ unless scanner.skip(/"/)
41
+ raise(ArgumentError, ERROR % scanner.string.inspect)
42
+ end
43
+
44
+ unless value = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
45
+ raise(ArgumentError, ERROR % scanner.string.inspect)
46
+ end
47
+
48
+ unless scanner.skip(/"/)
49
+ raise(ArgumentError, ERROR % scanner.string.inspect)
50
+ end
51
+ end
52
+
53
+ key.gsub!('\"', '"')
54
+ key.gsub!("\\\\", "\\")
55
+
56
+ if value
57
+ value.gsub!('\"', '"')
58
+ value.gsub!("\\\\", "\\")
59
+ end
60
+
61
+ hash[key] = value
62
+
63
+ unless scanner.skip(/, /) || scanner.eos?
64
+ raise(ArgumentError, ERROR % scanner.string.inspect)
65
+ end
23
66
  end
67
+
68
+ hash
24
69
  end
25
70
 
26
71
  def serialize(value)
@@ -46,12 +91,6 @@ module ActiveRecord
46
91
  end
47
92
 
48
93
  private
49
- HstorePair = begin
50
- quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
51
- unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
52
- /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
53
- end
54
-
55
94
  def escape_hstore(value)
56
95
  if value.nil?
57
96
  "NULL"
@@ -33,7 +33,7 @@ module ActiveRecord
33
33
  when ::Numeric
34
34
  # Sometimes operations on Times returns just float number of seconds so we need to handle that.
35
35
  # Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
36
- value.seconds.iso8601(precision: self.precision)
36
+ ActiveSupport::Duration.build(value).iso8601(precision: self.precision)
37
37
  else
38
38
  super
39
39
  end
@@ -27,9 +27,10 @@ module ActiveRecord
27
27
  value = value.sub(/^\((.+)\)$/, '-\1') # (4)
28
28
  case value
29
29
  when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
30
- value.gsub!(/[^-\d.]/, "")
30
+ value.delete!("^-0-9.")
31
31
  when /^-?\D*+[\d.]+,\d{2}$/ # (2)
32
- value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
32
+ value.delete!("^-0-9,")
33
+ value.tr!(",", ".")
33
34
  end
34
35
 
35
36
  super(value)
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  end
19
19
 
20
20
  def cast_value(value)
21
- return if value == "empty"
21
+ return if ["empty", ""].include? value
22
22
  return value unless value.is_a?(::String)
23
23
 
24
24
  extracted = extract_bounds(value)
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  if !infinity?(from) && extracted[:exclude_start]
29
29
  raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
30
30
  end
31
- ::Range.new(from, to, extracted[:exclude_end])
31
+ ::Range.new(*sanitize_bounds(from, to), extracted[:exclude_end])
32
32
  end
33
33
 
34
34
  def serialize(value)
@@ -76,6 +76,15 @@ module ActiveRecord
76
76
  }
77
77
  end
78
78
 
79
+ INFINITE_FLOAT_RANGE = (-::Float::INFINITY)..(::Float::INFINITY) # :nodoc:
80
+
81
+ def sanitize_bounds(from, to)
82
+ [
83
+ (from == -::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(to)) ? nil : from,
84
+ (to == ::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(from)) ? nil : to
85
+ ]
86
+ end
87
+
79
88
  # When formatting the bound values of range types, PostgreSQL quotes
80
89
  # the bound value using double-quotes in certain conditions. Within
81
90
  # a double-quoted string, literal " and \ characters are themselves
@@ -88,7 +97,7 @@ module ActiveRecord
88
97
  if value.start_with?('"') && value.end_with?('"')
89
98
  unquoted_value = value[1..-2]
90
99
  unquoted_value.gsub!('""', '"')
91
- unquoted_value.gsub!('\\\\', '\\')
100
+ unquoted_value.gsub!("\\\\", "\\")
92
101
  unquoted_value
93
102
  else
94
103
  value
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module OID # :nodoc:
7
+ class Timestamp < DateTime # :nodoc:
8
+ def type
9
+ real_type_unless_aliased(:timestamp)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module OID # :nodoc:
7
+ class TimestampWithTimeZone < DateTime # :nodoc:
8
+ def type
9
+ real_type_unless_aliased(:timestamptz)
10
+ end
11
+
12
+ def cast_value(value)
13
+ return if value.blank?
14
+
15
+ time = super
16
+ return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time)
17
+
18
+ # While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to PostgreSQL in UTC.
19
+ # We prefer times always in UTC, so here we convert back.
20
+ if is_utc?
21
+ time.getutc
22
+ else
23
+ time.getlocal
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -33,15 +33,27 @@ module ActiveRecord
33
33
  composites.each { |row| register_composite_type(row) }
34
34
  end
35
35
 
36
- def query_conditions_for_initial_load
36
+ def query_conditions_for_known_type_names
37
37
  known_type_names = @store.keys.map { |n| "'#{n}'" }
38
- known_type_types = %w('r' 'e' 'd')
39
- <<~SQL % [known_type_names.join(", "), known_type_types.join(", ")]
38
+ <<~SQL % known_type_names.join(", ")
40
39
  WHERE
41
40
  t.typname IN (%s)
42
- OR t.typtype IN (%s)
43
- OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure
44
- OR t.typelem != 0
41
+ SQL
42
+ end
43
+
44
+ def query_conditions_for_known_type_types
45
+ known_type_types = %w('r' 'e' 'd')
46
+ <<~SQL % known_type_types.join(", ")
47
+ WHERE
48
+ t.typtype IN (%s)
49
+ SQL
50
+ end
51
+
52
+ def query_conditions_for_array_types
53
+ known_type_oids = @store.keys.reject { |k| k.is_a?(String) }
54
+ <<~SQL % [known_type_oids.join(", ")]
55
+ WHERE
56
+ t.typelem IN (%s)
45
57
  SQL
46
58
  end
47
59
 
@@ -6,6 +6,7 @@ module ActiveRecord
6
6
  module OID # :nodoc:
7
7
  class Uuid < Type::Value # :nodoc:
8
8
  ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
9
+ CANONICAL_UUID = %r{\A[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\z}
9
10
 
10
11
  alias :serialize :deserialize
11
12
 
@@ -15,18 +16,27 @@ module ActiveRecord
15
16
 
16
17
  def changed?(old_value, new_value, _new_value_before_type_cast)
17
18
  old_value.class != new_value.class ||
18
- new_value && old_value.casecmp(new_value) != 0
19
+ new_value != old_value
19
20
  end
20
21
 
21
22
  def changed_in_place?(raw_old_value, new_value)
22
23
  raw_old_value.class != new_value.class ||
23
- new_value && raw_old_value.casecmp(new_value) != 0
24
+ new_value != raw_old_value
24
25
  end
25
26
 
26
27
  private
27
28
  def cast_value(value)
28
- casted = value.to_s
29
- casted if casted.match?(ACCEPTABLE_UUID)
29
+ value = value.to_s
30
+ format_uuid(value) if value.match?(ACCEPTABLE_UUID)
31
+ end
32
+
33
+ def format_uuid(uuid)
34
+ if uuid.match?(CANONICAL_UUID)
35
+ uuid
36
+ else
37
+ uuid = uuid.delete("{}-").downcase
38
+ "#{uuid[..7]}-#{uuid[8..11]}-#{uuid[12..15]}-#{uuid[16..19]}-#{uuid[20..]}"
39
+ end
30
40
  end
31
41
  end
32
42
  end
@@ -20,6 +20,8 @@ require "active_record/connection_adapters/postgresql/oid/point"
20
20
  require "active_record/connection_adapters/postgresql/oid/legacy_point"
21
21
  require "active_record/connection_adapters/postgresql/oid/range"
22
22
  require "active_record/connection_adapters/postgresql/oid/specialized_string"
23
+ require "active_record/connection_adapters/postgresql/oid/timestamp"
24
+ require "active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone"
23
25
  require "active_record/connection_adapters/postgresql/oid/uuid"
24
26
  require "active_record/connection_adapters/postgresql/oid/vector"
25
27
  require "active_record/connection_adapters/postgresql/oid/xml"