activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  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 +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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  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 +2 -4
  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 +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -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
14
  def query(sql, name = nil) # :nodoc:
14
- materialize_transactions
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,36 +37,41 @@ 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
- sql = transform_query(sql)
41
- check_if_write_query(sql)
42
-
43
- materialize_transactions
44
- mark_transaction_written_if_write(sql)
47
+ def execute(...) # :nodoc:
48
+ super
49
+ ensure
50
+ @notice_receiver_sql_warnings = []
51
+ end
45
52
 
46
- log(sql, name) do
47
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
48
- @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
49
61
  end
50
62
  end
51
63
  end
52
64
 
53
- def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
54
- execute_and_clear(sql, name, binds, prepare: prepare, async: async) 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|
55
67
  types = {}
56
68
  fields = result.fields
57
69
  fields.each_with_index do |fname, i|
58
70
  ftype = result.ftype i
59
71
  fmod = result.fmod i
60
- case type = get_oid_type(ftype, fmod, fname)
61
- when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
62
- # skip if a column has already been type casted by pg decoders
63
- else types[fname] = type
64
- end
72
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
65
73
  end
66
- build_result(columns: fields, rows: result.values, column_types: types)
74
+ build_result(columns: fields, rows: result.values, column_types: types.freeze)
67
75
  end
68
76
  end
69
77
 
@@ -72,26 +80,11 @@ module ActiveRecord
72
80
  end
73
81
  alias :exec_update :exec_delete
74
82
 
75
- def sql_for_insert(sql, pk, binds) # :nodoc:
76
- if pk.nil?
77
- # Extract the table from the insert sql. Yuck.
78
- table_ref = extract_table_ref_from_insert_sql(sql)
79
- pk = primary_key(table_ref) if table_ref
80
- end
81
-
82
- if pk = suppress_composite_primary_key(pk)
83
- sql = "#{sql} RETURNING #{quote_column_name(pk)}"
84
- end
85
-
86
- super
87
- end
88
- private :sql_for_insert
89
-
90
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) # :nodoc:
83
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
91
84
  if use_insert_returning? || pk == false
92
85
  super
93
86
  else
94
- result = exec_query(sql, name, binds)
87
+ result = internal_exec_query(sql, name, binds)
95
88
  unless sequence_name
96
89
  table_ref = extract_table_ref_from_insert_sql(sql)
97
90
  if table_ref
@@ -107,33 +100,76 @@ module ActiveRecord
107
100
 
108
101
  # Begins a transaction.
109
102
  def begin_db_transaction # :nodoc:
110
- execute("BEGIN", "TRANSACTION")
103
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
111
104
  end
112
105
 
113
106
  def begin_isolated_db_transaction(isolation) # :nodoc:
114
- begin_db_transaction
115
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
107
+ internal_execute("BEGIN ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
116
108
  end
117
109
 
118
110
  # Commits a transaction.
119
111
  def commit_db_transaction # :nodoc:
120
- execute("COMMIT", "TRANSACTION")
112
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
121
113
  end
122
114
 
123
115
  # Aborts a transaction.
124
116
  def exec_rollback_db_transaction # :nodoc:
125
- execute("ROLLBACK", "TRANSACTION")
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)
126
124
  end
127
125
 
128
126
  # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
129
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
127
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
130
128
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
131
129
 
132
130
  def high_precision_current_timestamp
133
131
  HIGH_PRECISION_CURRENT_TIMESTAMP
134
132
  end
135
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}")
159
+ end
160
+
136
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
+
137
173
  def execute_batch(statements, name = nil)
138
174
  execute(combine_multi_statements(statements))
139
175
  end
@@ -144,12 +180,29 @@ module ActiveRecord
144
180
 
145
181
  # Returns the current ID of a table's sequence.
146
182
  def last_insert_id_result(sequence_name)
147
- 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
148
188
  end
149
189
 
150
190
  def suppress_composite_primary_key(pk)
151
191
  pk unless pk.is_a?(Array)
152
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
153
206
  end
154
207
  end
155
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
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  raise(ArgumentError, ERROR % scanner.string.inspect)
27
27
  end
28
28
 
29
- unless key = scanner.scan_until(/(?<!\\)(?=")/)
29
+ unless key = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
30
30
  raise(ArgumentError, ERROR % scanner.string.inspect)
31
31
  end
32
32
 
@@ -41,7 +41,7 @@ module ActiveRecord
41
41
  raise(ArgumentError, ERROR % scanner.string.inspect)
42
42
  end
43
43
 
44
- unless value = scanner.scan_until(/(?<!\\)(?=")/)
44
+ unless value = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
45
45
  raise(ArgumentError, ERROR % scanner.string.inspect)
46
46
  end
47
47
 
@@ -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
@@ -10,10 +10,12 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def cast_value(value)
13
+ return if value.blank?
14
+
13
15
  time = super
14
- return time if time.is_a?(ActiveSupport::TimeWithZone)
16
+ return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time)
15
17
 
16
- # While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to Postgres in UTC.
18
+ # While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to PostgreSQL in UTC.
17
19
  # We prefer times always in UTC, so here we convert back.
18
20
  if is_utc?
19
21
  time.getutc
@@ -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
@@ -4,19 +4,101 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
+ extend ActiveSupport::Concern
8
+
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
+
12
+ module ClassMethods # :nodoc:
13
+ def column_name_matcher
14
+ /
15
+ \A
16
+ (
17
+ (?:
18
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
19
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
20
+ )
21
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
22
+ )
23
+ (?:\s*,\s*\g<1>)*
24
+ \z
25
+ /ix
26
+ end
27
+
28
+ def column_name_with_order_matcher
29
+ /
30
+ \A
31
+ (
32
+ (?:
33
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
34
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
35
+ )
36
+ (?:\s+COLLATE\s+"\w+")?
37
+ (?:\s+ASC|\s+DESC)?
38
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
39
+ )
40
+ (?:\s*,\s*\g<1>)*
41
+ \z
42
+ /ix
43
+ end
44
+
45
+ # Quotes column names for use in SQL queries.
46
+ def quote_column_name(name) # :nodoc:
47
+ QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze
48
+ end
49
+
50
+ # Checks the following cases:
51
+ #
52
+ # - table_name
53
+ # - "table.name"
54
+ # - schema_name.table_name
55
+ # - schema_name."table.name"
56
+ # - "schema.name".table_name
57
+ # - "schema.name"."table.name"
58
+ def quote_table_name(name) # :nodoc:
59
+ QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
60
+ end
61
+ end
62
+
63
+ class IntegerOutOf64BitRange < StandardError
64
+ def initialize(msg)
65
+ super(msg)
66
+ end
67
+ end
68
+
7
69
  # Escapes binary strings for bytea input to the database.
8
70
  def escape_bytea(value)
9
- @connection.escape_bytea(value) if value
71
+ valid_raw_connection.escape_bytea(value) if value
10
72
  end
11
73
 
12
74
  # Unescapes bytea output from a database to the binary string it represents.
13
75
  # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
14
76
  # on escaped binary output from database drive.
15
77
  def unescape_bytea(value)
16
- @connection.unescape_bytea(value) if value
78
+ valid_raw_connection.unescape_bytea(value) if value
79
+ end
80
+
81
+ def check_int_in_range(value)
82
+ if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808
83
+ exception = <<~ERROR
84
+ Provided value outside of the range of a signed 64bit integer.
85
+
86
+ PostgreSQL will treat the column type in question as a numeric.
87
+ This may result in a slow sequential scan due to a comparison
88
+ being performed between an integer or bigint value and a numeric value.
89
+
90
+ To allow for this potentially unwanted behavior, set
91
+ ActiveRecord.raise_int_wider_than_64bit to false.
92
+ ERROR
93
+ raise IntegerOutOf64BitRange.new exception
94
+ end
17
95
  end
18
96
 
19
97
  def quote(value) # :nodoc:
98
+ if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer)
99
+ check_int_in_range(value)
100
+ end
101
+
20
102
  case value
21
103
  when OID::Xml::Data
22
104
  "xml '#{quote_string(value.to_s)}'"
@@ -43,33 +125,18 @@ module ActiveRecord
43
125
 
44
126
  # Quotes strings for use in SQL input.
45
127
  def quote_string(s) # :nodoc:
46
- PG::Connection.escape(s)
47
- end
48
-
49
- # Checks the following cases:
50
- #
51
- # - table_name
52
- # - "table.name"
53
- # - schema_name.table_name
54
- # - schema_name."table.name"
55
- # - "schema.name".table_name
56
- # - "schema.name"."table.name"
57
- def quote_table_name(name) # :nodoc:
58
- self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
59
- end
60
-
61
- # Quotes schema names for use in SQL queries.
62
- def quote_schema_name(name)
63
- PG::Connection.quote_ident(name)
128
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
129
+ connection.escape(s)
130
+ end
64
131
  end
65
132
 
66
133
  def quote_table_name_for_assignment(table, attr)
67
134
  quote_column_name(attr)
68
135
  end
69
136
 
70
- # Quotes column names for use in SQL queries.
71
- def quote_column_name(name) # :nodoc:
72
- self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
137
+ # Quotes schema names for use in SQL queries.
138
+ def quote_schema_name(schema_name)
139
+ quote_column_name(schema_name)
73
140
  end
74
141
 
75
142
  # Quote date/time values for use in SQL input.
@@ -89,7 +156,7 @@ module ActiveRecord
89
156
  def quote_default_expression(value, column) # :nodoc:
90
157
  if value.is_a?(Proc)
91
158
  value.call
92
- elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value)
159
+ elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
93
160
  value # Does not quote function default values for UUID columns
94
161
  elsif column.respond_to?(:array?)
95
162
  type = lookup_cast_type_from_column(column)
@@ -112,52 +179,18 @@ module ActiveRecord
112
179
  encode_array(value)
113
180
  when Range
114
181
  encode_range(value)
182
+ when Rational
183
+ value.to_f
115
184
  else
116
185
  super
117
186
  end
118
187
  end
119
188
 
120
189
  def lookup_cast_type_from_column(column) # :nodoc:
190
+ verify! if type_map.nil?
121
191
  type_map.lookup(column.oid, column.fmod, column.sql_type)
122
192
  end
123
193
 
124
- def column_name_matcher
125
- COLUMN_NAME
126
- end
127
-
128
- def column_name_with_order_matcher
129
- COLUMN_NAME_WITH_ORDER
130
- end
131
-
132
- COLUMN_NAME = /
133
- \A
134
- (
135
- (?:
136
- # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
137
- ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
138
- )
139
- (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
140
- )
141
- (?:\s*,\s*\g<1>)*
142
- \z
143
- /ix
144
-
145
- COLUMN_NAME_WITH_ORDER = /
146
- \A
147
- (
148
- (?:
149
- # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
150
- ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
151
- )
152
- (?:\s+ASC|\s+DESC)?
153
- (?:\s+NULLS\s+(?:FIRST|LAST))?
154
- )
155
- (?:\s*,\s*\g<1>)*
156
- \z
157
- /ix
158
-
159
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
160
-
161
194
  private
162
195
  def lookup_cast_type(sql_type)
163
196
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
@@ -38,15 +38,17 @@ Rails needs superuser privileges to disable referential integrity.
38
38
  end
39
39
  end
40
40
 
41
- def all_foreign_keys_valid? # :nodoc:
41
+ def check_all_foreign_keys_valid! # :nodoc:
42
42
  sql = <<~SQL
43
43
  do $$
44
44
  declare r record;
45
45
  BEGIN
46
46
  FOR r IN (
47
47
  SELECT FORMAT(
48
- 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I''; ALTER TABLE %I VALIDATE CONSTRAINT %I;',
48
+ 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
49
49
  constraint_name,
50
+ table_schema,
51
+ table_schema,
50
52
  table_name,
51
53
  constraint_name
52
54
  ) AS constraint_check
@@ -59,14 +61,8 @@ Rails needs superuser privileges to disable referential integrity.
59
61
  $$;
60
62
  SQL
61
63
 
62
- begin
63
- transaction(requires_new: true) do
64
- execute(sql)
65
- end
66
-
67
- true
68
- rescue ActiveRecord::StatementInvalid
69
- false
64
+ transaction(requires_new: true) do
65
+ execute(sql)
70
66
  end
71
67
  end
72
68
  end