omg-activerecord 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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