activerecord 6.0.0 → 7.2.3

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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +996 -594
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +34 -34
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +22 -20
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +41 -30
  9. data/lib/active_record/associations/association.rb +106 -41
  10. data/lib/active_record/associations/association_scope.rb +30 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +69 -14
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
  13. data/lib/active_record/associations/builder/association.rb +39 -6
  14. data/lib/active_record/associations/builder/belongs_to.rb +47 -17
  15. data/lib/active_record/associations/builder/collection_association.rb +14 -6
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
  17. data/lib/active_record/associations/builder/has_many.rb +7 -3
  18. data/lib/active_record/associations/builder/has_one.rb +13 -16
  19. data/lib/active_record/associations/builder/singular_association.rb +7 -3
  20. data/lib/active_record/associations/collection_association.rb +90 -53
  21. data/lib/active_record/associations/collection_proxy.rb +54 -19
  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 +21 -1
  25. data/lib/active_record/associations/has_many_association.rb +41 -10
  26. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  27. data/lib/active_record/associations/has_one_association.rb +33 -9
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
  30. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  31. data/lib/active_record/associations/join_dependency.rb +97 -54
  32. data/lib/active_record/associations/nested_error.rb +47 -0
  33. data/lib/active_record/associations/preloader/association.rb +237 -54
  34. data/lib/active_record/associations/preloader/batch.rb +48 -0
  35. data/lib/active_record/associations/preloader/branch.rb +153 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +51 -17
  37. data/lib/active_record/associations/preloader.rb +55 -121
  38. data/lib/active_record/associations/singular_association.rb +16 -4
  39. data/lib/active_record/associations/through_association.rb +26 -15
  40. data/lib/active_record/associations.rb +454 -440
  41. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  42. data/lib/active_record/attribute_assignment.rb +11 -14
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
  44. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  45. data/lib/active_record/attribute_methods/dirty.rb +75 -34
  46. data/lib/active_record/attribute_methods/primary_key.rb +53 -31
  47. data/lib/active_record/attribute_methods/query.rb +31 -22
  48. data/lib/active_record/attribute_methods/read.rb +16 -17
  49. data/lib/active_record/attribute_methods/serialization.rb +177 -35
  50. data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
  51. data/lib/active_record/attribute_methods/write.rb +16 -28
  52. data/lib/active_record/attribute_methods.rb +227 -100
  53. data/lib/active_record/attributes.rb +94 -56
  54. data/lib/active_record/autosave_association.rb +119 -73
  55. data/lib/active_record/base.rb +31 -21
  56. data/lib/active_record/callbacks.rb +168 -55
  57. data/lib/active_record/coders/column_serializer.rb +61 -0
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +70 -25
  60. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
  76. data/lib/active_record/connection_adapters/column.rb +28 -1
  77. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  78. data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
  79. data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
  80. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  81. data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
  82. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
  83. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
  84. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
  85. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  87. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  88. data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
  89. data/lib/active_record/connection_adapters/pool_config.rb +83 -0
  90. data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
  91. data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
  92. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
  95. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
  99. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  101. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
  106. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  110. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
  111. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  112. data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
  113. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
  114. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
  115. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
  116. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
  117. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
  118. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  119. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
  120. data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
  121. data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
  122. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  123. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  124. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
  125. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
  126. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
  127. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  128. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  129. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
  130. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
  131. data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
  132. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  133. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  134. data/lib/active_record/connection_adapters.rb +176 -0
  135. data/lib/active_record/connection_handling.rb +243 -115
  136. data/lib/active_record/core.rb +481 -199
  137. data/lib/active_record/counter_cache.rb +69 -32
  138. data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
  139. data/lib/active_record/database_configurations/database_config.rb +77 -10
  140. data/lib/active_record/database_configurations/hash_config.rb +148 -26
  141. data/lib/active_record/database_configurations/url_config.rb +44 -45
  142. data/lib/active_record/database_configurations.rb +190 -114
  143. data/lib/active_record/delegated_type.rb +279 -0
  144. data/lib/active_record/deprecator.rb +7 -0
  145. data/lib/active_record/destroy_association_async_job.rb +38 -0
  146. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  147. data/lib/active_record/dynamic_matchers.rb +5 -6
  148. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  149. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  150. data/lib/active_record/encryption/cipher.rb +53 -0
  151. data/lib/active_record/encryption/config.rb +68 -0
  152. data/lib/active_record/encryption/configurable.rb +60 -0
  153. data/lib/active_record/encryption/context.rb +42 -0
  154. data/lib/active_record/encryption/contexts.rb +76 -0
  155. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  156. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  157. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  158. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  159. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  160. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  161. data/lib/active_record/encryption/encryptor.rb +171 -0
  162. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  163. data/lib/active_record/encryption/errors.rb +15 -0
  164. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  165. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  166. data/lib/active_record/encryption/key.rb +28 -0
  167. data/lib/active_record/encryption/key_generator.rb +53 -0
  168. data/lib/active_record/encryption/key_provider.rb +46 -0
  169. data/lib/active_record/encryption/message.rb +33 -0
  170. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  171. data/lib/active_record/encryption/message_serializer.rb +96 -0
  172. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  173. data/lib/active_record/encryption/properties.rb +76 -0
  174. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  175. data/lib/active_record/encryption/scheme.rb +100 -0
  176. data/lib/active_record/encryption.rb +58 -0
  177. data/lib/active_record/enum.rb +224 -73
  178. data/lib/active_record/errors.rb +254 -36
  179. data/lib/active_record/explain.rb +30 -17
  180. data/lib/active_record/explain_registry.rb +11 -6
  181. data/lib/active_record/explain_subscriber.rb +2 -2
  182. data/lib/active_record/fixture_set/file.rb +22 -15
  183. data/lib/active_record/fixture_set/model_metadata.rb +15 -6
  184. data/lib/active_record/fixture_set/render_context.rb +3 -1
  185. data/lib/active_record/fixture_set/table_row.rb +88 -16
  186. data/lib/active_record/fixture_set/table_rows.rb +4 -5
  187. data/lib/active_record/fixtures.rb +229 -116
  188. data/lib/active_record/future_result.rb +178 -0
  189. data/lib/active_record/gem_version.rb +4 -4
  190. data/lib/active_record/inheritance.rb +121 -48
  191. data/lib/active_record/insert_all.rb +178 -29
  192. data/lib/active_record/integration.rb +16 -14
  193. data/lib/active_record/internal_metadata.rb +132 -21
  194. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  195. data/lib/active_record/locking/optimistic.rb +64 -33
  196. data/lib/active_record/locking/pessimistic.rb +21 -8
  197. data/lib/active_record/log_subscriber.rb +61 -30
  198. data/lib/active_record/marshalling.rb +59 -0
  199. data/lib/active_record/message_pack.rb +124 -0
  200. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  201. data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
  202. data/lib/active_record/middleware/database_selector.rb +25 -13
  203. data/lib/active_record/middleware/shard_selector.rb +62 -0
  204. data/lib/active_record/migration/command_recorder.rb +160 -55
  205. data/lib/active_record/migration/compatibility.rb +286 -43
  206. data/lib/active_record/migration/default_strategy.rb +22 -0
  207. data/lib/active_record/migration/execution_strategy.rb +19 -0
  208. data/lib/active_record/migration/join_table.rb +1 -2
  209. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  210. data/lib/active_record/migration.rb +421 -193
  211. data/lib/active_record/model_schema.rb +217 -125
  212. data/lib/active_record/nested_attributes.rb +62 -27
  213. data/lib/active_record/no_touching.rb +4 -4
  214. data/lib/active_record/normalization.rb +163 -0
  215. data/lib/active_record/persistence.rb +322 -319
  216. data/lib/active_record/promise.rb +84 -0
  217. data/lib/active_record/query_cache.rb +18 -15
  218. data/lib/active_record/query_logs.rb +193 -0
  219. data/lib/active_record/query_logs_formatter.rb +41 -0
  220. data/lib/active_record/querying.rb +54 -14
  221. data/lib/active_record/railtie.rb +250 -72
  222. data/lib/active_record/railties/console_sandbox.rb +2 -4
  223. data/lib/active_record/railties/controller_runtime.rb +25 -11
  224. data/lib/active_record/railties/databases.rake +312 -197
  225. data/lib/active_record/railties/job_runtime.rb +23 -0
  226. data/lib/active_record/readonly_attributes.rb +45 -3
  227. data/lib/active_record/reflection.rb +389 -146
  228. data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
  229. data/lib/active_record/relation/batches.rb +214 -73
  230. data/lib/active_record/relation/calculations.rb +379 -124
  231. data/lib/active_record/relation/delegation.rb +36 -23
  232. data/lib/active_record/relation/finder_methods.rb +159 -49
  233. data/lib/active_record/relation/from_clause.rb +5 -1
  234. data/lib/active_record/relation/merger.rb +41 -33
  235. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
  236. data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
  237. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
  238. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  239. data/lib/active_record/relation/predicate_builder.rb +79 -53
  240. data/lib/active_record/relation/query_attribute.rb +30 -12
  241. data/lib/active_record/relation/query_methods.rb +1156 -279
  242. data/lib/active_record/relation/record_fetch_warning.rb +12 -11
  243. data/lib/active_record/relation/spawn_methods.rb +10 -9
  244. data/lib/active_record/relation/where_clause.rb +100 -66
  245. data/lib/active_record/relation.rb +829 -194
  246. data/lib/active_record/result.rb +76 -56
  247. data/lib/active_record/runtime_registry.rb +71 -13
  248. data/lib/active_record/sanitization.rb +86 -47
  249. data/lib/active_record/schema.rb +39 -23
  250. data/lib/active_record/schema_dumper.rb +140 -33
  251. data/lib/active_record/schema_migration.rb +74 -29
  252. data/lib/active_record/scoping/default.rb +73 -19
  253. data/lib/active_record/scoping/named.rb +10 -28
  254. data/lib/active_record/scoping.rb +65 -35
  255. data/lib/active_record/secure_password.rb +60 -0
  256. data/lib/active_record/secure_token.rb +34 -8
  257. data/lib/active_record/serialization.rb +11 -4
  258. data/lib/active_record/signed_id.rb +138 -0
  259. data/lib/active_record/statement_cache.rb +26 -10
  260. data/lib/active_record/store.rb +19 -14
  261. data/lib/active_record/suppressor.rb +15 -17
  262. data/lib/active_record/table_metadata.rb +46 -36
  263. data/lib/active_record/tasks/database_tasks.rb +371 -205
  264. data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
  265. data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
  266. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
  267. data/lib/active_record/test_databases.rb +5 -4
  268. data/lib/active_record/test_fixtures.rb +189 -104
  269. data/lib/active_record/testing/query_assertions.rb +121 -0
  270. data/lib/active_record/timestamp.rb +35 -25
  271. data/lib/active_record/token_for.rb +123 -0
  272. data/lib/active_record/touch_later.rb +31 -27
  273. data/lib/active_record/transaction.rb +132 -0
  274. data/lib/active_record/transactions.rb +131 -99
  275. data/lib/active_record/translation.rb +3 -5
  276. data/lib/active_record/type/adapter_specific_registry.rb +33 -18
  277. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  278. data/lib/active_record/type/internal/timezone.rb +7 -2
  279. data/lib/active_record/type/serialized.rb +11 -6
  280. data/lib/active_record/type/time.rb +14 -0
  281. data/lib/active_record/type/type_map.rb +17 -21
  282. data/lib/active_record/type/unsigned_integer.rb +0 -1
  283. data/lib/active_record/type.rb +7 -2
  284. data/lib/active_record/type_caster/connection.rb +4 -5
  285. data/lib/active_record/type_caster/map.rb +8 -5
  286. data/lib/active_record/validations/absence.rb +1 -1
  287. data/lib/active_record/validations/associated.rb +13 -8
  288. data/lib/active_record/validations/numericality.rb +36 -0
  289. data/lib/active_record/validations/presence.rb +5 -28
  290. data/lib/active_record/validations/uniqueness.rb +88 -18
  291. data/lib/active_record/validations.rb +15 -8
  292. data/lib/active_record/version.rb +1 -1
  293. data/lib/active_record.rb +446 -40
  294. data/lib/arel/alias_predication.rb +1 -1
  295. data/lib/arel/attributes/attribute.rb +4 -8
  296. data/lib/arel/collectors/bind.rb +8 -1
  297. data/lib/arel/collectors/composite.rb +15 -0
  298. data/lib/arel/collectors/sql_string.rb +7 -0
  299. data/lib/arel/collectors/substitute_binds.rb +7 -0
  300. data/lib/arel/crud.rb +30 -22
  301. data/lib/arel/delete_manager.rb +23 -4
  302. data/lib/arel/errors.rb +10 -0
  303. data/lib/arel/factory_methods.rb +4 -0
  304. data/lib/arel/filter_predications.rb +9 -0
  305. data/lib/arel/insert_manager.rb +2 -3
  306. data/lib/arel/nodes/binary.rb +82 -9
  307. data/lib/arel/nodes/bind_param.rb +8 -0
  308. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  309. data/lib/arel/nodes/casted.rb +22 -10
  310. data/lib/arel/nodes/cte.rb +36 -0
  311. data/lib/arel/nodes/delete_statement.rb +14 -13
  312. data/lib/arel/nodes/equality.rb +6 -9
  313. data/lib/arel/nodes/filter.rb +10 -0
  314. data/lib/arel/nodes/fragments.rb +35 -0
  315. data/lib/arel/nodes/function.rb +1 -0
  316. data/lib/arel/nodes/grouping.rb +3 -0
  317. data/lib/arel/nodes/homogeneous_in.rb +68 -0
  318. data/lib/arel/nodes/in.rb +8 -1
  319. data/lib/arel/nodes/infix_operation.rb +13 -1
  320. data/lib/arel/nodes/insert_statement.rb +2 -2
  321. data/lib/arel/nodes/join_source.rb +1 -1
  322. data/lib/arel/nodes/leading_join.rb +8 -0
  323. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  324. data/lib/arel/nodes/node.rb +122 -11
  325. data/lib/arel/nodes/ordering.rb +27 -0
  326. data/lib/arel/nodes/select_core.rb +2 -2
  327. data/lib/arel/nodes/select_statement.rb +2 -2
  328. data/lib/arel/nodes/sql_literal.rb +16 -0
  329. data/lib/arel/nodes/table_alias.rb +11 -3
  330. data/lib/arel/nodes/unary.rb +0 -1
  331. data/lib/arel/nodes/update_statement.rb +11 -4
  332. data/lib/arel/nodes.rb +10 -3
  333. data/lib/arel/predications.rb +31 -28
  334. data/lib/arel/select_manager.rb +18 -9
  335. data/lib/arel/table.rb +21 -10
  336. data/lib/arel/tree_manager.rb +8 -15
  337. data/lib/arel/update_manager.rb +25 -5
  338. data/lib/arel/visitors/dot.rb +94 -90
  339. data/lib/arel/visitors/mysql.rb +34 -6
  340. data/lib/arel/visitors/postgresql.rb +5 -16
  341. data/lib/arel/visitors/sqlite.rb +25 -1
  342. data/lib/arel/visitors/to_sql.rb +227 -81
  343. data/lib/arel/visitors/visitor.rb +2 -3
  344. data/lib/arel/visitors.rb +0 -7
  345. data/lib/arel.rb +37 -15
  346. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  347. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  348. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  349. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  350. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
  351. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  352. data/lib/rails/generators/active_record/migration.rb +9 -3
  353. data/lib/rails/generators/active_record/model/USAGE +113 -0
  354. data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
  355. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  356. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  357. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  358. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  359. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  360. metadata +117 -30
  361. data/lib/active_record/attribute_decorators.rb +0 -90
  362. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  363. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  364. data/lib/active_record/define_callbacks.rb +0 -22
  365. data/lib/active_record/null_relation.rb +0 -68
  366. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  367. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  368. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  369. data/lib/arel/attributes.rb +0 -22
  370. data/lib/arel/visitors/depth_first.rb +0 -204
  371. data/lib/arel/visitors/ibm_db.rb +0 -34
  372. data/lib/arel/visitors/informix.rb +0 -62
  373. data/lib/arel/visitors/mssql.rb +0 -157
  374. data/lib/arel/visitors/oracle.rb +0 -159
  375. data/lib/arel/visitors/oracle12.rb +0 -66
  376. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  module SchemaStatements
7
7
  # Drops the database specified on the +name+ attribute
8
8
  # and creates it again using the provided +options+.
9
- def recreate_database(name, options = {}) #:nodoc:
9
+ def recreate_database(name, options = {}) # :nodoc:
10
10
  drop_database(name)
11
11
  create_database(name, options)
12
12
  end
@@ -50,11 +50,12 @@ module ActiveRecord
50
50
  #
51
51
  # Example:
52
52
  # drop_database 'matt_development'
53
- def drop_database(name) #:nodoc:
53
+ def drop_database(name) # :nodoc:
54
54
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
55
55
  end
56
56
 
57
- def drop_table(table_name, options = {}) # :nodoc:
57
+ def drop_table(table_name, **options) # :nodoc:
58
+ schema_cache.clear_data_source_cache!(table_name.to_s)
58
59
  execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
59
60
  end
60
61
 
@@ -73,11 +74,11 @@ module ActiveRecord
73
74
  FROM pg_class t
74
75
  INNER JOIN pg_index d ON t.oid = d.indrelid
75
76
  INNER JOIN pg_class i ON d.indexrelid = i.oid
76
- LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
77
- WHERE i.relkind = 'i'
77
+ LEFT JOIN pg_namespace n ON n.oid = t.relnamespace
78
+ WHERE i.relkind IN ('i', 'I')
78
79
  AND i.relname = #{index[:name]}
79
80
  AND t.relname = #{table[:name]}
80
- AND n.nspname = #{index[:schema]}
81
+ AND n.nspname = #{table[:schema]}
81
82
  SQL
82
83
  end
83
84
 
@@ -87,12 +88,12 @@ module ActiveRecord
87
88
 
88
89
  result = query(<<~SQL, "SCHEMA")
89
90
  SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
90
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment
91
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid
91
92
  FROM pg_class t
92
93
  INNER JOIN pg_index d ON t.oid = d.indrelid
93
94
  INNER JOIN pg_class i ON d.indexrelid = i.oid
94
- LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
95
- WHERE i.relkind = 'i'
95
+ LEFT JOIN pg_namespace n ON n.oid = t.relnamespace
96
+ WHERE i.relkind IN ('i', 'I')
96
97
  AND d.indisprimary = 'f'
97
98
  AND t.relname = #{scope[:name]}
98
99
  AND n.nspname = #{scope[:schema]}
@@ -106,25 +107,24 @@ module ActiveRecord
106
107
  inddef = row[3]
107
108
  oid = row[4]
108
109
  comment = row[5]
109
-
110
- using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
110
+ valid = row[6]
111
+ using, expressions, include, nulls_not_distinct, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?( NULLS NOT DISTINCT)?(?: WHERE (.+))?\z/m).flatten
111
112
 
112
113
  orders = {}
113
114
  opclasses = {}
115
+ include_columns = include ? include.split(",").map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) } : []
114
116
 
115
117
  if indkey.include?(0)
116
118
  columns = expressions
117
119
  else
118
- columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact
119
- SELECT a.attnum, a.attname
120
- FROM pg_attribute a
121
- WHERE a.attrelid = #{oid}
122
- AND a.attnum IN (#{indkey.join(",")})
123
- SQL
120
+ columns = column_names_from_column_numbers(oid, indkey)
121
+
122
+ # prevent INCLUDE columns from being matched
123
+ columns.reject! { |c| include_columns.include?(c) }
124
124
 
125
125
  # add info on sort order (only desc order is explicitly specified, asc is the default)
126
126
  # and non-default opclasses
127
- expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
127
+ expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
128
128
  opclasses[column] = opclass.to_sym if opclass
129
129
  if nulls
130
130
  orders[column] = [desc, nulls].compact.join(" ")
@@ -143,7 +143,10 @@ module ActiveRecord
143
143
  opclasses: opclasses,
144
144
  where: where,
145
145
  using: using.to_sym,
146
- comment: comment.presence
146
+ include: include_columns.presence,
147
+ nulls_not_distinct: nulls_not_distinct.present?,
148
+ comment: comment.presence,
149
+ valid: valid
147
150
  )
148
151
  end
149
152
  end
@@ -206,12 +209,20 @@ module ActiveRecord
206
209
  end
207
210
 
208
211
  # Creates a schema for the given schema name.
209
- def create_schema(schema_name)
210
- execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
212
+ def create_schema(schema_name, force: nil, if_not_exists: nil)
213
+ if force && if_not_exists
214
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
215
+ end
216
+
217
+ if force
218
+ drop_schema(schema_name, if_exists: true)
219
+ end
220
+
221
+ execute("CREATE SCHEMA#{' IF NOT EXISTS' if if_not_exists} #{quote_schema_name(schema_name)}")
211
222
  end
212
223
 
213
224
  # Drops the schema for the given schema name.
214
- def drop_schema(schema_name, options = {})
225
+ def drop_schema(schema_name, **options)
215
226
  execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
216
227
  end
217
228
 
@@ -222,7 +233,7 @@ module ActiveRecord
222
233
  # This should be not be called manually but set in database.yml.
223
234
  def schema_search_path=(schema_csv)
224
235
  if schema_csv
225
- execute("SET search_path TO #{schema_csv}", "SCHEMA")
236
+ internal_execute("SET search_path TO #{schema_csv}")
226
237
  @schema_search_path = schema_csv
227
238
  end
228
239
  end
@@ -239,11 +250,13 @@ module ActiveRecord
239
250
 
240
251
  # Set the client message level.
241
252
  def client_min_messages=(level)
242
- execute("SET client_min_messages TO '#{level}'", "SCHEMA")
253
+ internal_execute("SET client_min_messages TO '#{level}'")
243
254
  end
244
255
 
245
256
  # Returns the sequence name for a table's primary key or some other specified key.
246
- def default_sequence_name(table_name, pk = "id") #:nodoc:
257
+ def default_sequence_name(table_name, pk = "id") # :nodoc:
258
+ return nil if pk.is_a?(Array)
259
+
247
260
  result = serial_sequence(table_name, pk)
248
261
  return nil unless result
249
262
  Utils.extract_schema_qualified_name(result).to_s
@@ -256,7 +269,7 @@ module ActiveRecord
256
269
  end
257
270
 
258
271
  # Sets the sequence of a table's primary key to the specified value.
259
- def set_pk_sequence!(table, value) #:nodoc:
272
+ def set_pk_sequence!(table, value) # :nodoc:
260
273
  pk, sequence = pk_and_sequence_for(table)
261
274
 
262
275
  if pk
@@ -271,7 +284,7 @@ module ActiveRecord
271
284
  end
272
285
 
273
286
  # Resets the sequence of a table's primary key to the maximum value.
274
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
287
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) # :nodoc:
275
288
  unless pk && sequence
276
289
  default_pk, default_sequence = pk_and_sequence_for(table)
277
290
 
@@ -287,19 +300,19 @@ module ActiveRecord
287
300
  quoted_sequence = quote_table_name(sequence)
288
301
  max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
289
302
  if max_pk.nil?
290
- if database_version >= 100000
303
+ if database_version >= 10_00_00
291
304
  minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
292
305
  else
293
306
  minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
294
307
  end
295
308
  end
296
309
 
297
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
310
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
298
311
  end
299
312
  end
300
313
 
301
314
  # Returns a table's primary key and belonging sequence.
302
- def pk_and_sequence_for(table) #:nodoc:
315
+ def pk_and_sequence_for(table) # :nodoc:
303
316
  # First try looking for a sequence with a dependency on the
304
317
  # given table's primary key.
305
318
  result = query(<<~SQL, "SCHEMA")[0]
@@ -338,7 +351,7 @@ module ActiveRecord
338
351
  JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
339
352
  WHERE t.oid = #{quote(quote_table_name(table))}::regclass
340
353
  AND cons.contype = 'p'
341
- AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
354
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid'
342
355
  SQL
343
356
  end
344
357
 
@@ -374,47 +387,78 @@ module ActiveRecord
374
387
  #
375
388
  # Example:
376
389
  # rename_table('octopuses', 'octopi')
377
- def rename_table(table_name, new_name)
390
+ def rename_table(table_name, new_name, **options)
391
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
378
392
  clear_cache!
393
+ schema_cache.clear_data_source_cache!(table_name.to_s)
394
+ schema_cache.clear_data_source_cache!(new_name.to_s)
379
395
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
380
396
  pk, seq = pk_and_sequence_for(new_name)
381
397
  if pk
382
- idx = "#{table_name}_pkey"
383
- new_idx = "#{new_name}_pkey"
398
+ # PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
399
+ # truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
400
+ max_pkey_prefix = max_identifier_length - "_pkey".size
401
+ idx = "#{table_name[0, max_pkey_prefix]}_pkey"
402
+ new_idx = "#{new_name[0, max_pkey_prefix]}_pkey"
384
403
  execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
385
- if seq && seq.identifier == "#{table_name}_#{pk}_seq"
386
- new_seq = "#{new_name}_#{pk}_seq"
404
+
405
+ # PostgreSQL automatically creates a sequence for PRIMARY KEY with name consisting of
406
+ # truncated table name and "#{primary_key}_seq" suffix fitting into max_identifier_length number of characters.
407
+ max_seq_prefix = max_identifier_length - "_#{pk}_seq".size
408
+ if seq && seq.identifier == "#{table_name[0, max_seq_prefix]}_#{pk}_seq"
409
+ new_seq = "#{new_name[0, max_seq_prefix]}_#{pk}_seq"
387
410
  execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
388
411
  end
389
412
  end
390
- rename_table_indexes(table_name, new_name)
413
+ rename_table_indexes(table_name, new_name, **options)
391
414
  end
392
415
 
393
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
416
+ def add_column(table_name, column_name, type, **options) # :nodoc:
394
417
  clear_cache!
395
418
  super
396
419
  change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
397
420
  end
398
421
 
399
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
422
+ def change_column(table_name, column_name, type, **options) # :nodoc:
400
423
  clear_cache!
401
- sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
424
+ sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) }
402
425
  execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
403
426
  procs.each(&:call)
404
427
  end
405
428
 
429
+ # Builds a ChangeColumnDefinition object.
430
+ #
431
+ # This definition object contains information about the column change that would occur
432
+ # if the same arguments were passed to #change_column. See #change_column for information about
433
+ # passing a +table_name+, +column_name+, +type+ and other options that can be passed.
434
+ def build_change_column_definition(table_name, column_name, type, **options) # :nodoc:
435
+ td = create_table_definition(table_name)
436
+ cd = td.new_column_definition(column_name, type, **options)
437
+ ChangeColumnDefinition.new(cd, column_name)
438
+ end
439
+
406
440
  # Changes the default value of a table column.
407
441
  def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
408
442
  execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
409
443
  end
410
444
 
411
- def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
445
+ def build_change_column_default_definition(table_name, column_name, default_or_changes) # :nodoc:
446
+ column = column_for(table_name, column_name)
447
+ return unless column
448
+
449
+ default = extract_new_default_value(default_or_changes)
450
+ ChangeColumnDefaultDefinition.new(column, default)
451
+ end
452
+
453
+ def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
454
+ validate_change_column_null_argument!(null)
455
+
412
456
  clear_cache!
413
457
  unless null || default.nil?
414
458
  column = column_for(table_name, column_name)
415
459
  execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
416
460
  end
417
- execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
461
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
418
462
  end
419
463
 
420
464
  # Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -432,23 +476,30 @@ module ActiveRecord
432
476
  end
433
477
 
434
478
  # Renames a column in a table.
435
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
479
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
436
480
  clear_cache!
437
- execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
481
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
438
482
  rename_column_indexes(table_name, column_name, new_column_name)
439
483
  end
440
484
 
441
- def add_index(table_name, column_name, options = {}) #:nodoc:
442
- index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
443
- execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
444
- execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
445
- end
485
+ def add_index(table_name, column_name, **options) # :nodoc:
486
+ create_index = build_create_index_definition(table_name, column_name, **options)
487
+ result = execute schema_creation.accept(create_index)
488
+
489
+ index = create_index.index
490
+ execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
491
+ result
492
+ end
493
+
494
+ def build_create_index_definition(table_name, column_name, **options) # :nodoc:
495
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
496
+ CreateIndexDefinition.new(index, algorithm, if_not_exists)
446
497
  end
447
498
 
448
- def remove_index(table_name, options = {}) #:nodoc:
499
+ def remove_index(table_name, column_name = nil, **options) # :nodoc:
449
500
  table = Utils.extract_schema_qualified_name(table_name.to_s)
450
501
 
451
- if options.is_a?(Hash) && options.key?(:name)
502
+ if options.key?(:name)
452
503
  provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
453
504
 
454
505
  options[:name] = provided_index.identifier
@@ -459,14 +510,11 @@ module ActiveRecord
459
510
  end
460
511
  end
461
512
 
462
- index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
463
- algorithm =
464
- if options.is_a?(Hash) && options.key?(:algorithm)
465
- index_algorithms.fetch(options[:algorithm]) do
466
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
467
- end
468
- end
469
- execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
513
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
514
+
515
+ index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, column_name, options))
516
+
517
+ execute "DROP INDEX #{index_algorithm(options[:algorithm])} #{quote_table_name(index_to_remove)}"
470
518
  end
471
519
 
472
520
  # Renames an index of a table. Raises error if length of new
@@ -474,13 +522,25 @@ module ActiveRecord
474
522
  def rename_index(table_name, old_name, new_name)
475
523
  validate_index_length!(table_name, new_name)
476
524
 
477
- execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
525
+ schema, = extract_schema_qualified_name(table_name)
526
+ execute "ALTER INDEX #{quote_table_name(schema) + '.' if schema}#{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
527
+ end
528
+
529
+ def index_name(table_name, options) # :nodoc:
530
+ _schema, table_name = extract_schema_qualified_name(table_name.to_s)
531
+ super
532
+ end
533
+
534
+ def add_foreign_key(from_table, to_table, **options)
535
+ assert_valid_deferrable(options[:deferrable])
536
+
537
+ super
478
538
  end
479
539
 
480
540
  def foreign_keys(table_name)
481
541
  scope = quoted_scope(table_name)
482
- fk_info = exec_query(<<~SQL, "SCHEMA")
483
- SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
542
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
543
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
484
544
  FROM pg_constraint c
485
545
  JOIN pg_class t1 ON c.conrelid = t1.oid
486
546
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -494,17 +554,31 @@ module ActiveRecord
494
554
  SQL
495
555
 
496
556
  fk_info.map do |row|
557
+ to_table = Utils.unquote_identifier(row["to_table"])
558
+ conkey = row["conkey"].scan(/\d+/).map(&:to_i)
559
+ confkey = row["confkey"].scan(/\d+/).map(&:to_i)
560
+
561
+ if conkey.size > 1
562
+ column = column_names_from_column_numbers(row["conrelid"], conkey)
563
+ primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
564
+ else
565
+ column = Utils.unquote_identifier(row["column"])
566
+ primary_key = row["primary_key"]
567
+ end
568
+
497
569
  options = {
498
- column: row["column"],
570
+ column: column,
499
571
  name: row["name"],
500
- primary_key: row["primary_key"]
572
+ primary_key: primary_key
501
573
  }
502
574
 
503
575
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
504
576
  options[:on_update] = extract_foreign_key_action(row["on_update"])
577
+ options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
578
+
505
579
  options[:validate] = row["valid"]
506
580
 
507
- ForeignKeyDefinition.new(table_name, row["to_table"], options)
581
+ ForeignKeyDefinition.new(table_name, to_table, options)
508
582
  end
509
583
  end
510
584
 
@@ -516,8 +590,201 @@ module ActiveRecord
516
590
  query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
517
591
  end
518
592
 
593
+ def check_constraints(table_name) # :nodoc:
594
+ scope = quoted_scope(table_name)
595
+
596
+ check_info = internal_exec_query(<<-SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
597
+ SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid
598
+ FROM pg_constraint c
599
+ JOIN pg_class t ON c.conrelid = t.oid
600
+ JOIN pg_namespace n ON n.oid = c.connamespace
601
+ WHERE c.contype = 'c'
602
+ AND t.relname = #{scope[:name]}
603
+ AND n.nspname = #{scope[:schema]}
604
+ SQL
605
+
606
+ check_info.map do |row|
607
+ options = {
608
+ name: row["conname"],
609
+ validate: row["valid"]
610
+ }
611
+ expression = row["constraintdef"][/CHECK \((.+)\)/m, 1]
612
+
613
+ CheckConstraintDefinition.new(table_name, expression, options)
614
+ end
615
+ end
616
+
617
+ # Returns an array of exclusion constraints for the given table.
618
+ # The exclusion constraints are represented as ExclusionConstraintDefinition objects.
619
+ def exclusion_constraints(table_name)
620
+ scope = quoted_scope(table_name)
621
+
622
+ exclusion_info = internal_exec_query(<<-SQL, "SCHEMA")
623
+ SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.condeferrable, c.condeferred
624
+ FROM pg_constraint c
625
+ JOIN pg_class t ON c.conrelid = t.oid
626
+ JOIN pg_namespace n ON n.oid = c.connamespace
627
+ WHERE c.contype = 'x'
628
+ AND t.relname = #{scope[:name]}
629
+ AND n.nspname = #{scope[:schema]}
630
+ SQL
631
+
632
+ exclusion_info.map do |row|
633
+ method_and_elements, predicate = row["constraintdef"].split(" WHERE ")
634
+ method_and_elements_parts = method_and_elements.match(/EXCLUDE(?: USING (?<using>\S+))? \((?<expression>.+)\)/)
635
+ predicate.remove!(/ DEFERRABLE(?: INITIALLY (?:IMMEDIATE|DEFERRED))?/) if predicate
636
+ predicate = predicate.from(2).to(-3) if predicate # strip 2 opening and closing parentheses
637
+
638
+ deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
639
+
640
+ options = {
641
+ name: row["conname"],
642
+ using: method_and_elements_parts["using"].to_sym,
643
+ where: predicate,
644
+ deferrable: deferrable
645
+ }
646
+
647
+ ExclusionConstraintDefinition.new(table_name, method_and_elements_parts["expression"], options)
648
+ end
649
+ end
650
+
651
+ # Returns an array of unique constraints for the given table.
652
+ # The unique constraints are represented as UniqueConstraintDefinition objects.
653
+ def unique_constraints(table_name)
654
+ scope = quoted_scope(table_name)
655
+
656
+ unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
657
+ SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
658
+ FROM pg_constraint c
659
+ JOIN pg_class t ON c.conrelid = t.oid
660
+ JOIN pg_namespace n ON n.oid = c.connamespace
661
+ WHERE c.contype = 'u'
662
+ AND t.relname = #{scope[:name]}
663
+ AND n.nspname = #{scope[:schema]}
664
+ SQL
665
+
666
+ unique_info.map do |row|
667
+ conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
668
+ columns = column_names_from_column_numbers(row["conrelid"], conkey)
669
+
670
+ deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
671
+
672
+ options = {
673
+ name: row["conname"],
674
+ deferrable: deferrable
675
+ }
676
+
677
+ UniqueConstraintDefinition.new(table_name, columns, options)
678
+ end
679
+ end
680
+
681
+ # Adds a new exclusion constraint to the table. +expression+ is a String
682
+ # representation of a list of exclusion elements and operators.
683
+ #
684
+ # add_exclusion_constraint :products, "price WITH =, availability_range WITH &&", using: :gist, name: "price_check"
685
+ #
686
+ # generates:
687
+ #
688
+ # ALTER TABLE "products" ADD CONSTRAINT price_check EXCLUDE USING gist (price WITH =, availability_range WITH &&)
689
+ #
690
+ # The +options+ hash can include the following keys:
691
+ # [<tt>:name</tt>]
692
+ # The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
693
+ # [<tt>:deferrable</tt>]
694
+ # Specify whether or not the exclusion constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
695
+ # [<tt>:using</tt>]
696
+ # Specify which index method to use when creating this exclusion constraint (e.g. +:btree+, +:gist+ etc).
697
+ # [<tt>:where</tt>]
698
+ # Specify an exclusion constraint on a subset of the table (internally PostgreSQL creates a partial index for this).
699
+ def add_exclusion_constraint(table_name, expression, **options)
700
+ options = exclusion_constraint_options(table_name, expression, options)
701
+ at = create_alter_table(table_name)
702
+ at.add_exclusion_constraint(expression, options)
703
+
704
+ execute schema_creation.accept(at)
705
+ end
706
+
707
+ def exclusion_constraint_options(table_name, expression, options) # :nodoc:
708
+ assert_valid_deferrable(options[:deferrable])
709
+
710
+ options = options.dup
711
+ options[:name] ||= exclusion_constraint_name(table_name, expression: expression, **options)
712
+ options
713
+ end
714
+
715
+ # Removes the given exclusion constraint from the table.
716
+ #
717
+ # remove_exclusion_constraint :products, name: "price_check"
718
+ #
719
+ # The +expression+ parameter will be ignored if present. It can be helpful
720
+ # to provide this in a migration's +change+ method so it can be reverted.
721
+ # In that case, +expression+ will be used by #add_exclusion_constraint.
722
+ def remove_exclusion_constraint(table_name, expression = nil, **options)
723
+ excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
724
+
725
+ at = create_alter_table(table_name)
726
+ at.drop_exclusion_constraint(excl_name_to_delete)
727
+
728
+ execute schema_creation.accept(at)
729
+ end
730
+
731
+ # Adds a new unique constraint to the table.
732
+ #
733
+ # add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
734
+ #
735
+ # generates:
736
+ #
737
+ # ALTER TABLE "sections" ADD CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY DEFERRED
738
+ #
739
+ # If you want to change an existing unique index to deferrable, you can use :using_index to create deferrable unique constraints.
740
+ #
741
+ # add_unique_constraint :sections, deferrable: :deferred, name: "unique_position", using_index: "index_sections_on_position"
742
+ #
743
+ # The +options+ hash can include the following keys:
744
+ # [<tt>:name</tt>]
745
+ # The constraint name. Defaults to <tt>uniq_rails_<identifier></tt>.
746
+ # [<tt>:deferrable</tt>]
747
+ # Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
748
+ # [<tt>:using_index</tt>]
749
+ # To specify an existing unique index name. Defaults to +nil+.
750
+ def add_unique_constraint(table_name, column_name = nil, **options)
751
+ options = unique_constraint_options(table_name, column_name, options)
752
+ at = create_alter_table(table_name)
753
+ at.add_unique_constraint(column_name, options)
754
+
755
+ execute schema_creation.accept(at)
756
+ end
757
+
758
+ def unique_constraint_options(table_name, column_name, options) # :nodoc:
759
+ assert_valid_deferrable(options[:deferrable])
760
+
761
+ if column_name && options[:using_index]
762
+ raise ArgumentError, "Cannot specify both column_name and :using_index options."
763
+ end
764
+
765
+ options = options.dup
766
+ options[:name] ||= unique_constraint_name(table_name, column: column_name, **options)
767
+ options
768
+ end
769
+
770
+ # Removes the given unique constraint from the table.
771
+ #
772
+ # remove_unique_constraint :sections, name: "unique_position"
773
+ #
774
+ # The +column_name+ parameter will be ignored if present. It can be helpful
775
+ # to provide this in a migration's +change+ method so it can be reverted.
776
+ # In that case, +column_name+ will be used by #add_unique_constraint.
777
+ def remove_unique_constraint(table_name, column_name = nil, **options)
778
+ unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
779
+
780
+ at = create_alter_table(table_name)
781
+ at.drop_unique_constraint(unique_name_to_delete)
782
+
783
+ execute schema_creation.accept(at)
784
+ end
785
+
519
786
  # Maps logical Rails types to PostgreSQL-specific data types.
520
- def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
787
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, enum_type: nil, **) # :nodoc:
521
788
  sql = \
522
789
  case type.to_s
523
790
  when "binary"
@@ -541,6 +808,10 @@ module ActiveRecord
541
808
  when 5..8; "bigint"
542
809
  else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
543
810
  end
811
+ when "enum"
812
+ raise ArgumentError, "enum_type is required for enums" if enum_type.nil?
813
+
814
+ enum_type
544
815
  else
545
816
  super
546
817
  end
@@ -551,14 +822,14 @@ module ActiveRecord
551
822
 
552
823
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
553
824
  # requires that the ORDER BY include the distinct column.
554
- def columns_for_distinct(columns, orders) #:nodoc:
555
- order_columns = orders.reject(&:blank?).map { |s|
556
- # Convert Arel node to string
557
- s = s.to_sql unless s.is_a?(String)
558
- # Remove any ASC/DESC modifiers
559
- s.gsub(/\s+(?:ASC|DESC)\b/i, "")
560
- .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
561
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
825
+ def columns_for_distinct(columns, orders) # :nodoc:
826
+ order_columns = orders.compact_blank.map { |s|
827
+ # Convert Arel node to string
828
+ s = visitor.compile(s) unless s.is_a?(String)
829
+ # Remove any ASC/DESC modifiers
830
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
831
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
832
+ }.compact_blank.map.with_index { |column, i| "#{column} AS alias_#{i}" }
562
833
 
563
834
  (order_columns << super).join(", ")
564
835
  end
@@ -577,8 +848,6 @@ module ActiveRecord
577
848
  #
578
849
  # validate_constraint :accounts, :constraint_name
579
850
  def validate_constraint(table_name, constraint_name)
580
- return unless supports_validate_constraints?
581
-
582
851
  at = create_alter_table table_name
583
852
  at.validate_constraint constraint_name
584
853
 
@@ -601,31 +870,66 @@ module ActiveRecord
601
870
  #
602
871
  # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
603
872
  def validate_foreign_key(from_table, to_table = nil, **options)
604
- return unless supports_validate_constraints?
605
-
606
873
  fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name
607
874
 
608
875
  validate_constraint from_table, fk_name_to_validate
609
876
  end
610
877
 
611
- private
612
- def schema_creation
613
- PostgreSQL::SchemaCreation.new(self)
878
+ # Validates the given check constraint.
879
+ #
880
+ # validate_check_constraint :products, name: "price_check"
881
+ #
882
+ # The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
883
+ def validate_check_constraint(table_name, **options)
884
+ chk_name_to_validate = check_constraint_for!(table_name, **options).name
885
+
886
+ validate_constraint table_name, chk_name_to_validate
887
+ end
888
+
889
+ def foreign_key_column_for(table_name, column_name) # :nodoc:
890
+ _schema, table_name = extract_schema_qualified_name(table_name)
891
+ super
892
+ end
893
+
894
+ def add_index_options(table_name, column_name, **options) # :nodoc:
895
+ if (where = options[:where]) && table_exists?(table_name) && column_exists?(table_name, where)
896
+ options[:where] = quote_column_name(where)
897
+ end
898
+ super
899
+ end
900
+
901
+ def quoted_include_columns_for_index(column_names) # :nodoc:
902
+ return quote_column_name(column_names) if column_names.is_a?(Symbol)
903
+
904
+ quoted_columns = column_names.each_with_object({}) do |name, result|
905
+ result[name.to_sym] = quote_column_name(name).dup
614
906
  end
907
+ add_options_for_index_columns(quoted_columns).values.join(", ")
908
+ end
615
909
 
616
- def create_table_definition(*args)
617
- PostgreSQL::TableDefinition.new(self, *args)
910
+ def schema_creation # :nodoc:
911
+ PostgreSQL::SchemaCreation.new(self)
912
+ end
913
+
914
+ private
915
+ def create_table_definition(name, **options)
916
+ PostgreSQL::TableDefinition.new(self, name, **options)
618
917
  end
619
918
 
620
919
  def create_alter_table(name)
621
920
  PostgreSQL::AlterTable.new create_table_definition(name)
622
921
  end
623
922
 
624
- def new_column_from_field(table_name, field)
625
- column_name, type, default, notnull, oid, fmod, collation, comment = field
923
+ def new_column_from_field(table_name, field, _definitions)
924
+ column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated = field
626
925
  type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
627
926
  default_value = extract_value_from_default(default)
628
- default_function = extract_default_function(default_value, default)
927
+
928
+ if attgenerated.present?
929
+ default_function = default
930
+ else
931
+ default_function = extract_default_function(default_value, default)
932
+ end
629
933
 
630
934
  if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
631
935
  serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
@@ -639,7 +943,9 @@ module ActiveRecord
639
943
  default_function,
640
944
  collation: collation,
641
945
  comment: comment.presence,
642
- serial: serial
946
+ serial: serial,
947
+ identity: identity.presence,
948
+ generated: attgenerated
643
949
  )
644
950
  end
645
951
 
@@ -679,62 +985,91 @@ module ActiveRecord
679
985
  end
680
986
  end
681
987
 
682
- def add_column_for_alter(table_name, column_name, type, options = {})
988
+ def assert_valid_deferrable(deferrable)
989
+ return if !deferrable || %i(immediate deferred).include?(deferrable)
990
+
991
+ raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
992
+ end
993
+
994
+ def extract_constraint_deferrable(deferrable, deferred)
995
+ deferrable && (deferred ? :deferred : :immediate)
996
+ end
997
+
998
+ def reference_name_for_table(table_name)
999
+ _schema, table_name = extract_schema_qualified_name(table_name.to_s)
1000
+ table_name.singularize
1001
+ end
1002
+
1003
+ def add_column_for_alter(table_name, column_name, type, **options)
683
1004
  return super unless options.key?(:comment)
684
1005
  [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
685
1006
  end
686
1007
 
687
- def change_column_for_alter(table_name, column_name, type, options = {})
688
- td = create_table_definition(table_name)
689
- cd = td.new_column_definition(column_name, type, options)
690
- sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
1008
+ def change_column_for_alter(table_name, column_name, type, **options)
1009
+ change_col_def = build_change_column_definition(table_name, column_name, type, **options)
1010
+ sqls = [schema_creation.accept(change_col_def)]
691
1011
  sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
692
1012
  sqls
693
1013
  end
694
1014
 
695
- def change_column_default_for_alter(table_name, column_name, default_or_changes)
696
- column = column_for(table_name, column_name)
697
- return unless column
698
-
699
- default = extract_new_default_value(default_or_changes)
700
- alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
1015
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
701
1016
  if default.nil?
702
- # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
703
- # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
704
- alter_column_query % "DROP DEFAULT"
1017
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
705
1018
  else
706
- alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
1019
+ Proc.new { change_column_null(table_name, column_name, null, default) }
707
1020
  end
708
1021
  end
709
1022
 
710
- def change_column_null_for_alter(table_name, column_name, null, default = nil)
711
- "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
1023
+ def add_index_opclass(quoted_columns, **options)
1024
+ opclasses = options_for_index_columns(options[:opclass])
1025
+ quoted_columns.each do |name, column|
1026
+ column << " #{opclasses[name]}" if opclasses[name].present?
1027
+ end
712
1028
  end
713
1029
 
714
- def add_timestamps_for_alter(table_name, options = {})
715
- options[:null] = false if options[:null].nil?
1030
+ def add_options_for_index_columns(quoted_columns, **options)
1031
+ quoted_columns = add_index_opclass(quoted_columns, **options)
1032
+ super
1033
+ end
1034
+
1035
+ def exclusion_constraint_name(table_name, **options)
1036
+ options.fetch(:name) do
1037
+ expression = options.fetch(:expression)
1038
+ identifier = "#{table_name}_#{expression}_excl"
1039
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
716
1040
 
717
- if !options.key?(:precision) && supports_datetime_with_precision?
718
- options[:precision] = 6
1041
+ "excl_rails_#{hashed_identifier}"
719
1042
  end
1043
+ end
720
1044
 
721
- [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
1045
+ def exclusion_constraint_for(table_name, **options)
1046
+ excl_name = exclusion_constraint_name(table_name, **options)
1047
+ exclusion_constraints(table_name).detect { |excl| excl.name == excl_name }
722
1048
  end
723
1049
 
724
- def remove_timestamps_for_alter(table_name, options = {})
725
- [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
1050
+ def exclusion_constraint_for!(table_name, expression: nil, **options)
1051
+ exclusion_constraint_for(table_name, expression: expression, **options) ||
1052
+ raise(ArgumentError, "Table '#{table_name}' has no exclusion constraint for #{expression || options}")
726
1053
  end
727
1054
 
728
- def add_index_opclass(quoted_columns, **options)
729
- opclasses = options_for_index_columns(options[:opclass])
730
- quoted_columns.each do |name, column|
731
- column << " #{opclasses[name]}" if opclasses[name].present?
1055
+ def unique_constraint_name(table_name, **options)
1056
+ options.fetch(:name) do
1057
+ column_or_index = Array(options[:column] || options[:using_index]).map(&:to_s)
1058
+ identifier = "#{table_name}_#{column_or_index * '_and_'}_unique"
1059
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1060
+
1061
+ "uniq_rails_#{hashed_identifier}"
732
1062
  end
733
1063
  end
734
1064
 
735
- def add_options_for_index_columns(quoted_columns, **options)
736
- quoted_columns = add_index_opclass(quoted_columns, options)
737
- super
1065
+ def unique_constraint_for(table_name, **options)
1066
+ name = unique_constraint_name(table_name, **options) unless options.key?(:column)
1067
+ unique_constraints(table_name).detect { |unique_constraint| unique_constraint.defined_for?(name: name, **options) }
1068
+ end
1069
+
1070
+ def unique_constraint_for!(table_name, column: nil, **options)
1071
+ unique_constraint_for(table_name, column: column, **options) ||
1072
+ raise(ArgumentError, "Table '#{table_name}' has no unique constraint for #{column || options}")
738
1073
  end
739
1074
 
740
1075
  def data_source_sql(name = nil, type: nil)
@@ -770,6 +1105,15 @@ module ActiveRecord
770
1105
  name = Utils.extract_schema_qualified_name(string.to_s)
771
1106
  [name.schema, name.identifier]
772
1107
  end
1108
+
1109
+ def column_names_from_column_numbers(table_oid, column_numbers)
1110
+ Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
1111
+ SELECT a.attnum, a.attname
1112
+ FROM pg_attribute a
1113
+ WHERE a.attrelid = #{table_oid}
1114
+ AND a.attnum IN (#{column_numbers.join(", ")})
1115
+ SQL
1116
+ end
773
1117
  end
774
1118
  end
775
1119
  end