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,1182 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "pg", "~> 1.1"
4
+ require "pg"
5
+
6
+ require "active_support/core_ext/object/try"
7
+ require "active_record/connection_adapters/abstract_adapter"
8
+ require "active_record/connection_adapters/statement_pool"
9
+ require "active_record/connection_adapters/postgresql/column"
10
+ require "active_record/connection_adapters/postgresql/database_statements"
11
+ require "active_record/connection_adapters/postgresql/explain_pretty_printer"
12
+ require "active_record/connection_adapters/postgresql/oid"
13
+ require "active_record/connection_adapters/postgresql/quoting"
14
+ require "active_record/connection_adapters/postgresql/referential_integrity"
15
+ require "active_record/connection_adapters/postgresql/schema_creation"
16
+ require "active_record/connection_adapters/postgresql/schema_definitions"
17
+ require "active_record/connection_adapters/postgresql/schema_dumper"
18
+ require "active_record/connection_adapters/postgresql/schema_statements"
19
+ require "active_record/connection_adapters/postgresql/type_metadata"
20
+ require "active_record/connection_adapters/postgresql/utils"
21
+
22
+ module ActiveRecord
23
+ module ConnectionAdapters
24
+ # = Active Record PostgreSQL Adapter
25
+ #
26
+ # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
27
+ #
28
+ # Options:
29
+ #
30
+ # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
31
+ # the default is to connect to localhost.
32
+ # * <tt>:port</tt> - Defaults to 5432.
33
+ # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
34
+ # * <tt>:password</tt> - Password to be used if the server demands password authentication.
35
+ # * <tt>:database</tt> - Defaults to be the same as the username.
36
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
37
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
38
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
39
+ # <encoding></tt> call on the connection.
40
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
41
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
42
+ # * <tt>:variables</tt> - An optional hash of additional parameters that
43
+ # will be used in <tt>SET SESSION key = val</tt> calls on the connection.
44
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use of <tt>RETURNING</tt> for <tt>INSERT</tt> statements
45
+ # defaults to true.
46
+ #
47
+ # Any further options are used as connection parameters to libpq. See
48
+ # https://www.postgresql.org/docs/current/static/libpq-connect.html for the
49
+ # list of parameters.
50
+ #
51
+ # In addition, default connection parameters of libpq can be set per environment variables.
52
+ # See https://www.postgresql.org/docs/current/static/libpq-envars.html .
53
+ class PostgreSQLAdapter < AbstractAdapter
54
+ ADAPTER_NAME = "PostgreSQL"
55
+
56
+ class << self
57
+ def new_client(conn_params)
58
+ PG.connect(**conn_params)
59
+ rescue ::PG::Error => error
60
+ if conn_params && conn_params[:dbname] == "postgres"
61
+ raise ActiveRecord::ConnectionNotEstablished, error.message
62
+ elsif conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
63
+ raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
64
+ elsif conn_params && conn_params[:user] && error.message.include?(conn_params[:user])
65
+ raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
66
+ elsif conn_params && conn_params[:host] && error.message.include?(conn_params[:host])
67
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
68
+ else
69
+ raise ActiveRecord::ConnectionNotEstablished, error.message
70
+ end
71
+ end
72
+
73
+ def dbconsole(config, options = {})
74
+ pg_config = config.configuration_hash
75
+
76
+ ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
77
+ ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
78
+ ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
79
+ ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
80
+ ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
81
+ ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
82
+ ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
83
+ ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
84
+ if pg_config[:variables]
85
+ ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
86
+ "-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
87
+ end.join(" ")
88
+ end
89
+ find_cmd_and_exec(ActiveRecord.database_cli[:postgresql], config.database)
90
+ end
91
+ end
92
+
93
+ ##
94
+ # :singleton-method:
95
+ # PostgreSQL allows the creation of "unlogged" tables, which do not record
96
+ # data in the PostgreSQL Write-Ahead Log. This can make the tables faster,
97
+ # but significantly increases the risk of data loss if the database
98
+ # crashes. As a result, this should not be used in production
99
+ # environments. If you would like all created tables to be unlogged in
100
+ # the test environment you can add the following to your test.rb file:
101
+ #
102
+ # ActiveSupport.on_load(:active_record_postgresqladapter) do
103
+ # self.create_unlogged_tables = true
104
+ # end
105
+ class_attribute :create_unlogged_tables, default: false
106
+
107
+ ##
108
+ # :singleton-method:
109
+ # PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+
110
+ # in migrations, \Rails will translate this to a PostgreSQL "timestamp without time zone".
111
+ # Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
112
+ # store DateTimes as "timestamp with time zone":
113
+ #
114
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
115
+ #
116
+ # Or if you are adding a custom type:
117
+ #
118
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "my_custom_type_name" }
119
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type
120
+ #
121
+ # If you're using +:ruby+ as your +config.active_record.schema_format+ and you change this
122
+ # setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
123
+ class_attribute :datetime_type, default: :timestamp
124
+
125
+ ##
126
+ # :singleton-method:
127
+ # Toggles automatic decoding of date columns.
128
+ #
129
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> String
130
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates = true
131
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> Date
132
+ class_attribute :decode_dates, default: false
133
+
134
+ NATIVE_DATABASE_TYPES = {
135
+ primary_key: "bigserial primary key",
136
+ string: { name: "character varying" },
137
+ text: { name: "text" },
138
+ integer: { name: "integer", limit: 4 },
139
+ bigint: { name: "bigint" },
140
+ float: { name: "float" },
141
+ decimal: { name: "decimal" },
142
+ datetime: {}, # set dynamically based on datetime_type
143
+ timestamp: { name: "timestamp" },
144
+ timestamptz: { name: "timestamptz" },
145
+ time: { name: "time" },
146
+ date: { name: "date" },
147
+ daterange: { name: "daterange" },
148
+ numrange: { name: "numrange" },
149
+ tsrange: { name: "tsrange" },
150
+ tstzrange: { name: "tstzrange" },
151
+ int4range: { name: "int4range" },
152
+ int8range: { name: "int8range" },
153
+ binary: { name: "bytea" },
154
+ boolean: { name: "boolean" },
155
+ xml: { name: "xml" },
156
+ tsvector: { name: "tsvector" },
157
+ hstore: { name: "hstore" },
158
+ inet: { name: "inet" },
159
+ cidr: { name: "cidr" },
160
+ macaddr: { name: "macaddr" },
161
+ uuid: { name: "uuid" },
162
+ json: { name: "json" },
163
+ jsonb: { name: "jsonb" },
164
+ ltree: { name: "ltree" },
165
+ citext: { name: "citext" },
166
+ point: { name: "point" },
167
+ line: { name: "line" },
168
+ lseg: { name: "lseg" },
169
+ box: { name: "box" },
170
+ path: { name: "path" },
171
+ polygon: { name: "polygon" },
172
+ circle: { name: "circle" },
173
+ bit: { name: "bit" },
174
+ bit_varying: { name: "bit varying" },
175
+ money: { name: "money" },
176
+ interval: { name: "interval" },
177
+ oid: { name: "oid" },
178
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
179
+ }
180
+
181
+ OID = PostgreSQL::OID # :nodoc:
182
+
183
+ include PostgreSQL::Quoting
184
+ include PostgreSQL::ReferentialIntegrity
185
+ include PostgreSQL::SchemaStatements
186
+ include PostgreSQL::DatabaseStatements
187
+
188
+ def supports_bulk_alter?
189
+ true
190
+ end
191
+
192
+ def supports_index_sort_order?
193
+ true
194
+ end
195
+
196
+ def supports_partitioned_indexes?
197
+ database_version >= 11_00_00 # >= 11.0
198
+ end
199
+
200
+ def supports_partial_index?
201
+ true
202
+ end
203
+
204
+ def supports_index_include?
205
+ database_version >= 11_00_00 # >= 11.0
206
+ end
207
+
208
+ def supports_expression_index?
209
+ true
210
+ end
211
+
212
+ def supports_transaction_isolation?
213
+ true
214
+ end
215
+
216
+ def supports_foreign_keys?
217
+ true
218
+ end
219
+
220
+ def supports_check_constraints?
221
+ true
222
+ end
223
+
224
+ def supports_exclusion_constraints?
225
+ true
226
+ end
227
+
228
+ def supports_unique_constraints?
229
+ true
230
+ end
231
+
232
+ def supports_validate_constraints?
233
+ true
234
+ end
235
+
236
+ def supports_deferrable_constraints?
237
+ true
238
+ end
239
+
240
+ def supports_views?
241
+ true
242
+ end
243
+
244
+ def supports_datetime_with_precision?
245
+ true
246
+ end
247
+
248
+ def supports_json?
249
+ true
250
+ end
251
+
252
+ def supports_comments?
253
+ true
254
+ end
255
+
256
+ def supports_savepoints?
257
+ true
258
+ end
259
+
260
+ def supports_restart_db_transaction?
261
+ database_version >= 12_00_00 # >= 12.0
262
+ end
263
+
264
+ def supports_insert_returning?
265
+ true
266
+ end
267
+
268
+ def supports_insert_on_conflict?
269
+ database_version >= 9_05_00 # >= 9.5
270
+ end
271
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
272
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
273
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
274
+
275
+ def supports_virtual_columns?
276
+ database_version >= 12_00_00 # >= 12.0
277
+ end
278
+
279
+ def supports_identity_columns? # :nodoc:
280
+ database_version >= 10_00_00 # >= 10.0
281
+ end
282
+
283
+ def supports_nulls_not_distinct?
284
+ database_version >= 15_00_00 # >= 15.0
285
+ end
286
+
287
+ def supports_native_partitioning? # :nodoc:
288
+ database_version >= 10_00_00 # >= 10.0
289
+ end
290
+
291
+ def index_algorithms
292
+ { concurrently: "CONCURRENTLY" }
293
+ end
294
+
295
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
296
+ def initialize(connection, max)
297
+ super(max)
298
+ @connection = connection
299
+ @counter = 0
300
+ end
301
+
302
+ def next_key
303
+ "a#{@counter += 1}"
304
+ end
305
+
306
+ private
307
+ def dealloc(key)
308
+ # This is ugly, but safe: the statement pool is only
309
+ # accessed while holding the connection's lock. (And we
310
+ # don't need the complication of with_raw_connection because
311
+ # a reconnect would invalidate the entire statement pool.)
312
+ if conn = @connection.instance_variable_get(:@raw_connection)
313
+ conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
314
+ end
315
+ rescue PG::Error
316
+ end
317
+ end
318
+
319
+ # Initializes and connects a PostgreSQL adapter.
320
+ def initialize(...)
321
+ super
322
+
323
+ conn_params = @config.compact
324
+
325
+ # Map ActiveRecords param names to PGs.
326
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
327
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
328
+
329
+ # Forward only valid config params to PG::Connection.connect.
330
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
331
+ conn_params.slice!(*valid_conn_param_keys)
332
+
333
+ @connection_parameters = conn_params
334
+
335
+ @max_identifier_length = nil
336
+ @type_map = nil
337
+ @raw_connection = nil
338
+ @notice_receiver_sql_warnings = []
339
+
340
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
341
+ end
342
+
343
+ def connected?
344
+ !(@raw_connection.nil? || @raw_connection.finished?)
345
+ end
346
+
347
+ # Is this connection alive and ready for queries?
348
+ def active?
349
+ @lock.synchronize do
350
+ return false unless @raw_connection
351
+ @raw_connection.query ";"
352
+ end
353
+ true
354
+ rescue PG::Error
355
+ false
356
+ end
357
+
358
+ def reload_type_map # :nodoc:
359
+ @lock.synchronize do
360
+ if @type_map
361
+ type_map.clear
362
+ else
363
+ @type_map = Type::HashLookupTypeMap.new
364
+ end
365
+
366
+ initialize_type_map
367
+ end
368
+ end
369
+
370
+ def reset!
371
+ @lock.synchronize do
372
+ return connect! unless @raw_connection
373
+
374
+ unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
375
+ @raw_connection.query "ROLLBACK"
376
+ end
377
+ @raw_connection.query "DISCARD ALL"
378
+
379
+ super
380
+ end
381
+ end
382
+
383
+ # Disconnects from the database if already connected. Otherwise, this
384
+ # method does nothing.
385
+ def disconnect!
386
+ @lock.synchronize do
387
+ super
388
+ @raw_connection&.close rescue nil
389
+ @raw_connection = nil
390
+ end
391
+ end
392
+
393
+ def discard! # :nodoc:
394
+ super
395
+ @raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
396
+ @raw_connection = nil
397
+ end
398
+
399
+ def native_database_types # :nodoc:
400
+ self.class.native_database_types
401
+ end
402
+
403
+ def self.native_database_types # :nodoc:
404
+ @native_database_types ||= begin
405
+ types = NATIVE_DATABASE_TYPES.dup
406
+ types[:datetime] = types[datetime_type]
407
+ types
408
+ end
409
+ end
410
+
411
+ def set_standard_conforming_strings
412
+ internal_execute("SET standard_conforming_strings = on", "SCHEMA")
413
+ end
414
+
415
+ def supports_ddl_transactions?
416
+ true
417
+ end
418
+
419
+ def supports_advisory_locks?
420
+ true
421
+ end
422
+
423
+ def supports_explain?
424
+ true
425
+ end
426
+
427
+ def supports_extensions?
428
+ true
429
+ end
430
+
431
+ def supports_materialized_views?
432
+ true
433
+ end
434
+
435
+ def supports_foreign_tables?
436
+ true
437
+ end
438
+
439
+ def supports_pgcrypto_uuid?
440
+ database_version >= 9_04_00 # >= 9.4
441
+ end
442
+
443
+ def supports_optimizer_hints?
444
+ unless defined?(@has_pg_hint_plan)
445
+ @has_pg_hint_plan = extension_available?("pg_hint_plan")
446
+ end
447
+ @has_pg_hint_plan
448
+ end
449
+
450
+ def supports_common_table_expressions?
451
+ true
452
+ end
453
+
454
+ def supports_lazy_transactions?
455
+ true
456
+ end
457
+
458
+ def get_advisory_lock(lock_id) # :nodoc:
459
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
460
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
461
+ end
462
+ query_value("SELECT pg_try_advisory_lock(#{lock_id})")
463
+ end
464
+
465
+ def release_advisory_lock(lock_id) # :nodoc:
466
+ unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
467
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
468
+ end
469
+ query_value("SELECT pg_advisory_unlock(#{lock_id})")
470
+ end
471
+
472
+ def enable_extension(name, **)
473
+ schema, name = name.to_s.split(".").values_at(-2, -1)
474
+ sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
475
+ sql << " SCHEMA #{schema}" if schema
476
+
477
+ internal_exec_query(sql).tap { reload_type_map }
478
+ end
479
+
480
+ # Removes an extension from the database.
481
+ #
482
+ # [<tt>:force</tt>]
483
+ # Set to +:cascade+ to drop dependent objects as well.
484
+ # Defaults to false.
485
+ def disable_extension(name, force: false)
486
+ _schema, name = name.to_s.split(".").values_at(-2, -1)
487
+ internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
488
+ reload_type_map
489
+ }
490
+ end
491
+
492
+ def extension_available?(name)
493
+ query_value("SELECT true FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
494
+ end
495
+
496
+ def extension_enabled?(name)
497
+ query_value("SELECT installed_version IS NOT NULL FROM pg_available_extensions WHERE name = #{quote(name)}", "SCHEMA")
498
+ end
499
+
500
+ def extensions
501
+ query = <<~SQL
502
+ SELECT
503
+ pg_extension.extname,
504
+ n.nspname AS schema
505
+ FROM pg_extension
506
+ JOIN pg_namespace n ON pg_extension.extnamespace = n.oid
507
+ SQL
508
+
509
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.map do |row|
510
+ name, schema = row[0], row[1]
511
+ schema = nil if schema == current_schema
512
+ [schema, name].compact.join(".")
513
+ end
514
+ end
515
+
516
+ # Returns a list of defined enum types, and their values.
517
+ def enum_types
518
+ query = <<~SQL
519
+ SELECT
520
+ type.typname AS name,
521
+ type.OID AS oid,
522
+ n.nspname AS schema,
523
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
524
+ FROM pg_enum AS enum
525
+ JOIN pg_type AS type ON (type.oid = enum.enumtypid)
526
+ JOIN pg_namespace n ON type.typnamespace = n.oid
527
+ WHERE n.nspname = ANY (current_schemas(false))
528
+ GROUP BY type.OID, n.nspname, type.typname;
529
+ SQL
530
+
531
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
532
+ name, schema = row[0], row[2]
533
+ schema = nil if schema == current_schema
534
+ full_name = [schema, name].compact.join(".")
535
+ memo[full_name] = row.last
536
+ end.to_a
537
+ end
538
+
539
+ # Given a name and an array of values, creates an enum type.
540
+ def create_enum(name, values, **options)
541
+ sql_values = values.map { |s| quote(s) }.join(", ")
542
+ scope = quoted_scope(name)
543
+ query = <<~SQL
544
+ DO $$
545
+ BEGIN
546
+ IF NOT EXISTS (
547
+ SELECT 1
548
+ FROM pg_type t
549
+ JOIN pg_namespace n ON t.typnamespace = n.oid
550
+ WHERE t.typname = #{scope[:name]}
551
+ AND n.nspname = #{scope[:schema]}
552
+ ) THEN
553
+ CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
554
+ END IF;
555
+ END
556
+ $$;
557
+ SQL
558
+ internal_exec_query(query).tap { reload_type_map }
559
+ end
560
+
561
+ # Drops an enum type.
562
+ #
563
+ # If the <tt>if_exists: true</tt> option is provided, the enum is dropped
564
+ # only if it exists. Otherwise, if the enum doesn't exist, an error is
565
+ # raised.
566
+ #
567
+ # The +values+ parameter will be ignored if present. It can be helpful
568
+ # to provide this in a migration's +change+ method so it can be reverted.
569
+ # In that case, +values+ will be used by #create_enum.
570
+ def drop_enum(name, values = nil, **options)
571
+ query = <<~SQL
572
+ DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
573
+ SQL
574
+ internal_exec_query(query).tap { reload_type_map }
575
+ end
576
+
577
+ # Rename an existing enum type to something else.
578
+ def rename_enum(name, **options)
579
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
580
+
581
+ exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
582
+ end
583
+
584
+ # Add enum value to an existing enum type.
585
+ def add_enum_value(type_name, value, **options)
586
+ before, after = options.values_at(:before, :after)
587
+ sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE"
588
+ sql << " IF NOT EXISTS" if options[:if_not_exists]
589
+ sql << " '#{value}'"
590
+
591
+ if before && after
592
+ raise ArgumentError, "Cannot have both :before and :after at the same time"
593
+ elsif before
594
+ sql << " BEFORE '#{before}'"
595
+ elsif after
596
+ sql << " AFTER '#{after}'"
597
+ end
598
+
599
+ execute(sql).tap { reload_type_map }
600
+ end
601
+
602
+ # Rename enum value on an existing enum type.
603
+ def rename_enum_value(type_name, **options)
604
+ unless database_version >= 10_00_00 # >= 10.0
605
+ raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
606
+ end
607
+
608
+ from = options.fetch(:from) { raise ArgumentError, ":from is required" }
609
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
610
+
611
+ execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
612
+ reload_type_map
613
+ }
614
+ end
615
+
616
+ # Returns the configured supported identifier length supported by PostgreSQL
617
+ def max_identifier_length
618
+ @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
619
+ end
620
+
621
+ # Set the authorized user for this session
622
+ def session_auth=(user)
623
+ clear_cache!
624
+ internal_execute("SET SESSION AUTHORIZATION #{user}", nil, materialize_transactions: true)
625
+ end
626
+
627
+ def use_insert_returning?
628
+ @use_insert_returning
629
+ end
630
+
631
+ # Returns the version of the connected PostgreSQL server.
632
+ def get_database_version # :nodoc:
633
+ with_raw_connection do |conn|
634
+ conn.server_version
635
+ end
636
+ end
637
+ alias :postgresql_version :database_version
638
+
639
+ def default_index_type?(index) # :nodoc:
640
+ index.using == :btree || super
641
+ end
642
+
643
+ def build_insert_sql(insert) # :nodoc:
644
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
645
+
646
+ if insert.skip_duplicates?
647
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
648
+ elsif insert.update_duplicates?
649
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
650
+ if insert.raw_update_sql?
651
+ sql << insert.raw_update_sql
652
+ else
653
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
654
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
655
+ end
656
+ end
657
+
658
+ sql << " RETURNING #{insert.returning}" if insert.returning
659
+ sql
660
+ end
661
+
662
+ def check_version # :nodoc:
663
+ if database_version < 9_03_00 # < 9.3
664
+ raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
665
+ end
666
+ end
667
+
668
+ class << self
669
+ def initialize_type_map(m) # :nodoc:
670
+ m.register_type "int2", Type::Integer.new(limit: 2)
671
+ m.register_type "int4", Type::Integer.new(limit: 4)
672
+ m.register_type "int8", Type::Integer.new(limit: 8)
673
+ m.register_type "oid", OID::Oid.new
674
+ m.register_type "float4", Type::Float.new
675
+ m.alias_type "float8", "float4"
676
+ m.register_type "text", Type::Text.new
677
+ register_class_with_limit m, "varchar", Type::String
678
+ m.alias_type "char", "varchar"
679
+ m.alias_type "name", "varchar"
680
+ m.alias_type "bpchar", "varchar"
681
+ m.register_type "bool", Type::Boolean.new
682
+ register_class_with_limit m, "bit", OID::Bit
683
+ register_class_with_limit m, "varbit", OID::BitVarying
684
+ m.register_type "date", OID::Date.new
685
+
686
+ m.register_type "money", OID::Money.new
687
+ m.register_type "bytea", OID::Bytea.new
688
+ m.register_type "point", OID::Point.new
689
+ m.register_type "hstore", OID::Hstore.new
690
+ m.register_type "json", Type::Json.new
691
+ m.register_type "jsonb", OID::Jsonb.new
692
+ m.register_type "cidr", OID::Cidr.new
693
+ m.register_type "inet", OID::Inet.new
694
+ m.register_type "uuid", OID::Uuid.new
695
+ m.register_type "xml", OID::Xml.new
696
+ m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
697
+ m.register_type "macaddr", OID::Macaddr.new
698
+ m.register_type "citext", OID::SpecializedString.new(:citext)
699
+ m.register_type "ltree", OID::SpecializedString.new(:ltree)
700
+ m.register_type "line", OID::SpecializedString.new(:line)
701
+ m.register_type "lseg", OID::SpecializedString.new(:lseg)
702
+ m.register_type "box", OID::SpecializedString.new(:box)
703
+ m.register_type "path", OID::SpecializedString.new(:path)
704
+ m.register_type "polygon", OID::SpecializedString.new(:polygon)
705
+ m.register_type "circle", OID::SpecializedString.new(:circle)
706
+
707
+ m.register_type "numeric" do |_, fmod, sql_type|
708
+ precision = extract_precision(sql_type)
709
+ scale = extract_scale(sql_type)
710
+
711
+ # The type for the numeric depends on the width of the field,
712
+ # so we'll do something special here.
713
+ #
714
+ # When dealing with decimal columns:
715
+ #
716
+ # places after decimal = fmod - 4 & 0xffff
717
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
718
+ if fmod && (fmod - 4 & 0xffff).zero?
719
+ # FIXME: Remove this class, and the second argument to
720
+ # lookups on PG
721
+ Type::DecimalWithoutScale.new(precision: precision)
722
+ else
723
+ OID::Decimal.new(precision: precision, scale: scale)
724
+ end
725
+ end
726
+
727
+ m.register_type "interval" do |*args, sql_type|
728
+ precision = extract_precision(sql_type)
729
+ OID::Interval.new(precision: precision)
730
+ end
731
+ end
732
+ end
733
+
734
+ private
735
+ attr_reader :type_map
736
+
737
+ def initialize_type_map(m = type_map)
738
+ self.class.initialize_type_map(m)
739
+
740
+ self.class.register_class_with_precision m, "time", Type::Time, timezone: @default_timezone
741
+ self.class.register_class_with_precision m, "timestamp", OID::Timestamp, timezone: @default_timezone
742
+ self.class.register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
743
+
744
+ load_additional_types
745
+ end
746
+
747
+ # Extracts the value from a PostgreSQL column default definition.
748
+ def extract_value_from_default(default)
749
+ case default
750
+ # Quoted types
751
+ when /\A[(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
752
+ # The default 'now'::date is CURRENT_DATE
753
+ if $1 == "now" && $2 == "date"
754
+ nil
755
+ else
756
+ $1.gsub("''", "'")
757
+ end
758
+ # Boolean types
759
+ when "true", "false"
760
+ default
761
+ # Numeric types
762
+ when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
763
+ $1
764
+ # Object identifier types
765
+ when /\A-?\d+\z/
766
+ $1
767
+ else
768
+ # Anything else is blank, some user type, or some function
769
+ # and we can't know the value of that, so return nil.
770
+ nil
771
+ end
772
+ end
773
+
774
+ def extract_default_function(default_value, default)
775
+ default if has_default_function?(default_value, default)
776
+ end
777
+
778
+ def has_default_function?(default_value, default)
779
+ !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
780
+ end
781
+
782
+ # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
783
+ VALUE_LIMIT_VIOLATION = "22001"
784
+ NUMERIC_VALUE_OUT_OF_RANGE = "22003"
785
+ NOT_NULL_VIOLATION = "23502"
786
+ FOREIGN_KEY_VIOLATION = "23503"
787
+ UNIQUE_VIOLATION = "23505"
788
+ SERIALIZATION_FAILURE = "40001"
789
+ DEADLOCK_DETECTED = "40P01"
790
+ DUPLICATE_DATABASE = "42P04"
791
+ LOCK_NOT_AVAILABLE = "55P03"
792
+ QUERY_CANCELED = "57014"
793
+
794
+ def translate_exception(exception, message:, sql:, binds:)
795
+ return exception unless exception.respond_to?(:result)
796
+
797
+ case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
798
+ when nil
799
+ if exception.message.match?(/connection is closed/i)
800
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
801
+ elsif exception.is_a?(PG::ConnectionBad)
802
+ # libpq message style always ends with a newline; the pg gem's internal
803
+ # errors do not. We separate these cases because a pg-internal
804
+ # ConnectionBad means it failed before it managed to send the query,
805
+ # whereas a libpq failure could have occurred at any time (meaning the
806
+ # server may have already executed part or all of the query).
807
+ if exception.message.end_with?("\n")
808
+ ConnectionFailed.new(exception, connection_pool: @pool)
809
+ else
810
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
811
+ end
812
+ else
813
+ super
814
+ end
815
+ when UNIQUE_VIOLATION
816
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
817
+ when FOREIGN_KEY_VIOLATION
818
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
819
+ when VALUE_LIMIT_VIOLATION
820
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
821
+ when NUMERIC_VALUE_OUT_OF_RANGE
822
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
823
+ when NOT_NULL_VIOLATION
824
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
825
+ when SERIALIZATION_FAILURE
826
+ SerializationFailure.new(message, sql: sql, binds: binds, connection_pool: @pool)
827
+ when DEADLOCK_DETECTED
828
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
829
+ when DUPLICATE_DATABASE
830
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
831
+ when LOCK_NOT_AVAILABLE
832
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
833
+ when QUERY_CANCELED
834
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
835
+ else
836
+ super
837
+ end
838
+ end
839
+
840
+ def retryable_query_error?(exception)
841
+ # We cannot retry anything if we're inside a broken transaction; we need to at
842
+ # least raise until the innermost savepoint is rolled back
843
+ @raw_connection&.transaction_status != ::PG::PQTRANS_INERROR &&
844
+ super
845
+ end
846
+
847
+ def get_oid_type(oid, fmod, column_name, sql_type = "")
848
+ if !type_map.key?(oid)
849
+ load_additional_types([oid])
850
+ end
851
+
852
+ type_map.fetch(oid, fmod, sql_type) {
853
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
854
+ Type.default_value.tap do |cast_type|
855
+ type_map.register_type(oid, cast_type)
856
+ end
857
+ }
858
+ end
859
+
860
+ def load_additional_types(oids = nil)
861
+ initializer = OID::TypeMapInitializer.new(type_map)
862
+ load_types_queries(initializer, oids) do |query|
863
+ records = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
864
+ initializer.run(records)
865
+ end
866
+ end
867
+
868
+ def load_types_queries(initializer, oids)
869
+ query = <<~SQL
870
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
871
+ FROM pg_type as t
872
+ LEFT JOIN pg_range as r ON oid = rngtypid
873
+ SQL
874
+ if oids
875
+ yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
876
+ else
877
+ yield query + initializer.query_conditions_for_known_type_names
878
+ yield query + initializer.query_conditions_for_known_type_types
879
+ yield query + initializer.query_conditions_for_array_types
880
+ end
881
+ end
882
+
883
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
884
+
885
+ # Annoyingly, the code for prepared statements whose return value may
886
+ # have changed is FEATURE_NOT_SUPPORTED.
887
+ #
888
+ # This covers various different error types so we need to do additional
889
+ # work to classify the exception definitively as a
890
+ # ActiveRecord::PreparedStatementCacheExpired
891
+ #
892
+ # Check here for more details:
893
+ # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
894
+ def is_cached_plan_failure?(pgerror)
895
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
896
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
897
+ rescue
898
+ false
899
+ end
900
+
901
+ def in_transaction?
902
+ open_transactions > 0
903
+ end
904
+
905
+ # Returns the statement identifier for the client side cache
906
+ # of statements
907
+ def sql_key(sql)
908
+ "#{schema_search_path}-#{sql}"
909
+ end
910
+
911
+ # Prepare the statement if it hasn't been prepared, return
912
+ # the statement key.
913
+ def prepare_statement(sql, binds, conn)
914
+ sql_key = sql_key(sql)
915
+ unless @statements.key? sql_key
916
+ nextkey = @statements.next_key
917
+ begin
918
+ conn.prepare nextkey, sql
919
+ rescue => e
920
+ raise translate_exception_class(e, sql, binds)
921
+ end
922
+ # Clear the queue
923
+ conn.get_last_result
924
+ @statements[sql_key] = nextkey
925
+ end
926
+ @statements[sql_key]
927
+ end
928
+
929
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
930
+ # connected server's characteristics.
931
+ def connect
932
+ @raw_connection = self.class.new_client(@connection_parameters)
933
+ rescue ConnectionNotEstablished => ex
934
+ raise ex.set_pool(@pool)
935
+ end
936
+
937
+ def reconnect
938
+ begin
939
+ @raw_connection&.reset
940
+ rescue PG::ConnectionBad
941
+ @raw_connection = nil
942
+ end
943
+
944
+ connect unless @raw_connection
945
+ end
946
+
947
+ # Configures the encoding, verbosity, schema search path, and time zone of the connection.
948
+ # This is called by #connect and should not be called manually.
949
+ def configure_connection
950
+ super
951
+
952
+ if @config[:encoding]
953
+ @raw_connection.set_client_encoding(@config[:encoding])
954
+ end
955
+ self.client_min_messages = @config[:min_messages] || "warning"
956
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
957
+
958
+ unless ActiveRecord.db_warnings_action.nil?
959
+ @raw_connection.set_notice_receiver do |result|
960
+ message = result.error_field(PG::Result::PG_DIAG_MESSAGE_PRIMARY)
961
+ code = result.error_field(PG::Result::PG_DIAG_SQLSTATE)
962
+ level = result.error_field(PG::Result::PG_DIAG_SEVERITY)
963
+ @notice_receiver_sql_warnings << SQLWarning.new(message, code, level, nil, @pool)
964
+ end
965
+ end
966
+
967
+ # Use standard-conforming strings so we don't have to do the E'...' dance.
968
+ set_standard_conforming_strings
969
+
970
+ variables = @config.fetch(:variables, {}).stringify_keys
971
+
972
+ # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
973
+ internal_execute("SET intervalstyle = iso_8601", "SCHEMA")
974
+
975
+ # SET statements from :variables config hash
976
+ # https://www.postgresql.org/docs/current/static/sql-set.html
977
+ variables.map do |k, v|
978
+ if v == ":default" || v == :default
979
+ # Sets the value to the global or compile default
980
+ internal_execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
981
+ elsif !v.nil?
982
+ internal_execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
983
+ end
984
+ end
985
+
986
+ add_pg_encoders
987
+ add_pg_decoders
988
+
989
+ reload_type_map
990
+ end
991
+
992
+ def reconfigure_connection_timezone
993
+ variables = @config.fetch(:variables, {}).stringify_keys
994
+
995
+ # If it's been directly configured as a connection variable, we don't
996
+ # need to do anything here; it will be set up by configure_connection
997
+ # and then never changed.
998
+ return if variables["timezone"]
999
+
1000
+ # If using Active Record's time zone support configure the connection
1001
+ # to return TIMESTAMP WITH ZONE types in UTC.
1002
+ if default_timezone == :utc
1003
+ raw_execute("SET SESSION timezone TO 'UTC'", "SCHEMA")
1004
+ else
1005
+ raw_execute("SET SESSION timezone TO DEFAULT", "SCHEMA")
1006
+ end
1007
+ end
1008
+
1009
+ # Returns the list of a table's column names, data types, and default values.
1010
+ #
1011
+ # The underlying query is roughly:
1012
+ # SELECT column.name, column.type, default.value, column.comment
1013
+ # FROM column LEFT JOIN default
1014
+ # ON column.table_id = default.table_id
1015
+ # AND column.num = default.column_num
1016
+ # WHERE column.table_id = get_table_id('table_name')
1017
+ # AND column.num > 0
1018
+ # AND NOT column.is_dropped
1019
+ # ORDER BY column.num
1020
+ #
1021
+ # If the table name is not prefixed with a schema, the database will
1022
+ # take the first match from the schema search path.
1023
+ #
1024
+ # Query implementation notes:
1025
+ # - format_type includes the column size constraint, e.g. varchar(50)
1026
+ # - ::regclass is a function that gives the id for a table name
1027
+ def column_definitions(table_name)
1028
+ query(<<~SQL, "SCHEMA")
1029
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1030
+ pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
1031
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
1032
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
1033
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
1034
+ FROM pg_attribute a
1035
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
1036
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
1037
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
1038
+ WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
1039
+ AND a.attnum > 0 AND NOT a.attisdropped
1040
+ ORDER BY a.attnum
1041
+ SQL
1042
+ end
1043
+
1044
+ def arel_visitor
1045
+ Arel::Visitors::PostgreSQL.new(self)
1046
+ end
1047
+
1048
+ def build_statement_pool
1049
+ StatementPool.new(self, self.class.type_cast_config_to_integer(@config[:statement_limit]))
1050
+ end
1051
+
1052
+ def can_perform_case_insensitive_comparison_for?(column)
1053
+ # NOTE: citext is an exception. It is possible to perform a
1054
+ # case-insensitive comparison using `LOWER()`, but it is
1055
+ # unnecessary, as `citext` is case-insensitive by definition.
1056
+ @case_insensitive_cache ||= { "citext" => false }
1057
+ @case_insensitive_cache.fetch(column.sql_type) do
1058
+ @case_insensitive_cache[column.sql_type] = begin
1059
+ sql = <<~SQL
1060
+ SELECT exists(
1061
+ SELECT * FROM pg_proc
1062
+ WHERE proname = 'lower'
1063
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
1064
+ ) OR exists(
1065
+ SELECT * FROM pg_proc
1066
+ INNER JOIN pg_cast
1067
+ ON ARRAY[casttarget]::oidvector = proargtypes
1068
+ WHERE proname = 'lower'
1069
+ AND castsource = #{quote column.sql_type}::regtype
1070
+ )
1071
+ SQL
1072
+ result = internal_execute(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
1073
+ result.getvalue(0, 0)
1074
+ end
1075
+ end
1076
+ end
1077
+
1078
+ def add_pg_encoders
1079
+ map = PG::TypeMapByClass.new
1080
+ map[Integer] = PG::TextEncoder::Integer.new
1081
+ map[TrueClass] = PG::TextEncoder::Boolean.new
1082
+ map[FalseClass] = PG::TextEncoder::Boolean.new
1083
+ @raw_connection.type_map_for_queries = map
1084
+ end
1085
+
1086
+ def update_typemap_for_default_timezone
1087
+ if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
1088
+ decoder_class = default_timezone == :utc ?
1089
+ PG::TextDecoder::TimestampUtc :
1090
+ PG::TextDecoder::TimestampWithoutTimeZone
1091
+
1092
+ @timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h)
1093
+ @raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
1094
+
1095
+ @mapped_default_timezone = default_timezone
1096
+
1097
+ # if default timezone has changed, we need to reconfigure the connection
1098
+ # (specifically, the session time zone)
1099
+ reconfigure_connection_timezone
1100
+
1101
+ true
1102
+ end
1103
+ end
1104
+
1105
+ def add_pg_decoders
1106
+ @mapped_default_timezone = nil
1107
+ @timestamp_decoder = nil
1108
+
1109
+ coders_by_name = {
1110
+ "int2" => PG::TextDecoder::Integer,
1111
+ "int4" => PG::TextDecoder::Integer,
1112
+ "int8" => PG::TextDecoder::Integer,
1113
+ "oid" => PG::TextDecoder::Integer,
1114
+ "float4" => PG::TextDecoder::Float,
1115
+ "float8" => PG::TextDecoder::Float,
1116
+ "numeric" => PG::TextDecoder::Numeric,
1117
+ "bool" => PG::TextDecoder::Boolean,
1118
+ "timestamp" => PG::TextDecoder::TimestampUtc,
1119
+ "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
1120
+ }
1121
+ coders_by_name["date"] = PG::TextDecoder::Date if decode_dates
1122
+
1123
+ known_coder_types = coders_by_name.keys.map { |n| quote(n) }
1124
+ query = <<~SQL % known_coder_types.join(", ")
1125
+ SELECT t.oid, t.typname
1126
+ FROM pg_type as t
1127
+ WHERE t.typname IN (%s)
1128
+ SQL
1129
+ result = internal_execute(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false)
1130
+ coders = result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
1131
+
1132
+ map = PG::TypeMapByOid.new
1133
+ coders.each { |coder| map.add_coder(coder) }
1134
+ @raw_connection.type_map_for_results = map
1135
+
1136
+ @type_map_for_results = PG::TypeMapByOid.new
1137
+ @type_map_for_results.default_type_map = map
1138
+ @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
1139
+ @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
1140
+
1141
+ # extract timestamp decoder for use in update_typemap_for_default_timezone
1142
+ @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
1143
+ update_typemap_for_default_timezone
1144
+ end
1145
+
1146
+ def construct_coder(row, coder_class)
1147
+ return unless coder_class
1148
+ coder_class.new(oid: row["oid"].to_i, name: row["typname"])
1149
+ end
1150
+
1151
+ class MoneyDecoder < PG::SimpleDecoder # :nodoc:
1152
+ TYPE = OID::Money.new
1153
+
1154
+ def decode(value, tuple = nil, field = nil)
1155
+ TYPE.deserialize(value)
1156
+ end
1157
+ end
1158
+
1159
+ ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
1160
+ ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
1161
+ ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
1162
+ ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
1163
+ ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
1164
+ ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
1165
+ ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql)
1166
+ ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
1167
+ ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
1168
+ ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
1169
+ ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
1170
+ ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
1171
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
1172
+ ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
1173
+ ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
1174
+ ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
1175
+ ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql)
1176
+ ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql)
1177
+ ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
1178
+ ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
1179
+ end
1180
+ ActiveSupport.run_load_hooks(:active_record_postgresqladapter, PostgreSQLAdapter)
1181
+ end
1182
+ end