activerecord 6.1.7 → 7.2.0

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