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