omg-activerecord 8.0.0.alpha1

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