activerecord 6.1.7 → 7.2.2

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 (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  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 +19 -8
  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 +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  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 +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  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 +15 -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 +57 -54
  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 +19 -35
  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 +325 -604
  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 +230 -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 +378 -143
  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 +348 -165
  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 +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  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 +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  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 +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  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 +59 -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 +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  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 +18 -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 +229 -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 +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  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 +870 -163
  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 +6 -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 +288 -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 +65 -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/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. 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,13 @@ 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:
258
+ return nil if pk.is_a?(Array)
259
+
248
260
  result = serial_sequence(table_name, pk)
249
261
  return nil unless result
250
262
  Utils.extract_schema_qualified_name(result).to_s
@@ -257,7 +269,7 @@ module ActiveRecord
257
269
  end
258
270
 
259
271
  # Sets the sequence of a table's primary key to the specified value.
260
- def set_pk_sequence!(table, value) #:nodoc:
272
+ def set_pk_sequence!(table, value) # :nodoc:
261
273
  pk, sequence = pk_and_sequence_for(table)
262
274
 
263
275
  if pk
@@ -272,7 +284,7 @@ module ActiveRecord
272
284
  end
273
285
 
274
286
  # Resets the sequence of a table's primary key to the maximum value.
275
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
287
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) # :nodoc:
276
288
  unless pk && sequence
277
289
  default_pk, default_sequence = pk_and_sequence_for(table)
278
290
 
@@ -288,19 +300,19 @@ module ActiveRecord
288
300
  quoted_sequence = quote_table_name(sequence)
289
301
  max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
290
302
  if max_pk.nil?
291
- if database_version >= 100000
303
+ if database_version >= 10_00_00
292
304
  minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
293
305
  else
294
306
  minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
295
307
  end
296
308
  end
297
309
 
298
- 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")
299
311
  end
300
312
  end
301
313
 
302
314
  # Returns a table's primary key and belonging sequence.
303
- def pk_and_sequence_for(table) #:nodoc:
315
+ def pk_and_sequence_for(table) # :nodoc:
304
316
  # First try looking for a sequence with a dependency on the
305
317
  # given table's primary key.
306
318
  result = query(<<~SQL, "SCHEMA")[0]
@@ -339,7 +351,7 @@ module ActiveRecord
339
351
  JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
340
352
  WHERE t.oid = #{quote(quote_table_name(table))}::regclass
341
353
  AND cons.contype = 'p'
342
- 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'
343
355
  SQL
344
356
  end
345
357
 
@@ -375,49 +387,78 @@ module ActiveRecord
375
387
  #
376
388
  # Example:
377
389
  # rename_table('octopuses', 'octopi')
378
- 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]
379
392
  clear_cache!
380
393
  schema_cache.clear_data_source_cache!(table_name.to_s)
381
394
  schema_cache.clear_data_source_cache!(new_name.to_s)
382
395
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
383
396
  pk, seq = pk_and_sequence_for(new_name)
384
397
  if pk
385
- idx = "#{table_name}_pkey"
386
- 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"
387
403
  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"
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"
390
410
  execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
391
411
  end
392
412
  end
393
- rename_table_indexes(table_name, new_name)
413
+ rename_table_indexes(table_name, new_name, **options)
394
414
  end
395
415
 
396
- def add_column(table_name, column_name, type, **options) #:nodoc:
416
+ def add_column(table_name, column_name, type, **options) # :nodoc:
397
417
  clear_cache!
398
418
  super
399
419
  change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
400
420
  end
401
421
 
402
- def change_column(table_name, column_name, type, **options) #:nodoc:
422
+ def change_column(table_name, column_name, type, **options) # :nodoc:
403
423
  clear_cache!
404
424
  sqls, procs = Array(change_column_for_alter(table_name, column_name, type, **options)).partition { |v| v.is_a?(String) }
405
425
  execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
406
426
  procs.each(&:call)
407
427
  end
408
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
+
409
440
  # Changes the default value of a table column.
410
441
  def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
411
442
  execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
412
443
  end
413
444
 
414
- 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
+
415
456
  clear_cache!
416
457
  unless null || default.nil?
417
458
  column = column_for(table_name, column_name)
418
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
419
460
  end
420
- 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"
421
462
  end
422
463
 
423
464
  # Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -435,22 +476,26 @@ module ActiveRecord
435
476
  end
436
477
 
437
478
  # Renames a column in a table.
438
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
479
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
439
480
  clear_cache!
440
481
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
441
482
  rename_column_indexes(table_name, column_name, new_column_name)
442
483
  end
443
484
 
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)
485
+ def add_index(table_name, column_name, **options) # :nodoc:
486
+ create_index = build_create_index_definition(table_name, column_name, **options)
448
487
  result = execute schema_creation.accept(create_index)
449
488
 
489
+ index = create_index.index
450
490
  execute "COMMENT ON INDEX #{quote_column_name(index.name)} IS #{quote(index.comment)}" if index.comment
451
491
  result
452
492
  end
453
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)
497
+ end
498
+
454
499
  def remove_index(table_name, column_name = nil, **options) # :nodoc:
455
500
  table = Utils.extract_schema_qualified_name(table_name.to_s)
456
501
 
@@ -477,13 +522,25 @@ module ActiveRecord
477
522
  def rename_index(table_name, old_name, new_name)
478
523
  validate_index_length!(table_name, new_name)
479
524
 
480
- 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
481
538
  end
482
539
 
483
540
  def foreign_keys(table_name)
484
541
  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
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
487
544
  FROM pg_constraint c
488
545
  JOIN pg_class t1 ON c.conrelid = t1.oid
489
546
  JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -497,17 +554,31 @@ module ActiveRecord
497
554
  SQL
498
555
 
499
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
+
500
569
  options = {
501
- column: row["column"],
570
+ column: column,
502
571
  name: row["name"],
503
- primary_key: row["primary_key"]
572
+ primary_key: primary_key
504
573
  }
505
574
 
506
575
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
507
576
  options[:on_update] = extract_foreign_key_action(row["on_update"])
577
+ options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
578
+
508
579
  options[:validate] = row["valid"]
509
580
 
510
- ForeignKeyDefinition.new(table_name, row["to_table"], options)
581
+ ForeignKeyDefinition.new(table_name, to_table, options)
511
582
  end
512
583
  end
513
584
 
@@ -522,12 +593,14 @@ module ActiveRecord
522
593
  def check_constraints(table_name) # :nodoc:
523
594
  scope = quoted_scope(table_name)
524
595
 
525
- check_info = exec_query(<<-SQL, "SCHEMA")
526
- SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
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
527
598
  FROM pg_constraint c
528
599
  JOIN pg_class t ON c.conrelid = t.oid
600
+ JOIN pg_namespace n ON n.oid = c.connamespace
529
601
  WHERE c.contype = 'c'
530
602
  AND t.relname = #{scope[:name]}
603
+ AND n.nspname = #{scope[:schema]}
531
604
  SQL
532
605
 
533
606
  check_info.map do |row|
@@ -535,14 +608,183 @@ module ActiveRecord
535
608
  name: row["conname"],
536
609
  validate: row["valid"]
537
610
  }
538
- expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
611
+ expression = row["constraintdef"][/CHECK \((.+)\)/m, 1]
539
612
 
540
613
  CheckConstraintDefinition.new(table_name, expression, options)
541
614
  end
542
615
  end
543
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
+
544
786
  # Maps logical Rails types to PostgreSQL-specific data types.
545
- 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:
546
788
  sql = \
547
789
  case type.to_s
548
790
  when "binary"
@@ -566,6 +808,10 @@ module ActiveRecord
566
808
  when 5..8; "bigint"
567
809
  else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
568
810
  end
811
+ when "enum"
812
+ raise ArgumentError, "enum_type is required for enums" if enum_type.nil?
813
+
814
+ enum_type
569
815
  else
570
816
  super
571
817
  end
@@ -576,7 +822,7 @@ module ActiveRecord
576
822
 
577
823
  # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
578
824
  # requires that the ORDER BY include the distinct column.
579
- def columns_for_distinct(columns, orders) #:nodoc:
825
+ def columns_for_distinct(columns, orders) # :nodoc:
580
826
  order_columns = orders.compact_blank.map { |s|
581
827
  # Convert Arel node to string
582
828
  s = visitor.compile(s) unless s.is_a?(String)
@@ -640,11 +886,32 @@ module ActiveRecord
640
886
  validate_constraint table_name, chk_name_to_validate
641
887
  end
642
888
 
643
- private
644
- def schema_creation
645
- PostgreSQL::SchemaCreation.new(self)
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
646
906
  end
907
+ add_options_for_index_columns(quoted_columns).values.join(", ")
908
+ end
909
+
910
+ def schema_creation # :nodoc:
911
+ PostgreSQL::SchemaCreation.new(self)
912
+ end
647
913
 
914
+ private
648
915
  def create_table_definition(name, **options)
649
916
  PostgreSQL::TableDefinition.new(self, name, **options)
650
917
  end
@@ -653,11 +920,16 @@ module ActiveRecord
653
920
  PostgreSQL::AlterTable.new create_table_definition(name)
654
921
  end
655
922
 
656
- def new_column_from_field(table_name, field)
657
- 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
658
925
  type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
659
926
  default_value = extract_value_from_default(default)
660
- 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
661
933
 
662
934
  if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
663
935
  serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
@@ -671,7 +943,9 @@ module ActiveRecord
671
943
  default_function,
672
944
  collation: collation,
673
945
  comment: comment.presence,
674
- serial: serial
946
+ serial: serial,
947
+ identity: identity.presence,
948
+ generated: attgenerated
675
949
  )
676
950
  end
677
951
 
@@ -711,38 +985,41 @@ module ActiveRecord
711
985
  end
712
986
  end
713
987
 
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
+
714
1003
  def add_column_for_alter(table_name, column_name, type, **options)
715
1004
  return super unless options.key?(:comment)
716
1005
  [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
717
1006
  end
718
1007
 
719
1008
  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))]
1009
+ change_col_def = build_change_column_definition(table_name, column_name, type, **options)
1010
+ sqls = [schema_creation.accept(change_col_def)]
723
1011
  sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
724
1012
  sqls
725
1013
  end
726
1014
 
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"
1015
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
733
1016
  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"
1017
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
737
1018
  else
738
- alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
1019
+ Proc.new { change_column_null(table_name, column_name, null, default) }
739
1020
  end
740
1021
  end
741
1022
 
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
1023
  def add_index_opclass(quoted_columns, **options)
747
1024
  opclasses = options_for_index_columns(options[:opclass])
748
1025
  quoted_columns.each do |name, column|
@@ -755,6 +1032,46 @@ module ActiveRecord
755
1032
  super
756
1033
  end
757
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)
1040
+
1041
+ "excl_rails_#{hashed_identifier}"
1042
+ end
1043
+ end
1044
+
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 }
1048
+ end
1049
+
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}")
1053
+ end
1054
+
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}"
1062
+ end
1063
+ end
1064
+
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}")
1073
+ end
1074
+
758
1075
  def data_source_sql(name = nil, type: nil)
759
1076
  scope = quoted_scope(name, type: type)
760
1077
  scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
@@ -788,6 +1105,15 @@ module ActiveRecord
788
1105
  name = Utils.extract_schema_qualified_name(string.to_s)
789
1106
  [name.schema, name.identifier]
790
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
791
1117
  end
792
1118
  end
793
1119
  end