activerecord 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (340) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1013 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +219 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +195 -0
  8. data/lib/active_record/aggregations.rb +285 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1865 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +332 -0
  13. data/lib/active_record/associations/association_scope.rb +166 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +124 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +36 -0
  16. data/lib/active_record/associations/builder/association.rb +136 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +130 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +72 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +114 -0
  20. data/lib/active_record/associations/builder/has_many.rb +19 -0
  21. data/lib/active_record/associations/builder/has_one.rb +64 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +44 -0
  23. data/lib/active_record/associations/collection_association.rb +498 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1128 -0
  25. data/lib/active_record/associations/foreign_association.rb +20 -0
  26. data/lib/active_record/associations/has_many_association.rb +136 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +220 -0
  28. data/lib/active_record/associations/has_one_association.rb +118 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +258 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +80 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +201 -0
  35. data/lib/active_record/associations/preloader/association.rb +133 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +116 -0
  37. data/lib/active_record/associations/singular_association.rb +59 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +85 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +420 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +81 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +221 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +136 -0
  45. data/lib/active_record/attribute_methods/query.rb +41 -0
  46. data/lib/active_record/attribute_methods/read.rb +47 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +61 -0
  50. data/lib/active_record/attributes.rb +279 -0
  51. data/lib/active_record/autosave_association.rb +508 -0
  52. data/lib/active_record/base.rb +328 -0
  53. data/lib/active_record/callbacks.rb +339 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1165 -0
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +85 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +512 -0
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +154 -0
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +251 -0
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +713 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +93 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1475 -0
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +323 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +761 -0
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +821 -0
  69. data/lib/active_record/connection_adapters/column.rb +95 -0
  70. data/lib/active_record/connection_adapters/connection_specification.rb +297 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +200 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +146 -0
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +182 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +113 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +205 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +222 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +776 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +949 -0
  117. data/lib/active_record/connection_adapters/schema_cache.rb +141 -0
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
  119. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +557 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +267 -0
  129. data/lib/active_record/core.rb +599 -0
  130. data/lib/active_record/counter_cache.rb +193 -0
  131. data/lib/active_record/database_configurations.rb +233 -0
  132. data/lib/active_record/database_configurations/database_config.rb +37 -0
  133. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  134. data/lib/active_record/database_configurations/url_config.rb +79 -0
  135. data/lib/active_record/define_callbacks.rb +22 -0
  136. data/lib/active_record/dynamic_matchers.rb +122 -0
  137. data/lib/active_record/enum.rb +274 -0
  138. data/lib/active_record/errors.rb +388 -0
  139. data/lib/active_record/explain.rb +50 -0
  140. data/lib/active_record/explain_registry.rb +32 -0
  141. data/lib/active_record/explain_subscriber.rb +34 -0
  142. data/lib/active_record/fixture_set/file.rb +82 -0
  143. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  144. data/lib/active_record/fixture_set/render_context.rb +17 -0
  145. data/lib/active_record/fixture_set/table_row.rb +153 -0
  146. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  147. data/lib/active_record/fixtures.rb +738 -0
  148. data/lib/active_record/gem_version.rb +17 -0
  149. data/lib/active_record/inheritance.rb +293 -0
  150. data/lib/active_record/insert_all.rb +179 -0
  151. data/lib/active_record/integration.rb +207 -0
  152. data/lib/active_record/internal_metadata.rb +53 -0
  153. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  154. data/lib/active_record/locale/en.yml +48 -0
  155. data/lib/active_record/locking/optimistic.rb +197 -0
  156. data/lib/active_record/locking/pessimistic.rb +89 -0
  157. data/lib/active_record/log_subscriber.rb +118 -0
  158. data/lib/active_record/middleware/database_selector.rb +75 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  160. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  161. data/lib/active_record/migration.rb +1397 -0
  162. data/lib/active_record/migration/command_recorder.rb +284 -0
  163. data/lib/active_record/migration/compatibility.rb +244 -0
  164. data/lib/active_record/migration/join_table.rb +17 -0
  165. data/lib/active_record/model_schema.rb +542 -0
  166. data/lib/active_record/nested_attributes.rb +600 -0
  167. data/lib/active_record/no_touching.rb +65 -0
  168. data/lib/active_record/null_relation.rb +68 -0
  169. data/lib/active_record/persistence.rb +967 -0
  170. data/lib/active_record/query_cache.rb +52 -0
  171. data/lib/active_record/querying.rb +82 -0
  172. data/lib/active_record/railtie.rb +263 -0
  173. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  174. data/lib/active_record/railties/console_sandbox.rb +7 -0
  175. data/lib/active_record/railties/controller_runtime.rb +51 -0
  176. data/lib/active_record/railties/databases.rake +527 -0
  177. data/lib/active_record/readonly_attributes.rb +24 -0
  178. data/lib/active_record/reflection.rb +1042 -0
  179. data/lib/active_record/relation.rb +859 -0
  180. data/lib/active_record/relation/batches.rb +290 -0
  181. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  182. data/lib/active_record/relation/calculations.rb +424 -0
  183. data/lib/active_record/relation/delegation.rb +130 -0
  184. data/lib/active_record/relation/finder_methods.rb +552 -0
  185. data/lib/active_record/relation/from_clause.rb +26 -0
  186. data/lib/active_record/relation/merger.rb +184 -0
  187. data/lib/active_record/relation/predicate_builder.rb +150 -0
  188. data/lib/active_record/relation/predicate_builder/array_handler.rb +49 -0
  189. data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
  190. data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -0
  191. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +53 -0
  193. data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
  194. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  195. data/lib/active_record/relation/query_attribute.rb +50 -0
  196. data/lib/active_record/relation/query_methods.rb +1359 -0
  197. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  198. data/lib/active_record/relation/spawn_methods.rb +77 -0
  199. data/lib/active_record/relation/where_clause.rb +190 -0
  200. data/lib/active_record/relation/where_clause_factory.rb +33 -0
  201. data/lib/active_record/result.rb +168 -0
  202. data/lib/active_record/runtime_registry.rb +24 -0
  203. data/lib/active_record/sanitization.rb +214 -0
  204. data/lib/active_record/schema.rb +61 -0
  205. data/lib/active_record/schema_dumper.rb +270 -0
  206. data/lib/active_record/schema_migration.rb +60 -0
  207. data/lib/active_record/scoping.rb +106 -0
  208. data/lib/active_record/scoping/default.rb +151 -0
  209. data/lib/active_record/scoping/named.rb +217 -0
  210. data/lib/active_record/secure_token.rb +40 -0
  211. data/lib/active_record/serialization.rb +22 -0
  212. data/lib/active_record/statement_cache.rb +148 -0
  213. data/lib/active_record/store.rb +290 -0
  214. data/lib/active_record/suppressor.rb +61 -0
  215. data/lib/active_record/table_metadata.rb +75 -0
  216. data/lib/active_record/tasks/database_tasks.rb +506 -0
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +141 -0
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +77 -0
  220. data/lib/active_record/test_databases.rb +23 -0
  221. data/lib/active_record/test_fixtures.rb +224 -0
  222. data/lib/active_record/timestamp.rb +167 -0
  223. data/lib/active_record/touch_later.rb +66 -0
  224. data/lib/active_record/transactions.rb +493 -0
  225. data/lib/active_record/translation.rb +24 -0
  226. data/lib/active_record/type.rb +78 -0
  227. data/lib/active_record/type/adapter_specific_registry.rb +129 -0
  228. data/lib/active_record/type/date.rb +9 -0
  229. data/lib/active_record/type/date_time.rb +9 -0
  230. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  231. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  232. data/lib/active_record/type/internal/timezone.rb +17 -0
  233. data/lib/active_record/type/json.rb +30 -0
  234. data/lib/active_record/type/serialized.rb +71 -0
  235. data/lib/active_record/type/text.rb +11 -0
  236. data/lib/active_record/type/time.rb +21 -0
  237. data/lib/active_record/type/type_map.rb +62 -0
  238. data/lib/active_record/type/unsigned_integer.rb +17 -0
  239. data/lib/active_record/type_caster.rb +9 -0
  240. data/lib/active_record/type_caster/connection.rb +34 -0
  241. data/lib/active_record/type_caster/map.rb +20 -0
  242. data/lib/active_record/validations.rb +94 -0
  243. data/lib/active_record/validations/absence.rb +25 -0
  244. data/lib/active_record/validations/associated.rb +60 -0
  245. data/lib/active_record/validations/length.rb +26 -0
  246. data/lib/active_record/validations/presence.rb +68 -0
  247. data/lib/active_record/validations/uniqueness.rb +226 -0
  248. data/lib/active_record/version.rb +10 -0
  249. data/lib/arel.rb +51 -0
  250. data/lib/arel/alias_predication.rb +9 -0
  251. data/lib/arel/attributes.rb +22 -0
  252. data/lib/arel/attributes/attribute.rb +37 -0
  253. data/lib/arel/collectors/bind.rb +24 -0
  254. data/lib/arel/collectors/composite.rb +31 -0
  255. data/lib/arel/collectors/plain_string.rb +20 -0
  256. data/lib/arel/collectors/sql_string.rb +20 -0
  257. data/lib/arel/collectors/substitute_binds.rb +28 -0
  258. data/lib/arel/crud.rb +42 -0
  259. data/lib/arel/delete_manager.rb +18 -0
  260. data/lib/arel/errors.rb +9 -0
  261. data/lib/arel/expressions.rb +29 -0
  262. data/lib/arel/factory_methods.rb +49 -0
  263. data/lib/arel/insert_manager.rb +49 -0
  264. data/lib/arel/math.rb +45 -0
  265. data/lib/arel/nodes.rb +68 -0
  266. data/lib/arel/nodes/and.rb +32 -0
  267. data/lib/arel/nodes/ascending.rb +23 -0
  268. data/lib/arel/nodes/binary.rb +52 -0
  269. data/lib/arel/nodes/bind_param.rb +36 -0
  270. data/lib/arel/nodes/case.rb +55 -0
  271. data/lib/arel/nodes/casted.rb +50 -0
  272. data/lib/arel/nodes/comment.rb +29 -0
  273. data/lib/arel/nodes/count.rb +12 -0
  274. data/lib/arel/nodes/delete_statement.rb +45 -0
  275. data/lib/arel/nodes/descending.rb +23 -0
  276. data/lib/arel/nodes/equality.rb +18 -0
  277. data/lib/arel/nodes/extract.rb +24 -0
  278. data/lib/arel/nodes/false.rb +16 -0
  279. data/lib/arel/nodes/full_outer_join.rb +8 -0
  280. data/lib/arel/nodes/function.rb +44 -0
  281. data/lib/arel/nodes/grouping.rb +8 -0
  282. data/lib/arel/nodes/in.rb +8 -0
  283. data/lib/arel/nodes/infix_operation.rb +80 -0
  284. data/lib/arel/nodes/inner_join.rb +8 -0
  285. data/lib/arel/nodes/insert_statement.rb +37 -0
  286. data/lib/arel/nodes/join_source.rb +20 -0
  287. data/lib/arel/nodes/matches.rb +18 -0
  288. data/lib/arel/nodes/named_function.rb +23 -0
  289. data/lib/arel/nodes/node.rb +50 -0
  290. data/lib/arel/nodes/node_expression.rb +13 -0
  291. data/lib/arel/nodes/outer_join.rb +8 -0
  292. data/lib/arel/nodes/over.rb +15 -0
  293. data/lib/arel/nodes/regexp.rb +16 -0
  294. data/lib/arel/nodes/right_outer_join.rb +8 -0
  295. data/lib/arel/nodes/select_core.rb +67 -0
  296. data/lib/arel/nodes/select_statement.rb +41 -0
  297. data/lib/arel/nodes/sql_literal.rb +16 -0
  298. data/lib/arel/nodes/string_join.rb +11 -0
  299. data/lib/arel/nodes/table_alias.rb +27 -0
  300. data/lib/arel/nodes/terminal.rb +16 -0
  301. data/lib/arel/nodes/true.rb +16 -0
  302. data/lib/arel/nodes/unary.rb +45 -0
  303. data/lib/arel/nodes/unary_operation.rb +20 -0
  304. data/lib/arel/nodes/unqualified_column.rb +22 -0
  305. data/lib/arel/nodes/update_statement.rb +41 -0
  306. data/lib/arel/nodes/values_list.rb +9 -0
  307. data/lib/arel/nodes/window.rb +126 -0
  308. data/lib/arel/nodes/with.rb +11 -0
  309. data/lib/arel/order_predications.rb +13 -0
  310. data/lib/arel/predications.rb +257 -0
  311. data/lib/arel/select_manager.rb +271 -0
  312. data/lib/arel/table.rb +110 -0
  313. data/lib/arel/tree_manager.rb +72 -0
  314. data/lib/arel/update_manager.rb +34 -0
  315. data/lib/arel/visitors.rb +20 -0
  316. data/lib/arel/visitors/depth_first.rb +204 -0
  317. data/lib/arel/visitors/dot.rb +297 -0
  318. data/lib/arel/visitors/ibm_db.rb +34 -0
  319. data/lib/arel/visitors/informix.rb +62 -0
  320. data/lib/arel/visitors/mssql.rb +157 -0
  321. data/lib/arel/visitors/mysql.rb +83 -0
  322. data/lib/arel/visitors/oracle.rb +159 -0
  323. data/lib/arel/visitors/oracle12.rb +66 -0
  324. data/lib/arel/visitors/postgresql.rb +110 -0
  325. data/lib/arel/visitors/sqlite.rb +39 -0
  326. data/lib/arel/visitors/to_sql.rb +889 -0
  327. data/lib/arel/visitors/visitor.rb +46 -0
  328. data/lib/arel/visitors/where_sql.rb +23 -0
  329. data/lib/arel/window_predications.rb +9 -0
  330. data/lib/rails/generators/active_record.rb +19 -0
  331. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  332. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  333. data/lib/rails/generators/active_record/migration.rb +48 -0
  334. data/lib/rails/generators/active_record/migration/migration_generator.rb +75 -0
  335. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  336. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
  337. data/lib/rails/generators/active_record/model/model_generator.rb +49 -0
  338. data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
  339. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  340. metadata +415 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
7
+ private
8
+ def visit_AlterTable(o)
9
+ super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
10
+ end
11
+
12
+ def visit_AddForeignKey(o)
13
+ super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
14
+ end
15
+
16
+ def visit_ValidateConstraint(name)
17
+ "VALIDATE CONSTRAINT #{quote_column_name(name)}"
18
+ end
19
+
20
+ def visit_ChangeColumnDefinition(o)
21
+ column = o.column
22
+ column.sql_type = type_to_sql(column.type, column.options)
23
+ quoted_column_name = quote_column_name(o.name)
24
+
25
+ change_column_sql = +"ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}"
26
+
27
+ options = column_options(column)
28
+
29
+ if options[:collation]
30
+ change_column_sql << " COLLATE \"#{options[:collation]}\""
31
+ end
32
+
33
+ if options[:using]
34
+ change_column_sql << " USING #{options[:using]}"
35
+ elsif options[:cast_as]
36
+ cast_as_type = type_to_sql(options[:cast_as], options)
37
+ change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
38
+ end
39
+
40
+ if options.key?(:default)
41
+ if options[:default].nil?
42
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
43
+ else
44
+ quoted_default = quote_default_expression(options[:default], column)
45
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
46
+ end
47
+ end
48
+
49
+ if options.key?(:null)
50
+ change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL"
51
+ end
52
+
53
+ change_column_sql
54
+ end
55
+
56
+ def add_column_options!(sql, options)
57
+ if options[:collation]
58
+ sql << " COLLATE \"#{options[:collation]}\""
59
+ end
60
+ super
61
+ end
62
+
63
+ # Returns any SQL string to go between CREATE and TABLE. May be nil.
64
+ def table_modifier_in_create(o)
65
+ # A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
66
+ # tables are already UNLOGGED.
67
+ if o.temporary
68
+ " TEMPORARY"
69
+ elsif o.unlogged
70
+ " UNLOGGED"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module ColumnMethods
7
+ extend ActiveSupport::Concern
8
+
9
+ # Defines the primary key field.
10
+ # Use of the native PostgreSQL UUID type is supported, and can be used
11
+ # by defining your tables as such:
12
+ #
13
+ # create_table :stuffs, id: :uuid do |t|
14
+ # t.string :content
15
+ # t.timestamps
16
+ # end
17
+ #
18
+ # By default, this will use the <tt>gen_random_uuid()</tt> function from the
19
+ # +pgcrypto+ extension. As that extension is only available in
20
+ # PostgreSQL 9.4+, for earlier versions an explicit default can be set
21
+ # to use <tt>uuid_generate_v4()</tt> from the +uuid-ossp+ extension instead:
22
+ #
23
+ # create_table :stuffs, id: false do |t|
24
+ # t.primary_key :id, :uuid, default: "uuid_generate_v4()"
25
+ # t.uuid :foo_id
26
+ # t.timestamps
27
+ # end
28
+ #
29
+ # To enable the appropriate extension, which is a requirement, use
30
+ # the +enable_extension+ method in your migrations.
31
+ #
32
+ # To use a UUID primary key without any of the extensions, set the
33
+ # +:default+ option to +nil+:
34
+ #
35
+ # create_table :stuffs, id: false do |t|
36
+ # t.primary_key :id, :uuid, default: nil
37
+ # t.uuid :foo_id
38
+ # t.timestamps
39
+ # end
40
+ #
41
+ # You may also pass a custom stored procedure that returns a UUID or use a
42
+ # different UUID generation function from another library.
43
+ #
44
+ # Note that setting the UUID primary key default value to +nil+ will
45
+ # require you to assure that you always provide a UUID value before saving
46
+ # a record (as primary keys cannot be +nil+). This might be done via the
47
+ # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
48
+ def primary_key(name, type = :primary_key, **options)
49
+ if type == :uuid
50
+ options[:default] = options.fetch(:default, "gen_random_uuid()")
51
+ end
52
+
53
+ super
54
+ end
55
+
56
+ ##
57
+ # :method: bigserial
58
+ # :call-seq: bigserial(*names, **options)
59
+
60
+ ##
61
+ # :method: bit
62
+ # :call-seq: bit(*names, **options)
63
+
64
+ ##
65
+ # :method: bit_varying
66
+ # :call-seq: bit_varying(*names, **options)
67
+
68
+ ##
69
+ # :method: cidr
70
+ # :call-seq: cidr(*names, **options)
71
+
72
+ ##
73
+ # :method: citext
74
+ # :call-seq: citext(*names, **options)
75
+
76
+ ##
77
+ # :method: daterange
78
+ # :call-seq: daterange(*names, **options)
79
+
80
+ ##
81
+ # :method: hstore
82
+ # :call-seq: hstore(*names, **options)
83
+
84
+ ##
85
+ # :method: inet
86
+ # :call-seq: inet(*names, **options)
87
+
88
+ ##
89
+ # :method: interval
90
+ # :call-seq: interval(*names, **options)
91
+
92
+ ##
93
+ # :method: int4range
94
+ # :call-seq: int4range(*names, **options)
95
+
96
+ ##
97
+ # :method: int8range
98
+ # :call-seq: int8range(*names, **options)
99
+
100
+ ##
101
+ # :method: jsonb
102
+ # :call-seq: jsonb(*names, **options)
103
+
104
+ ##
105
+ # :method: ltree
106
+ # :call-seq: ltree(*names, **options)
107
+
108
+ ##
109
+ # :method: macaddr
110
+ # :call-seq: macaddr(*names, **options)
111
+
112
+ ##
113
+ # :method: money
114
+ # :call-seq: money(*names, **options)
115
+
116
+ ##
117
+ # :method: numrange
118
+ # :call-seq: numrange(*names, **options)
119
+
120
+ ##
121
+ # :method: oid
122
+ # :call-seq: oid(*names, **options)
123
+
124
+ ##
125
+ # :method: point
126
+ # :call-seq: point(*names, **options)
127
+
128
+ ##
129
+ # :method: line
130
+ # :call-seq: line(*names, **options)
131
+
132
+ ##
133
+ # :method: lseg
134
+ # :call-seq: lseg(*names, **options)
135
+
136
+ ##
137
+ # :method: box
138
+ # :call-seq: box(*names, **options)
139
+
140
+ ##
141
+ # :method: path
142
+ # :call-seq: path(*names, **options)
143
+
144
+ ##
145
+ # :method: polygon
146
+ # :call-seq: polygon(*names, **options)
147
+
148
+ ##
149
+ # :method: circle
150
+ # :call-seq: circle(*names, **options)
151
+
152
+ ##
153
+ # :method: serial
154
+ # :call-seq: serial(*names, **options)
155
+
156
+ ##
157
+ # :method: tsrange
158
+ # :call-seq: tsrange(*names, **options)
159
+
160
+ ##
161
+ # :method: tstzrange
162
+ # :call-seq: tstzrange(*names, **options)
163
+
164
+ ##
165
+ # :method: tsvector
166
+ # :call-seq: tsvector(*names, **options)
167
+
168
+ ##
169
+ # :method: uuid
170
+ # :call-seq: uuid(*names, **options)
171
+
172
+ ##
173
+ # :method: xml
174
+ # :call-seq: xml(*names, **options)
175
+
176
+ included do
177
+ define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
178
+ :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
179
+ :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
180
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml
181
+ end
182
+ end
183
+
184
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
185
+ include ColumnMethods
186
+
187
+ attr_reader :unlogged
188
+
189
+ def initialize(*)
190
+ super
191
+ @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
192
+ end
193
+
194
+ private
195
+ def integer_like_primary_key_type(type, options)
196
+ if type == :bigint || options[:limit] == 8
197
+ :bigserial
198
+ else
199
+ :serial
200
+ end
201
+ end
202
+ end
203
+
204
+ class Table < ActiveRecord::ConnectionAdapters::Table
205
+ include ColumnMethods
206
+ end
207
+
208
+ class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
209
+ attr_reader :constraint_validations
210
+
211
+ def initialize(td)
212
+ super
213
+ @constraint_validations = []
214
+ end
215
+
216
+ def validate_constraint(name)
217
+ @constraint_validations << name
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
+ private
8
+
9
+ def extensions(stream)
10
+ extensions = @connection.extensions
11
+ if extensions.any?
12
+ stream.puts " # These are extensions that must be enabled in order to support this database"
13
+ extensions.sort.each do |extension|
14
+ stream.puts " enable_extension #{extension.inspect}"
15
+ end
16
+ stream.puts
17
+ end
18
+ end
19
+
20
+ def prepare_column_options(column)
21
+ spec = super
22
+ spec[:array] = "true" if column.array?
23
+ spec
24
+ end
25
+
26
+ def default_primary_key?(column)
27
+ schema_type(column) == :bigserial
28
+ end
29
+
30
+ def explicit_primary_key_default?(column)
31
+ column.type == :uuid || (column.type == :integer && !column.serial?)
32
+ end
33
+
34
+ def schema_type(column)
35
+ return super unless column.serial?
36
+
37
+ if column.bigint?
38
+ :bigserial
39
+ else
40
+ :serial
41
+ end
42
+ end
43
+
44
+ def schema_expression(column)
45
+ super unless column.serial?
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,776 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module SchemaStatements
7
+ # Drops the database specified on the +name+ attribute
8
+ # and creates it again using the provided +options+.
9
+ def recreate_database(name, options = {}) #:nodoc:
10
+ drop_database(name)
11
+ create_database(name, options)
12
+ end
13
+
14
+ # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
15
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
16
+ # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
17
+ # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
18
+ #
19
+ # Example:
20
+ # create_database config[:database], config
21
+ # create_database 'foo_development', encoding: 'unicode'
22
+ def create_database(name, options = {})
23
+ options = { encoding: "utf8" }.merge!(options.symbolize_keys)
24
+
25
+ option_string = options.each_with_object(+"") do |(key, value), memo|
26
+ memo << case key
27
+ when :owner
28
+ " OWNER = \"#{value}\""
29
+ when :template
30
+ " TEMPLATE = \"#{value}\""
31
+ when :encoding
32
+ " ENCODING = '#{value}'"
33
+ when :collation
34
+ " LC_COLLATE = '#{value}'"
35
+ when :ctype
36
+ " LC_CTYPE = '#{value}'"
37
+ when :tablespace
38
+ " TABLESPACE = \"#{value}\""
39
+ when :connection_limit
40
+ " CONNECTION LIMIT = #{value}"
41
+ else
42
+ ""
43
+ end
44
+ end
45
+
46
+ execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
47
+ end
48
+
49
+ # Drops a PostgreSQL database.
50
+ #
51
+ # Example:
52
+ # drop_database 'matt_development'
53
+ def drop_database(name) #:nodoc:
54
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
55
+ end
56
+
57
+ def drop_table(table_name, options = {}) # :nodoc:
58
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
59
+ end
60
+
61
+ # Returns true if schema exists.
62
+ def schema_exists?(name)
63
+ query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0
64
+ end
65
+
66
+ # Verifies existence of an index with a given name.
67
+ def index_name_exists?(table_name, index_name)
68
+ table = quoted_scope(table_name)
69
+ index = quoted_scope(index_name)
70
+
71
+ query_value(<<~SQL, "SCHEMA").to_i > 0
72
+ SELECT COUNT(*)
73
+ FROM pg_class t
74
+ INNER JOIN pg_index d ON t.oid = d.indrelid
75
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
76
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
77
+ WHERE i.relkind = 'i'
78
+ AND i.relname = #{index[:name]}
79
+ AND t.relname = #{table[:name]}
80
+ AND n.nspname = #{index[:schema]}
81
+ SQL
82
+ end
83
+
84
+ # Returns an array of indexes for the given table.
85
+ def indexes(table_name) # :nodoc:
86
+ scope = quoted_scope(table_name)
87
+
88
+ result = query(<<~SQL, "SCHEMA")
89
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
90
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
91
+ FROM pg_class t
92
+ INNER JOIN pg_index d ON t.oid = d.indrelid
93
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
94
+ LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
95
+ WHERE i.relkind = 'i'
96
+ AND d.indisprimary = 'f'
97
+ AND t.relname = #{scope[:name]}
98
+ AND n.nspname = #{scope[:schema]}
99
+ ORDER BY i.relname
100
+ SQL
101
+
102
+ result.map do |row|
103
+ index_name = row[0]
104
+ unique = row[1]
105
+ indkey = row[2].split(" ").map(&:to_i)
106
+ inddef = row[3]
107
+ oid = row[4]
108
+ comment = row[5]
109
+
110
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten
111
+
112
+ orders = {}
113
+ opclasses = {}
114
+
115
+ if indkey.include?(0)
116
+ columns = expressions
117
+ else
118
+ columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact
119
+ SELECT a.attnum, a.attname
120
+ FROM pg_attribute a
121
+ WHERE a.attrelid = #{oid}
122
+ AND a.attnum IN (#{indkey.join(",")})
123
+ SQL
124
+
125
+ # add info on sort order (only desc order is explicitly specified, asc is the default)
126
+ # and non-default opclasses
127
+ expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
128
+ opclasses[column] = opclass.to_sym if opclass
129
+ if nulls
130
+ orders[column] = [desc, nulls].compact.join(" ")
131
+ else
132
+ orders[column] = :desc if desc
133
+ end
134
+ end
135
+ end
136
+
137
+ IndexDefinition.new(
138
+ table_name,
139
+ index_name,
140
+ unique,
141
+ columns,
142
+ orders: orders,
143
+ opclasses: opclasses,
144
+ where: where,
145
+ using: using.to_sym,
146
+ comment: comment.presence
147
+ )
148
+ end
149
+ end
150
+
151
+ def table_options(table_name) # :nodoc:
152
+ if comment = table_comment(table_name)
153
+ { comment: comment }
154
+ end
155
+ end
156
+
157
+ # Returns a comment stored in database for given table
158
+ def table_comment(table_name) # :nodoc:
159
+ scope = quoted_scope(table_name, type: "BASE TABLE")
160
+ if scope[:name]
161
+ query_value(<<~SQL, "SCHEMA")
162
+ SELECT pg_catalog.obj_description(c.oid, 'pg_class')
163
+ FROM pg_catalog.pg_class c
164
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
165
+ WHERE c.relname = #{scope[:name]}
166
+ AND c.relkind IN (#{scope[:type]})
167
+ AND n.nspname = #{scope[:schema]}
168
+ SQL
169
+ end
170
+ end
171
+
172
+ # Returns the current database name.
173
+ def current_database
174
+ query_value("SELECT current_database()", "SCHEMA")
175
+ end
176
+
177
+ # Returns the current schema name.
178
+ def current_schema
179
+ query_value("SELECT current_schema", "SCHEMA")
180
+ end
181
+
182
+ # Returns the current database encoding format.
183
+ def encoding
184
+ query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
185
+ end
186
+
187
+ # Returns the current database collation.
188
+ def collation
189
+ query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
190
+ end
191
+
192
+ # Returns the current database ctype.
193
+ def ctype
194
+ query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
195
+ end
196
+
197
+ # Returns an array of schema names.
198
+ def schema_names
199
+ query_values(<<~SQL, "SCHEMA")
200
+ SELECT nspname
201
+ FROM pg_namespace
202
+ WHERE nspname !~ '^pg_.*'
203
+ AND nspname NOT IN ('information_schema')
204
+ ORDER by nspname;
205
+ SQL
206
+ end
207
+
208
+ # Creates a schema for the given schema name.
209
+ def create_schema(schema_name)
210
+ execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
211
+ end
212
+
213
+ # Drops the schema for the given schema name.
214
+ def drop_schema(schema_name, options = {})
215
+ execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
216
+ end
217
+
218
+ # Sets the schema search path to a string of comma-separated schema names.
219
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
220
+ # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
221
+ #
222
+ # This should be not be called manually but set in database.yml.
223
+ def schema_search_path=(schema_csv)
224
+ if schema_csv
225
+ execute("SET search_path TO #{schema_csv}", "SCHEMA")
226
+ @schema_search_path = schema_csv
227
+ end
228
+ end
229
+
230
+ # Returns the active schema search path.
231
+ def schema_search_path
232
+ @schema_search_path ||= query_value("SHOW search_path", "SCHEMA")
233
+ end
234
+
235
+ # Returns the current client message level.
236
+ def client_min_messages
237
+ query_value("SHOW client_min_messages", "SCHEMA")
238
+ end
239
+
240
+ # Set the client message level.
241
+ def client_min_messages=(level)
242
+ execute("SET client_min_messages TO '#{level}'", "SCHEMA")
243
+ end
244
+
245
+ # Returns the sequence name for a table's primary key or some other specified key.
246
+ def default_sequence_name(table_name, pk = "id") #:nodoc:
247
+ result = serial_sequence(table_name, pk)
248
+ return nil unless result
249
+ Utils.extract_schema_qualified_name(result).to_s
250
+ rescue ActiveRecord::StatementInvalid
251
+ PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s
252
+ end
253
+
254
+ def serial_sequence(table, column)
255
+ query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA")
256
+ end
257
+
258
+ # Sets the sequence of a table's primary key to the specified value.
259
+ def set_pk_sequence!(table, value) #:nodoc:
260
+ pk, sequence = pk_and_sequence_for(table)
261
+
262
+ if pk
263
+ if sequence
264
+ quoted_sequence = quote_table_name(sequence)
265
+
266
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
267
+ else
268
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
269
+ end
270
+ end
271
+ end
272
+
273
+ # Resets the sequence of a table's primary key to the maximum value.
274
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
275
+ unless pk && sequence
276
+ default_pk, default_sequence = pk_and_sequence_for(table)
277
+
278
+ pk ||= default_pk
279
+ sequence ||= default_sequence
280
+ end
281
+
282
+ if @logger && pk && !sequence
283
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
284
+ end
285
+
286
+ if pk && sequence
287
+ quoted_sequence = quote_table_name(sequence)
288
+ max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
289
+ if max_pk.nil?
290
+ if database_version >= 100000
291
+ minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
292
+ else
293
+ minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
294
+ end
295
+ end
296
+
297
+ query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
298
+ end
299
+ end
300
+
301
+ # Returns a table's primary key and belonging sequence.
302
+ def pk_and_sequence_for(table) #:nodoc:
303
+ # First try looking for a sequence with a dependency on the
304
+ # given table's primary key.
305
+ result = query(<<~SQL, "SCHEMA")[0]
306
+ SELECT attr.attname, nsp.nspname, seq.relname
307
+ FROM pg_class seq,
308
+ pg_attribute attr,
309
+ pg_depend dep,
310
+ pg_constraint cons,
311
+ pg_namespace nsp
312
+ WHERE seq.oid = dep.objid
313
+ AND seq.relkind = 'S'
314
+ AND attr.attrelid = dep.refobjid
315
+ AND attr.attnum = dep.refobjsubid
316
+ AND attr.attrelid = cons.conrelid
317
+ AND attr.attnum = cons.conkey[1]
318
+ AND seq.relnamespace = nsp.oid
319
+ AND cons.contype = 'p'
320
+ AND dep.classid = 'pg_class'::regclass
321
+ AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
322
+ SQL
323
+
324
+ if result.nil? || result.empty?
325
+ result = query(<<~SQL, "SCHEMA")[0]
326
+ SELECT attr.attname, nsp.nspname,
327
+ CASE
328
+ WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
329
+ WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
330
+ substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
331
+ strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
332
+ ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
333
+ END
334
+ FROM pg_class t
335
+ JOIN pg_attribute attr ON (t.oid = attrelid)
336
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
337
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
338
+ JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
339
+ WHERE t.oid = #{quote(quote_table_name(table))}::regclass
340
+ AND cons.contype = 'p'
341
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
342
+ SQL
343
+ end
344
+
345
+ pk = result.shift
346
+ if result.last
347
+ [pk, PostgreSQL::Name.new(*result)]
348
+ else
349
+ [pk, nil]
350
+ end
351
+ rescue
352
+ nil
353
+ end
354
+
355
+ def primary_keys(table_name) # :nodoc:
356
+ query_values(<<~SQL, "SCHEMA")
357
+ SELECT a.attname
358
+ FROM (
359
+ SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
360
+ FROM pg_index
361
+ WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
362
+ AND indisprimary
363
+ ) i
364
+ JOIN pg_attribute a
365
+ ON a.attrelid = i.indrelid
366
+ AND a.attnum = i.indkey[i.idx]
367
+ ORDER BY i.idx
368
+ SQL
369
+ end
370
+
371
+ # Renames a table.
372
+ # Also renames a table's primary key sequence if the sequence name exists and
373
+ # matches the Active Record default.
374
+ #
375
+ # Example:
376
+ # rename_table('octopuses', 'octopi')
377
+ def rename_table(table_name, new_name)
378
+ clear_cache!
379
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
380
+ pk, seq = pk_and_sequence_for(new_name)
381
+ if pk
382
+ idx = "#{table_name}_pkey"
383
+ new_idx = "#{new_name}_pkey"
384
+ execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
385
+ if seq && seq.identifier == "#{table_name}_#{pk}_seq"
386
+ new_seq = "#{new_name}_#{pk}_seq"
387
+ execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
388
+ end
389
+ end
390
+ rename_table_indexes(table_name, new_name)
391
+ end
392
+
393
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
394
+ clear_cache!
395
+ super
396
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
397
+ end
398
+
399
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
400
+ clear_cache!
401
+ sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
402
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
403
+ procs.each(&:call)
404
+ end
405
+
406
+ # Changes the default value of a table column.
407
+ def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
408
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
409
+ end
410
+
411
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
412
+ clear_cache!
413
+ unless null || default.nil?
414
+ column = column_for(table_name, column_name)
415
+ execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
416
+ end
417
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
418
+ end
419
+
420
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
421
+ def change_column_comment(table_name, column_name, comment_or_changes) # :nodoc:
422
+ clear_cache!
423
+ comment = extract_new_comment_value(comment_or_changes)
424
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
425
+ end
426
+
427
+ # Adds comment for given table or drops it if +comment+ is a +nil+
428
+ def change_table_comment(table_name, comment_or_changes) # :nodoc:
429
+ clear_cache!
430
+ comment = extract_new_comment_value(comment_or_changes)
431
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
432
+ end
433
+
434
+ # Renames a column in a table.
435
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
436
+ clear_cache!
437
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
438
+ rename_column_indexes(table_name, column_name, new_column_name)
439
+ end
440
+
441
+ def add_index(table_name, column_name, options = {}) #:nodoc:
442
+ index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
443
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
444
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
445
+ end
446
+ end
447
+
448
+ def remove_index(table_name, options = {}) #:nodoc:
449
+ table = Utils.extract_schema_qualified_name(table_name.to_s)
450
+
451
+ if options.is_a?(Hash) && options.key?(:name)
452
+ provided_index = Utils.extract_schema_qualified_name(options[:name].to_s)
453
+
454
+ options[:name] = provided_index.identifier
455
+ table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present?
456
+
457
+ if provided_index.schema.present? && table.schema != provided_index.schema
458
+ raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'")
459
+ end
460
+ end
461
+
462
+ index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options))
463
+ algorithm =
464
+ if options.is_a?(Hash) && options.key?(:algorithm)
465
+ index_algorithms.fetch(options[:algorithm]) do
466
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
467
+ end
468
+ end
469
+ execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}"
470
+ end
471
+
472
+ # Renames an index of a table. Raises error if length of new
473
+ # index name is greater than allowed limit.
474
+ def rename_index(table_name, old_name, new_name)
475
+ validate_index_length!(table_name, new_name)
476
+
477
+ execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
478
+ end
479
+
480
+ def foreign_keys(table_name)
481
+ scope = quoted_scope(table_name)
482
+ fk_info = exec_query(<<~SQL, "SCHEMA")
483
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
484
+ FROM pg_constraint c
485
+ JOIN pg_class t1 ON c.conrelid = t1.oid
486
+ JOIN pg_class t2 ON c.confrelid = t2.oid
487
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
488
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
489
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
490
+ WHERE c.contype = 'f'
491
+ AND t1.relname = #{scope[:name]}
492
+ AND t3.nspname = #{scope[:schema]}
493
+ ORDER BY c.conname
494
+ SQL
495
+
496
+ fk_info.map do |row|
497
+ options = {
498
+ column: row["column"],
499
+ name: row["name"],
500
+ primary_key: row["primary_key"]
501
+ }
502
+
503
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
504
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
505
+ options[:validate] = row["valid"]
506
+
507
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
508
+ end
509
+ end
510
+
511
+ def foreign_tables
512
+ query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
513
+ end
514
+
515
+ def foreign_table_exists?(table_name)
516
+ query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
517
+ end
518
+
519
+ # Maps logical Rails types to PostgreSQL-specific data types.
520
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
521
+ sql = \
522
+ case type.to_s
523
+ when "binary"
524
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
525
+ # The hard limit is 1GB, because of a 32-bit size field, and TOAST.
526
+ case limit
527
+ when nil, 0..0x3fffffff; super(type)
528
+ else raise ArgumentError, "No binary type has byte size #{limit}. The limit on binary can be at most 1GB - 1byte."
529
+ end
530
+ when "text"
531
+ # PostgreSQL doesn't support limits on text columns.
532
+ # The hard limit is 1GB, according to section 8.3 in the manual.
533
+ case limit
534
+ when nil, 0..0x3fffffff; super(type)
535
+ else raise ArgumentError, "No text type has byte size #{limit}. The limit on text can be at most 1GB - 1byte."
536
+ end
537
+ when "integer"
538
+ case limit
539
+ when 1, 2; "smallint"
540
+ when nil, 3, 4; "integer"
541
+ when 5..8; "bigint"
542
+ else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
543
+ end
544
+ else
545
+ super
546
+ end
547
+
548
+ sql = "#{sql}[]" if array && type != :primary_key
549
+ sql
550
+ end
551
+
552
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
553
+ # requires that the ORDER BY include the distinct column.
554
+ def columns_for_distinct(columns, orders) #:nodoc:
555
+ order_columns = orders.reject(&:blank?).map { |s|
556
+ # Convert Arel node to string
557
+ s = s.to_sql unless s.is_a?(String)
558
+ # Remove any ASC/DESC modifiers
559
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
560
+ .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "")
561
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
562
+
563
+ (order_columns << super).join(", ")
564
+ end
565
+
566
+ def update_table_definition(table_name, base) # :nodoc:
567
+ PostgreSQL::Table.new(table_name, base)
568
+ end
569
+
570
+ def create_schema_dumper(options) # :nodoc:
571
+ PostgreSQL::SchemaDumper.create(self, options)
572
+ end
573
+
574
+ # Validates the given constraint.
575
+ #
576
+ # Validates the constraint named +constraint_name+ on +accounts+.
577
+ #
578
+ # validate_constraint :accounts, :constraint_name
579
+ def validate_constraint(table_name, constraint_name)
580
+ return unless supports_validate_constraints?
581
+
582
+ at = create_alter_table table_name
583
+ at.validate_constraint constraint_name
584
+
585
+ execute schema_creation.accept(at)
586
+ end
587
+
588
+ # Validates the given foreign key.
589
+ #
590
+ # Validates the foreign key on +accounts.branch_id+.
591
+ #
592
+ # validate_foreign_key :accounts, :branches
593
+ #
594
+ # Validates the foreign key on +accounts.owner_id+.
595
+ #
596
+ # validate_foreign_key :accounts, column: :owner_id
597
+ #
598
+ # Validates the foreign key named +special_fk_name+ on the +accounts+ table.
599
+ #
600
+ # validate_foreign_key :accounts, name: :special_fk_name
601
+ #
602
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
603
+ def validate_foreign_key(from_table, to_table = nil, **options)
604
+ return unless supports_validate_constraints?
605
+
606
+ fk_name_to_validate = foreign_key_for!(from_table, to_table: to_table, **options).name
607
+
608
+ validate_constraint from_table, fk_name_to_validate
609
+ end
610
+
611
+ private
612
+ def schema_creation
613
+ PostgreSQL::SchemaCreation.new(self)
614
+ end
615
+
616
+ def create_table_definition(*args)
617
+ PostgreSQL::TableDefinition.new(self, *args)
618
+ end
619
+
620
+ def create_alter_table(name)
621
+ PostgreSQL::AlterTable.new create_table_definition(name)
622
+ end
623
+
624
+ def new_column_from_field(table_name, field)
625
+ column_name, type, default, notnull, oid, fmod, collation, comment = field
626
+ type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
627
+ default_value = extract_value_from_default(default)
628
+ default_function = extract_default_function(default_value, default)
629
+
630
+ if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
631
+ serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
632
+ end
633
+
634
+ PostgreSQL::Column.new(
635
+ column_name,
636
+ default_value,
637
+ type_metadata,
638
+ !notnull,
639
+ default_function,
640
+ collation: collation,
641
+ comment: comment.presence,
642
+ serial: serial
643
+ )
644
+ end
645
+
646
+ def fetch_type_metadata(column_name, sql_type, oid, fmod)
647
+ cast_type = get_oid_type(oid, fmod, column_name, sql_type)
648
+ simple_type = SqlTypeMetadata.new(
649
+ sql_type: sql_type,
650
+ type: cast_type.type,
651
+ limit: cast_type.limit,
652
+ precision: cast_type.precision,
653
+ scale: cast_type.scale,
654
+ )
655
+ PostgreSQL::TypeMetadata.new(simple_type, oid: oid, fmod: fmod)
656
+ end
657
+
658
+ def sequence_name_from_parts(table_name, column_name, suffix)
659
+ over_length = [table_name, column_name, suffix].sum(&:length) + 2 - max_identifier_length
660
+
661
+ if over_length > 0
662
+ column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
663
+ over_length -= column_name.length - column_name_length
664
+ column_name = column_name[0, column_name_length - [over_length, 0].min]
665
+ end
666
+
667
+ if over_length > 0
668
+ table_name = table_name[0, table_name.length - over_length]
669
+ end
670
+
671
+ "#{table_name}_#{column_name}_#{suffix}"
672
+ end
673
+
674
+ def extract_foreign_key_action(specifier)
675
+ case specifier
676
+ when "c"; :cascade
677
+ when "n"; :nullify
678
+ when "r"; :restrict
679
+ end
680
+ end
681
+
682
+ def add_column_for_alter(table_name, column_name, type, options = {})
683
+ return super unless options.key?(:comment)
684
+ [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }]
685
+ end
686
+
687
+ def change_column_for_alter(table_name, column_name, type, options = {})
688
+ td = create_table_definition(table_name)
689
+ cd = td.new_column_definition(column_name, type, options)
690
+ sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))]
691
+ sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
692
+ sqls
693
+ end
694
+
695
+ def change_column_default_for_alter(table_name, column_name, default_or_changes)
696
+ column = column_for(table_name, column_name)
697
+ return unless column
698
+
699
+ default = extract_new_default_value(default_or_changes)
700
+ alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
701
+ if default.nil?
702
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
703
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
704
+ alter_column_query % "DROP DEFAULT"
705
+ else
706
+ alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
707
+ end
708
+ end
709
+
710
+ def change_column_null_for_alter(table_name, column_name, null, default = nil)
711
+ "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
712
+ end
713
+
714
+ def add_timestamps_for_alter(table_name, options = {})
715
+ options[:null] = false if options[:null].nil?
716
+
717
+ if !options.key?(:precision) && supports_datetime_with_precision?
718
+ options[:precision] = 6
719
+ end
720
+
721
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
722
+ end
723
+
724
+ def remove_timestamps_for_alter(table_name, options = {})
725
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
726
+ end
727
+
728
+ def add_index_opclass(quoted_columns, **options)
729
+ opclasses = options_for_index_columns(options[:opclass])
730
+ quoted_columns.each do |name, column|
731
+ column << " #{opclasses[name]}" if opclasses[name].present?
732
+ end
733
+ end
734
+
735
+ def add_options_for_index_columns(quoted_columns, **options)
736
+ quoted_columns = add_index_opclass(quoted_columns, options)
737
+ super
738
+ end
739
+
740
+ def data_source_sql(name = nil, type: nil)
741
+ scope = quoted_scope(name, type: type)
742
+ scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table
743
+
744
+ sql = +"SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace"
745
+ sql << " WHERE n.nspname = #{scope[:schema]}"
746
+ sql << " AND c.relname = #{scope[:name]}" if scope[:name]
747
+ sql << " AND c.relkind IN (#{scope[:type]})"
748
+ sql
749
+ end
750
+
751
+ def quoted_scope(name = nil, type: nil)
752
+ schema, name = extract_schema_qualified_name(name)
753
+ type = \
754
+ case type
755
+ when "BASE TABLE"
756
+ "'r','p'"
757
+ when "VIEW"
758
+ "'v','m'"
759
+ when "FOREIGN TABLE"
760
+ "'f'"
761
+ end
762
+ scope = {}
763
+ scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
764
+ scope[:name] = quote(name) if name
765
+ scope[:type] = type if type
766
+ scope
767
+ end
768
+
769
+ def extract_schema_qualified_name(string)
770
+ name = Utils.extract_schema_qualified_name(string.to_s)
771
+ [name.schema, name.identifier]
772
+ end
773
+ end
774
+ end
775
+ end
776
+ end